~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2009-06-19 19:26:32 UTC
  • mto: This revision was merged to the branch mainline in revision 4466.
  • Revision ID: john@arbash-meinel.com-20090619192632-1a4ntoq61fkhlp2x
Make a note of the 'worst case' for heads.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
 
18
import stat
 
19
from StringIO import StringIO
 
20
import sys
18
21
 
 
22
from bzrlib import (
 
23
    bencode,
 
24
    errors,
 
25
    generate_ids,
 
26
    osutils,
 
27
    progress,
 
28
    revision as _mod_revision,
 
29
    symbol_versioning,
 
30
    tests,
 
31
    urlutils,
 
32
    )
19
33
from bzrlib.bzrdir import BzrDir
20
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
 
                              UnversionedParent, ParentLoop)
 
35
                              UnversionedParent, ParentLoop, DeletingParent,
 
36
                              NonDirectoryParent)
 
37
from bzrlib.diff import show_diff_trees
22
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
 
                           ReusingTransform, CantMoveRoot, 
 
39
                           ReusingTransform, CantMoveRoot,
24
40
                           PathsNotVersionedError, ExistingLimbo,
25
 
                           ImmortalLimbo, LockError)
26
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
27
 
from bzrlib.merge import Merge3Merger
28
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
29
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
30
 
                              resolve_conflicts, cook_conflicts, 
31
 
                              find_interesting, build_tree, get_backup_name)
32
 
import bzrlib.urlutils as urlutils
33
 
 
34
 
class TestTreeTransform(TestCaseInTempDir):
 
41
                           ExistingPendingDeletion, ImmortalLimbo,
 
42
                           ImmortalPendingDeletion, LockError)
 
43
from bzrlib.osutils import file_kind, pathjoin
 
44
from bzrlib.merge import Merge3Merger, Merger
 
45
from bzrlib.tests import (
 
46
    HardlinkFeature,
 
47
    SymlinkFeature,
 
48
    TestCase,
 
49
    TestCaseInTempDir,
 
50
    TestSkipped,
 
51
    )
 
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
 
53
                              resolve_conflicts, cook_conflicts,
 
54
                              build_tree, get_backup_name,
 
55
                              _FileMover, resolve_checkout,
 
56
                              TransformPreview, create_from_tree)
 
57
 
 
58
 
 
59
class TestTreeTransform(tests.TestCaseWithTransport):
35
60
 
36
61
    def setUp(self):
37
62
        super(TestTreeTransform, self).setUp()
38
 
        self.wt = BzrDir.create_standalone_workingtree('.')
 
63
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
39
64
        os.chdir('..')
40
65
 
41
66
    def get_transform(self):
42
67
        transform = TreeTransform(self.wt)
43
 
        #self.addCleanup(transform.finalize)
44
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
68
        self.addCleanup(transform.finalize)
 
69
        return transform, transform.root
45
70
 
46
71
    def test_existing_limbo(self):
47
 
        limbo_name = urlutils.local_path_from_url(
48
 
            self.wt._control_files.controlfilename('limbo'))
49
72
        transform, root = self.get_transform()
 
73
        limbo_name = transform._limbodir
 
74
        deletion_path = transform._deletiondir
50
75
        os.mkdir(pathjoin(limbo_name, 'hehe'))
51
76
        self.assertRaises(ImmortalLimbo, transform.apply)
52
77
        self.assertRaises(LockError, self.wt.unlock)
54
79
        self.assertRaises(LockError, self.wt.unlock)
55
80
        os.rmdir(pathjoin(limbo_name, 'hehe'))
56
81
        os.rmdir(limbo_name)
 
82
        os.rmdir(deletion_path)
57
83
        transform, root = self.get_transform()
58
84
        transform.apply()
59
85
 
 
86
    def test_existing_pending_deletion(self):
 
87
        transform, root = self.get_transform()
 
88
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
89
            transform._tree._transport.abspath('pending-deletion'))
 
90
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
91
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
92
        self.assertRaises(LockError, self.wt.unlock)
 
93
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
94
 
60
95
    def test_build(self):
61
 
        transform, root = self.get_transform() 
 
96
        transform, root = self.get_transform()
 
97
        self.wt.lock_tree_write()
 
98
        self.addCleanup(self.wt.unlock)
62
99
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
63
100
        imaginary_id = transform.trans_id_tree_path('imaginary')
64
101
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
92
129
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
93
130
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
94
131
        self.assertEqual(len(modified_paths), 3)
95
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
132
        tree_mod_paths = [self.wt.id2abspath(f) for f in
96
133
                          ('ozzie', 'my_pretties', 'my_pretties2')]
97
134
        self.assertSubset(tree_mod_paths, modified_paths)
98
135
        # is it safe to finalize repeatedly?
99
136
        transform.finalize()
100
137
        transform.finalize()
101
138
 
 
139
    def test_hardlink(self):
 
140
        self.requireFeature(HardlinkFeature)
 
141
        transform, root = self.get_transform()
 
142
        transform.new_file('file1', root, 'contents')
 
143
        transform.apply()
 
144
        target = self.make_branch_and_tree('target')
 
145
        target_transform = TreeTransform(target)
 
146
        trans_id = target_transform.create_path('file1', target_transform.root)
 
147
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
 
148
        target_transform.apply()
 
149
        self.failUnlessExists('target/file1')
 
150
        source_stat = os.stat(self.wt.abspath('file1'))
 
151
        target_stat = os.stat('target/file1')
 
152
        self.assertEqual(source_stat, target_stat)
 
153
 
102
154
    def test_convenience(self):
103
155
        transform, root = self.get_transform()
104
 
        trans_id = transform.new_file('name', root, 'contents', 
 
156
        self.wt.lock_tree_write()
 
157
        self.addCleanup(self.wt.unlock)
 
158
        trans_id = transform.new_file('name', root, 'contents',
105
159
                                      'my_pretties', True)
106
160
        oz = transform.new_directory('oz', root, 'oz-id')
107
161
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
108
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
162
        toto = transform.new_file('toto', dorothy, 'toto-contents',
109
163
                                  'toto-id', False)
110
164
 
111
165
        self.assertEqual(len(transform.find_conflicts()), 0)
118
172
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
119
173
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
120
174
 
121
 
        self.assertEqual('toto-contents', 
 
175
        self.assertEqual('toto-contents',
122
176
                         self.wt.get_file_byname('oz/dorothy/toto').read())
123
177
        self.assertIs(self.wt.is_executable('toto-id'), False)
124
178
 
 
179
    def test_tree_reference(self):
 
180
        transform, root = self.get_transform()
 
181
        tree = transform._tree
 
182
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
183
        transform.set_tree_reference('subtree-revision', trans_id)
 
184
        transform.apply()
 
185
        tree.lock_read()
 
186
        self.addCleanup(tree.unlock)
 
187
        self.assertEqual('subtree-revision',
 
188
                         tree.inventory['subtree-id'].reference_revision)
 
189
 
125
190
    def test_conflicts(self):
126
191
        transform, root = self.get_transform()
127
 
        trans_id = transform.new_file('name', root, 'contents', 
 
192
        trans_id = transform.new_file('name', root, 'contents',
128
193
                                      'my_pretties')
129
194
        self.assertEqual(len(transform.find_conflicts()), 0)
130
195
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
131
 
        self.assertEqual(transform.find_conflicts(), 
 
196
        self.assertEqual(transform.find_conflicts(),
132
197
                         [('duplicate', trans_id, trans_id2, 'name')])
133
198
        self.assertRaises(MalformedTransform, transform.apply)
134
199
        transform.adjust_path('name', trans_id, trans_id2)
135
 
        self.assertEqual(transform.find_conflicts(), 
 
200
        self.assertEqual(transform.find_conflicts(),
136
201
                         [('non-directory parent', trans_id)])
137
202
        tinman_id = transform.trans_id_tree_path('tinman')
138
203
        transform.adjust_path('name', tinman_id, trans_id2)
139
 
        self.assertEqual(transform.find_conflicts(), 
140
 
                         [('unversioned parent', tinman_id), 
 
204
        self.assertEqual(transform.find_conflicts(),
 
205
                         [('unversioned parent', tinman_id),
141
206
                          ('missing parent', tinman_id)])
142
207
        lion_id = transform.create_path('lion', root)
143
 
        self.assertEqual(transform.find_conflicts(), 
144
 
                         [('unversioned parent', tinman_id), 
 
208
        self.assertEqual(transform.find_conflicts(),
 
209
                         [('unversioned parent', tinman_id),
145
210
                          ('missing parent', tinman_id)])
146
211
        transform.adjust_path('name', lion_id, trans_id2)
147
 
        self.assertEqual(transform.find_conflicts(), 
 
212
        self.assertEqual(transform.find_conflicts(),
148
213
                         [('unversioned parent', lion_id),
149
214
                          ('missing parent', lion_id)])
150
215
        transform.version_file("Courage", lion_id)
151
 
        self.assertEqual(transform.find_conflicts(), 
152
 
                         [('missing parent', lion_id), 
 
216
        self.assertEqual(transform.find_conflicts(),
 
217
                         [('missing parent', lion_id),
153
218
                          ('versioning no contents', lion_id)])
154
219
        transform.adjust_path('name2', root, trans_id2)
155
 
        self.assertEqual(transform.find_conflicts(), 
 
220
        self.assertEqual(transform.find_conflicts(),
156
221
                         [('versioning no contents', lion_id)])
157
222
        transform.create_file('Contents, okay?', lion_id)
158
223
        transform.adjust_path('name2', trans_id2, trans_id2)
159
 
        self.assertEqual(transform.find_conflicts(), 
160
 
                         [('parent loop', trans_id2), 
 
224
        self.assertEqual(transform.find_conflicts(),
 
225
                         [('parent loop', trans_id2),
161
226
                          ('non-directory parent', trans_id2)])
162
227
        transform.adjust_path('name2', root, trans_id2)
163
228
        oz_id = transform.new_directory('oz', root)
164
229
        transform.set_executability(True, oz_id)
165
 
        self.assertEqual(transform.find_conflicts(), 
 
230
        self.assertEqual(transform.find_conflicts(),
166
231
                         [('unversioned executability', oz_id)])
167
232
        transform.version_file('oz-id', oz_id)
168
 
        self.assertEqual(transform.find_conflicts(), 
 
233
        self.assertEqual(transform.find_conflicts(),
169
234
                         [('non-file executability', oz_id)])
170
235
        transform.set_executability(None, oz_id)
171
236
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
180
245
        self.assert_('oz/tip' in transform2._tree_path_ids)
181
246
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
182
247
        self.assertEqual(len(result), 2)
183
 
        self.assertEqual((result[0][0], result[0][1]), 
 
248
        self.assertEqual((result[0][0], result[0][1]),
184
249
                         ('duplicate', newtip))
185
 
        self.assertEqual((result[1][0], result[1][2]), 
 
250
        self.assertEqual((result[1][0], result[1][2]),
186
251
                         ('duplicate id', newtip))
187
252
        transform2.finalize()
188
253
        transform3 = TreeTransform(self.wt)
189
254
        self.addCleanup(transform3.finalize)
190
255
        oz_id = transform3.trans_id_tree_file_id('oz-id')
191
256
        transform3.delete_contents(oz_id)
192
 
        self.assertEqual(transform3.find_conflicts(), 
 
257
        self.assertEqual(transform3.find_conflicts(),
193
258
                         [('missing parent', oz_id)])
194
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
259
        root_id = transform3.root
195
260
        tip_id = transform3.trans_id_tree_file_id('tip-id')
196
261
        transform3.adjust_path('tip', root_id, tip_id)
197
262
        transform3.apply()
198
263
 
 
264
    def test_conflict_on_case_insensitive(self):
 
265
        tree = self.make_branch_and_tree('tree')
 
266
        # Don't try this at home, kids!
 
267
        # Force the tree to report that it is case sensitive, for conflict
 
268
        # resolution tests
 
269
        tree.case_sensitive = True
 
270
        transform = TreeTransform(tree)
 
271
        self.addCleanup(transform.finalize)
 
272
        transform.new_file('file', transform.root, 'content')
 
273
        transform.new_file('FiLe', transform.root, 'content')
 
274
        result = transform.find_conflicts()
 
275
        self.assertEqual([], result)
 
276
        transform.finalize()
 
277
        # Force the tree to report that it is case insensitive, for conflict
 
278
        # generation tests
 
279
        tree.case_sensitive = False
 
280
        transform = TreeTransform(tree)
 
281
        self.addCleanup(transform.finalize)
 
282
        transform.new_file('file', transform.root, 'content')
 
283
        transform.new_file('FiLe', transform.root, 'content')
 
284
        result = transform.find_conflicts()
 
285
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
286
 
 
287
    def test_conflict_on_case_insensitive_existing(self):
 
288
        tree = self.make_branch_and_tree('tree')
 
289
        self.build_tree(['tree/FiLe'])
 
290
        # Don't try this at home, kids!
 
291
        # Force the tree to report that it is case sensitive, for conflict
 
292
        # resolution tests
 
293
        tree.case_sensitive = True
 
294
        transform = TreeTransform(tree)
 
295
        self.addCleanup(transform.finalize)
 
296
        transform.new_file('file', transform.root, 'content')
 
297
        result = transform.find_conflicts()
 
298
        self.assertEqual([], result)
 
299
        transform.finalize()
 
300
        # Force the tree to report that it is case insensitive, for conflict
 
301
        # generation tests
 
302
        tree.case_sensitive = False
 
303
        transform = TreeTransform(tree)
 
304
        self.addCleanup(transform.finalize)
 
305
        transform.new_file('file', transform.root, 'content')
 
306
        result = transform.find_conflicts()
 
307
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
308
 
 
309
    def test_resolve_case_insensitive_conflict(self):
 
310
        tree = self.make_branch_and_tree('tree')
 
311
        # Don't try this at home, kids!
 
312
        # Force the tree to report that it is case insensitive, for conflict
 
313
        # resolution tests
 
314
        tree.case_sensitive = False
 
315
        transform = TreeTransform(tree)
 
316
        self.addCleanup(transform.finalize)
 
317
        transform.new_file('file', transform.root, 'content')
 
318
        transform.new_file('FiLe', transform.root, 'content')
 
319
        resolve_conflicts(transform)
 
320
        transform.apply()
 
321
        self.failUnlessExists('tree/file')
 
322
        self.failUnlessExists('tree/FiLe.moved')
 
323
 
 
324
    def test_resolve_checkout_case_conflict(self):
 
325
        tree = self.make_branch_and_tree('tree')
 
326
        # Don't try this at home, kids!
 
327
        # Force the tree to report that it is case insensitive, for conflict
 
328
        # resolution tests
 
329
        tree.case_sensitive = False
 
330
        transform = TreeTransform(tree)
 
331
        self.addCleanup(transform.finalize)
 
332
        transform.new_file('file', transform.root, 'content')
 
333
        transform.new_file('FiLe', transform.root, 'content')
 
334
        resolve_conflicts(transform,
 
335
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
 
336
        transform.apply()
 
337
        self.failUnlessExists('tree/file')
 
338
        self.failUnlessExists('tree/FiLe.moved')
 
339
 
 
340
    def test_apply_case_conflict(self):
 
341
        """Ensure that a transform with case conflicts can always be applied"""
 
342
        tree = self.make_branch_and_tree('tree')
 
343
        transform = TreeTransform(tree)
 
344
        self.addCleanup(transform.finalize)
 
345
        transform.new_file('file', transform.root, 'content')
 
346
        transform.new_file('FiLe', transform.root, 'content')
 
347
        dir = transform.new_directory('dir', transform.root)
 
348
        transform.new_file('dirfile', dir, 'content')
 
349
        transform.new_file('dirFiLe', dir, 'content')
 
350
        resolve_conflicts(transform)
 
351
        transform.apply()
 
352
        self.failUnlessExists('tree/file')
 
353
        if not os.path.exists('tree/FiLe.moved'):
 
354
            self.failUnlessExists('tree/FiLe')
 
355
        self.failUnlessExists('tree/dir/dirfile')
 
356
        if not os.path.exists('tree/dir/dirFiLe.moved'):
 
357
            self.failUnlessExists('tree/dir/dirFiLe')
 
358
 
 
359
    def test_case_insensitive_limbo(self):
 
360
        tree = self.make_branch_and_tree('tree')
 
361
        # Don't try this at home, kids!
 
362
        # Force the tree to report that it is case insensitive
 
363
        tree.case_sensitive = False
 
364
        transform = TreeTransform(tree)
 
365
        self.addCleanup(transform.finalize)
 
366
        dir = transform.new_directory('dir', transform.root)
 
367
        first = transform.new_file('file', dir, 'content')
 
368
        second = transform.new_file('FiLe', dir, 'content')
 
369
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
 
370
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
 
371
 
199
372
    def test_add_del(self):
200
373
        start, root = self.get_transform()
201
374
        start.new_directory('a', root, 'a')
214
387
        self.addCleanup(unversion.finalize)
215
388
        parent = unversion.trans_id_tree_path('parent')
216
389
        unversion.unversion_file(parent)
217
 
        self.assertEqual(unversion.find_conflicts(), 
 
390
        self.assertEqual(unversion.find_conflicts(),
218
391
                         [('unversioned parent', parent_id)])
219
392
        file_id = unversion.trans_id_tree_file_id('child-id')
220
393
        unversion.unversion_file(file_id)
223
396
    def test_name_invariants(self):
224
397
        create_tree, root = self.get_transform()
225
398
        # prepare tree
226
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
399
        root = create_tree.root
227
400
        create_tree.new_file('name1', root, 'hello1', 'name1')
228
401
        create_tree.new_file('name2', root, 'hello2', 'name2')
229
402
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
233
406
        create_tree.apply()
234
407
 
235
408
        mangle_tree,root = self.get_transform()
236
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
409
        root = mangle_tree.root
237
410
        #swap names
238
411
        name1 = mangle_tree.trans_id_tree_file_id('name1')
239
412
        name2 = mangle_tree.trans_id_tree_file_id('name2')
240
413
        mangle_tree.adjust_path('name2', root, name1)
241
414
        mangle_tree.adjust_path('name1', root, name2)
242
415
 
243
 
        #tests for deleting parent directories 
 
416
        #tests for deleting parent directories
244
417
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
245
418
        mangle_tree.delete_contents(ddir)
246
419
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
275
448
        create_tree,root = self.get_transform()
276
449
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
277
450
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
278
 
        create_tree.apply()        
 
451
        create_tree.apply()
279
452
        mangle_tree,root = self.get_transform()
280
453
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
281
454
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
289
462
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
290
463
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
291
464
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
292
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
465
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
293
466
                             'test_too_much-id')
294
 
        create_tree.apply()        
 
467
        create_tree.apply()
295
468
        mangle_tree,root = self.get_transform()
296
469
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
297
470
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
298
471
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
299
472
        mangle_tree.adjust_path('selftest', bzrlib, tests)
300
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
473
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
301
474
        mangle_tree.set_executability(True, test_too_much)
302
475
        mangle_tree.apply()
303
476
 
304
477
    def test_both_rename3(self):
305
478
        create_tree,root = self.get_transform()
306
479
        tests = create_tree.new_directory('tests', root, 'tests-id')
307
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
480
        create_tree.new_file('test_too_much.py', tests, 'hello1',
308
481
                             'test_too_much-id')
309
 
        create_tree.apply()        
 
482
        create_tree.apply()
310
483
        mangle_tree,root = self.get_transform()
311
484
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
312
485
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
313
486
        mangle_tree.adjust_path('selftest', root, tests)
314
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
487
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
315
488
        mangle_tree.set_executability(True, test_too_much)
316
489
        mangle_tree.apply()
317
490
 
318
491
    def test_move_dangling_ie(self):
319
492
        create_tree, root = self.get_transform()
320
493
        # prepare tree
321
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
494
        root = create_tree.root
322
495
        create_tree.new_file('name1', root, 'hello1', 'name1')
323
496
        create_tree.apply()
324
497
        delete_contents, root = self.get_transform()
330
503
        newdir = move_id.new_directory('dir', root, 'newdir')
331
504
        move_id.adjust_path('name2', newdir, name1)
332
505
        move_id.apply()
333
 
        
 
506
 
334
507
    def test_replace_dangling_ie(self):
335
508
        create_tree, root = self.get_transform()
336
509
        # prepare tree
337
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
510
        root = create_tree.root
338
511
        create_tree.new_file('name1', root, 'hello1', 'name1')
339
512
        create_tree.apply()
340
513
        delete_contents = TreeTransform(self.wt)
352
525
        resolve_conflicts(replace)
353
526
        replace.apply()
354
527
 
355
 
    def test_symlinks(self):
356
 
        if not has_symlinks():
357
 
            raise TestSkipped('Symlinks are not supported on this platform')
358
 
        transform,root = self.get_transform()
 
528
    def _test_symlinks(self, link_name1,link_target1,
 
529
                       link_name2, link_target2):
 
530
 
 
531
        def ozpath(p): return 'oz/' + p
 
532
 
 
533
        self.requireFeature(SymlinkFeature)
 
534
        transform, root = self.get_transform()
359
535
        oz_id = transform.new_directory('oz', root, 'oz-id')
360
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
536
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
361
537
                                       'wizard-id')
362
 
        wiz_id = transform.create_path('wizard2', oz_id)
363
 
        transform.create_symlink('behind_curtain', wiz_id)
364
 
        transform.version_file('wiz-id2', wiz_id)            
 
538
        wiz_id = transform.create_path(link_name2, oz_id)
 
539
        transform.create_symlink(link_target2, wiz_id)
 
540
        transform.version_file('wiz-id2', wiz_id)
365
541
        transform.set_executability(True, wiz_id)
366
 
        self.assertEqual(transform.find_conflicts(), 
 
542
        self.assertEqual(transform.find_conflicts(),
367
543
                         [('non-file executability', wiz_id)])
368
544
        transform.set_executability(None, wiz_id)
369
545
        transform.apply()
370
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
371
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
372
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
373
 
                         'behind_curtain')
374
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
375
 
                         'wizard-target')
376
 
 
 
546
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
 
547
        self.assertEqual('symlink',
 
548
                         file_kind(self.wt.abspath(ozpath(link_name1))))
 
549
        self.assertEqual(link_target2,
 
550
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
 
551
        self.assertEqual(link_target1,
 
552
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
 
553
 
 
554
    def test_symlinks(self):
 
555
        self._test_symlinks('wizard', 'wizard-target',
 
556
                            'wizard2', 'behind_curtain')
 
557
 
 
558
    def test_symlinks_unicode(self):
 
559
        self.requireFeature(tests.UnicodeFilenameFeature)
 
560
        self._test_symlinks(u'\N{Euro Sign}wizard',
 
561
                            u'wizard-targ\N{Euro Sign}t',
 
562
                            u'\N{Euro Sign}wizard2',
 
563
                            u'b\N{Euro Sign}hind_curtain')
 
564
 
 
565
    def test_unable_create_symlink(self):
 
566
        def tt_helper():
 
567
            wt = self.make_branch_and_tree('.')
 
568
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
569
            try:
 
570
                tt.new_symlink('foo', tt.root, 'bar')
 
571
                tt.apply()
 
572
            finally:
 
573
                wt.unlock()
 
574
        os_symlink = getattr(os, 'symlink', None)
 
575
        os.symlink = None
 
576
        try:
 
577
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
578
            self.assertEquals(
 
579
                "Unable to create symlink 'foo' on this platform",
 
580
                str(err))
 
581
        finally:
 
582
            if os_symlink:
 
583
                os.symlink = os_symlink
377
584
 
378
585
    def get_conflicted(self):
379
586
        create,root = self.get_transform()
383
590
        create.apply()
384
591
        conflicts,root = self.get_transform()
385
592
        # set up duplicate entry, duplicate id
386
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
593
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
387
594
                                         'dorothy-id')
388
595
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
389
596
        oz = conflicts.trans_id_tree_file_id('oz-id')
390
 
        # set up missing, unversioned parent
 
597
        # set up DeletedParent parent conflict
391
598
        conflicts.delete_versioned(oz)
392
599
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
600
        # set up MissingParent conflict
 
601
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
602
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
603
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
393
604
        # set up parent loop
394
605
        conflicts.adjust_path('emeraldcity', emerald, emerald)
395
606
        return conflicts, emerald, oz, old_dorothy, new_dorothy
409
620
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
410
621
        raw_conflicts = resolve_conflicts(tt)
411
622
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
412
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
623
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
413
624
                                   'dorothy', None, 'dorothy-id')
414
625
        self.assertEqual(cooked_conflicts[0], duplicate)
415
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
626
        duplicate_id = DuplicateID('Unversioned existing file',
416
627
                                   'dorothy.moved', 'dorothy', None,
417
628
                                   'dorothy-id')
418
629
        self.assertEqual(cooked_conflicts[1], duplicate_id)
419
 
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
 
630
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
631
                                       'munchkincity-id')
 
632
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
420
633
        self.assertEqual(cooked_conflicts[2], missing_parent)
421
 
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
 
634
        unversioned_parent = UnversionedParent('Versioned directory',
 
635
                                               'munchkincity',
 
636
                                               'munchkincity-id')
 
637
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
422
638
                                               'oz-id')
423
639
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
424
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
640
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
425
641
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
426
 
        self.assertEqual(cooked_conflicts[4], parent_loop)
427
 
        self.assertEqual(len(cooked_conflicts), 5)
 
642
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
643
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
644
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
645
        self.assertEqual(len(cooked_conflicts), 7)
428
646
        tt.finalize()
429
647
 
430
648
    def test_string_conflicts(self):
440
658
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
441
659
                                         'Unversioned existing file '
442
660
                                         'dorothy.moved.')
443
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
444
 
                                         'Not deleting.')
445
 
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
446
 
                                         'oz.  Versioned directory.')
447
 
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
 
661
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
662
                                         ' munchkincity.  Created directory.')
 
663
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
664
                                         ' versioned, but has versioned'
 
665
                                         ' children.  Versioned directory.')
 
666
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
667
                                         " is not empty.  Not deleting.")
 
668
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
669
                                         ' versioned, but has versioned'
 
670
                                         ' children.  Versioned directory.')
 
671
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
448
672
                                         ' oz/emeraldcity.  Cancelled move.')
449
673
 
 
674
    def prepare_wrong_parent_kind(self):
 
675
        tt, root = self.get_transform()
 
676
        tt.new_file('parent', root, 'contents', 'parent-id')
 
677
        tt.apply()
 
678
        tt, root = self.get_transform()
 
679
        parent_id = tt.trans_id_file_id('parent-id')
 
680
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
681
        return tt
 
682
 
 
683
    def test_find_conflicts_wrong_parent_kind(self):
 
684
        tt = self.prepare_wrong_parent_kind()
 
685
        tt.find_conflicts()
 
686
 
 
687
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
 
688
        tt = self.prepare_wrong_parent_kind()
 
689
        raw_conflicts = resolve_conflicts(tt)
 
690
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
691
                         'new-3')]), raw_conflicts)
 
692
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
693
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
 
694
        'parent-id')], cooked_conflicts)
 
695
        tt.apply()
 
696
        self.assertEqual(None, self.wt.path2id('parent'))
 
697
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
698
 
 
699
    def test_resolve_conflicts_wrong_new_parent_kind(self):
 
700
        tt, root = self.get_transform()
 
701
        parent_id = tt.new_directory('parent', root, 'parent-id')
 
702
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
703
        tt.apply()
 
704
        tt, root = self.get_transform()
 
705
        parent_id = tt.trans_id_file_id('parent-id')
 
706
        tt.delete_contents(parent_id)
 
707
        tt.create_file('contents', parent_id)
 
708
        raw_conflicts = resolve_conflicts(tt)
 
709
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
710
                         'new-3')]), raw_conflicts)
 
711
        tt.apply()
 
712
        self.assertEqual(None, self.wt.path2id('parent'))
 
713
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
714
 
 
715
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
 
716
        tt, root = self.get_transform()
 
717
        parent_id = tt.new_directory('parent', root)
 
718
        tt.new_file('child,', parent_id, 'contents2')
 
719
        tt.apply()
 
720
        tt, root = self.get_transform()
 
721
        parent_id = tt.trans_id_tree_path('parent')
 
722
        tt.delete_contents(parent_id)
 
723
        tt.create_file('contents', parent_id)
 
724
        resolve_conflicts(tt)
 
725
        tt.apply()
 
726
        self.assertIs(None, self.wt.path2id('parent'))
 
727
        self.assertIs(None, self.wt.path2id('parent.new'))
 
728
 
450
729
    def test_moving_versioned_directories(self):
451
730
        create, root = self.get_transform()
452
731
        kansas = create.new_directory('kansas', root, 'kansas-id')
485
764
        rename.set_executability(True, myfile)
486
765
        rename.apply()
487
766
 
488
 
    def test_find_interesting(self):
489
 
        create, root = self.get_transform()
490
 
        wt = create._tree
491
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
492
 
        create.new_file('uvfile', root, 'othertext')
493
 
        create.apply()
494
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
495
 
                         set(['myfile-id']))
496
 
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
497
 
                          ['uvfile'])
498
 
 
499
767
    def test_set_executability_order(self):
500
768
        """Ensure that executability behaves the same, no matter what order.
501
 
        
 
769
 
502
770
        - create file and set executability simultaneously
503
771
        - create file and set executability afterward
504
772
        - unsetting the executability of a file whose executability has not been
507
775
        """
508
776
        transform, root = self.get_transform()
509
777
        wt = transform._tree
 
778
        wt.lock_read()
 
779
        self.addCleanup(wt.unlock)
510
780
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
511
781
                           True)
512
 
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
 
782
        sac = transform.new_file('set_after_creation', root,
 
783
                                 'Set after creation', 'sac')
513
784
        transform.set_executability(True, sac)
514
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
 
785
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
786
                                 'uws')
515
787
        self.assertRaises(KeyError, transform.set_executability, None, uws)
516
788
        transform.apply()
517
789
        self.assertTrue(wt.is_executable('soc'))
518
790
        self.assertTrue(wt.is_executable('sac'))
519
791
 
 
792
    def test_preserve_mode(self):
 
793
        """File mode is preserved when replacing content"""
 
794
        if sys.platform == 'win32':
 
795
            raise TestSkipped('chmod has no effect on win32')
 
796
        transform, root = self.get_transform()
 
797
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
798
        transform.apply()
 
799
        self.wt.lock_write()
 
800
        self.addCleanup(self.wt.unlock)
 
801
        self.assertTrue(self.wt.is_executable('file1-id'))
 
802
        transform, root = self.get_transform()
 
803
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
804
        transform.delete_contents(file1_id)
 
805
        transform.create_file('contents2', file1_id)
 
806
        transform.apply()
 
807
        self.assertTrue(self.wt.is_executable('file1-id'))
 
808
 
 
809
    def test__set_mode_stats_correctly(self):
 
810
        """_set_mode stats to determine file mode."""
 
811
        if sys.platform == 'win32':
 
812
            raise TestSkipped('chmod has no effect on win32')
 
813
 
 
814
        stat_paths = []
 
815
        real_stat = os.stat
 
816
        def instrumented_stat(path):
 
817
            stat_paths.append(path)
 
818
            return real_stat(path)
 
819
 
 
820
        transform, root = self.get_transform()
 
821
 
 
822
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
823
                                     file_id='bar-id-1', executable=False)
 
824
        transform.apply()
 
825
 
 
826
        transform, root = self.get_transform()
 
827
        bar1_id = transform.trans_id_tree_path('bar')
 
828
        bar2_id = transform.trans_id_tree_path('bar2')
 
829
        try:
 
830
            os.stat = instrumented_stat
 
831
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
832
        finally:
 
833
            os.stat = real_stat
 
834
            transform.finalize()
 
835
 
 
836
        bar1_abspath = self.wt.abspath('bar')
 
837
        self.assertEqual([bar1_abspath], stat_paths)
 
838
 
 
839
    def test_iter_changes(self):
 
840
        self.wt.set_root_id('eert_toor')
 
841
        transform, root = self.get_transform()
 
842
        transform.new_file('old', root, 'blah', 'id-1', True)
 
843
        transform.apply()
 
844
        transform, root = self.get_transform()
 
845
        try:
 
846
            self.assertEqual([], list(transform.iter_changes()))
 
847
            old = transform.trans_id_tree_file_id('id-1')
 
848
            transform.unversion_file(old)
 
849
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
850
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
851
                (True, True))], list(transform.iter_changes()))
 
852
            transform.new_directory('new', root, 'id-1')
 
853
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
854
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
855
                ('file', 'directory'),
 
856
                (True, False))], list(transform.iter_changes()))
 
857
        finally:
 
858
            transform.finalize()
 
859
 
 
860
    def test_iter_changes_new(self):
 
861
        self.wt.set_root_id('eert_toor')
 
862
        transform, root = self.get_transform()
 
863
        transform.new_file('old', root, 'blah')
 
864
        transform.apply()
 
865
        transform, root = self.get_transform()
 
866
        try:
 
867
            old = transform.trans_id_tree_path('old')
 
868
            transform.version_file('id-1', old)
 
869
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
870
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
871
                (False, False))], list(transform.iter_changes()))
 
872
        finally:
 
873
            transform.finalize()
 
874
 
 
875
    def test_iter_changes_modifications(self):
 
876
        self.wt.set_root_id('eert_toor')
 
877
        transform, root = self.get_transform()
 
878
        transform.new_file('old', root, 'blah', 'id-1')
 
879
        transform.new_file('new', root, 'blah')
 
880
        transform.new_directory('subdir', root, 'subdir-id')
 
881
        transform.apply()
 
882
        transform, root = self.get_transform()
 
883
        try:
 
884
            old = transform.trans_id_tree_path('old')
 
885
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
886
            new = transform.trans_id_tree_path('new')
 
887
            self.assertEqual([], list(transform.iter_changes()))
 
888
 
 
889
            #content deletion
 
890
            transform.delete_contents(old)
 
891
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
892
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
893
                (False, False))], list(transform.iter_changes()))
 
894
 
 
895
            #content change
 
896
            transform.create_file('blah', old)
 
897
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
898
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
899
                (False, False))], list(transform.iter_changes()))
 
900
            transform.cancel_deletion(old)
 
901
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
902
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
903
                (False, False))], list(transform.iter_changes()))
 
904
            transform.cancel_creation(old)
 
905
 
 
906
            # move file_id to a different file
 
907
            self.assertEqual([], list(transform.iter_changes()))
 
908
            transform.unversion_file(old)
 
909
            transform.version_file('id-1', new)
 
910
            transform.adjust_path('old', root, new)
 
911
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
912
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
913
                (False, False))], list(transform.iter_changes()))
 
914
            transform.cancel_versioning(new)
 
915
            transform._removed_id = set()
 
916
 
 
917
            #execute bit
 
918
            self.assertEqual([], list(transform.iter_changes()))
 
919
            transform.set_executability(True, old)
 
920
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
921
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
922
                (False, True))], list(transform.iter_changes()))
 
923
            transform.set_executability(None, old)
 
924
 
 
925
            # filename
 
926
            self.assertEqual([], list(transform.iter_changes()))
 
927
            transform.adjust_path('new', root, old)
 
928
            transform._new_parent = {}
 
929
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
930
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
931
                (False, False))], list(transform.iter_changes()))
 
932
            transform._new_name = {}
 
933
 
 
934
            # parent directory
 
935
            self.assertEqual([], list(transform.iter_changes()))
 
936
            transform.adjust_path('new', subdir, old)
 
937
            transform._new_name = {}
 
938
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
939
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
940
                ('file', 'file'), (False, False))],
 
941
                list(transform.iter_changes()))
 
942
            transform._new_path = {}
 
943
 
 
944
        finally:
 
945
            transform.finalize()
 
946
 
 
947
    def test_iter_changes_modified_bleed(self):
 
948
        self.wt.set_root_id('eert_toor')
 
949
        """Modified flag should not bleed from one change to another"""
 
950
        # unfortunately, we have no guarantee that file1 (which is modified)
 
951
        # will be applied before file2.  And if it's applied after file2, it
 
952
        # obviously can't bleed into file2's change output.  But for now, it
 
953
        # works.
 
954
        transform, root = self.get_transform()
 
955
        transform.new_file('file1', root, 'blah', 'id-1')
 
956
        transform.new_file('file2', root, 'blah', 'id-2')
 
957
        transform.apply()
 
958
        transform, root = self.get_transform()
 
959
        try:
 
960
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
961
            transform.set_executability(True,
 
962
            transform.trans_id_file_id('id-2'))
 
963
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
964
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
965
                ('file', None), (False, False)),
 
966
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
967
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
968
                ('file', 'file'), (False, True))],
 
969
                list(transform.iter_changes()))
 
970
        finally:
 
971
            transform.finalize()
 
972
 
 
973
    def test_iter_changes_move_missing(self):
 
974
        """Test moving ids with no files around"""
 
975
        self.wt.set_root_id('toor_eert')
 
976
        # Need two steps because versioning a non-existant file is a conflict.
 
977
        transform, root = self.get_transform()
 
978
        transform.new_directory('floater', root, 'floater-id')
 
979
        transform.apply()
 
980
        transform, root = self.get_transform()
 
981
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
982
        transform.apply()
 
983
        transform, root = self.get_transform()
 
984
        floater = transform.trans_id_tree_path('floater')
 
985
        try:
 
986
            transform.adjust_path('flitter', root, floater)
 
987
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
988
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
989
            (None, None), (False, False))], list(transform.iter_changes()))
 
990
        finally:
 
991
            transform.finalize()
 
992
 
 
993
    def test_iter_changes_pointless(self):
 
994
        """Ensure that no-ops are not treated as modifications"""
 
995
        self.wt.set_root_id('eert_toor')
 
996
        transform, root = self.get_transform()
 
997
        transform.new_file('old', root, 'blah', 'id-1')
 
998
        transform.new_directory('subdir', root, 'subdir-id')
 
999
        transform.apply()
 
1000
        transform, root = self.get_transform()
 
1001
        try:
 
1002
            old = transform.trans_id_tree_path('old')
 
1003
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
1004
            self.assertEqual([], list(transform.iter_changes()))
 
1005
            transform.delete_contents(subdir)
 
1006
            transform.create_directory(subdir)
 
1007
            transform.set_executability(False, old)
 
1008
            transform.unversion_file(old)
 
1009
            transform.version_file('id-1', old)
 
1010
            transform.adjust_path('old', root, old)
 
1011
            self.assertEqual([], list(transform.iter_changes()))
 
1012
        finally:
 
1013
            transform.finalize()
 
1014
 
 
1015
    def test_rename_count(self):
 
1016
        transform, root = self.get_transform()
 
1017
        transform.new_file('name1', root, 'contents')
 
1018
        self.assertEqual(transform.rename_count, 0)
 
1019
        transform.apply()
 
1020
        self.assertEqual(transform.rename_count, 1)
 
1021
        transform2, root = self.get_transform()
 
1022
        transform2.adjust_path('name2', root,
 
1023
                               transform2.trans_id_tree_path('name1'))
 
1024
        self.assertEqual(transform2.rename_count, 0)
 
1025
        transform2.apply()
 
1026
        self.assertEqual(transform2.rename_count, 2)
 
1027
 
 
1028
    def test_change_parent(self):
 
1029
        """Ensure that after we change a parent, the results are still right.
 
1030
 
 
1031
        Renames and parent changes on pending transforms can happen as part
 
1032
        of conflict resolution, and are explicitly permitted by the
 
1033
        TreeTransform API.
 
1034
 
 
1035
        This test ensures they work correctly with the rename-avoidance
 
1036
        optimization.
 
1037
        """
 
1038
        transform, root = self.get_transform()
 
1039
        parent1 = transform.new_directory('parent1', root)
 
1040
        child1 = transform.new_file('child1', parent1, 'contents')
 
1041
        parent2 = transform.new_directory('parent2', root)
 
1042
        transform.adjust_path('child1', parent2, child1)
 
1043
        transform.apply()
 
1044
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
1045
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1046
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1047
        # no rename for child1 (counting only renames during apply)
 
1048
        self.failUnlessEqual(2, transform.rename_count)
 
1049
 
 
1050
    def test_cancel_parent(self):
 
1051
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1052
 
 
1053
        This is like the test_change_parent, except that we cancel the parent
 
1054
        before adjusting the path.  The transform must detect that the
 
1055
        directory is non-empty, and move children to safe locations.
 
1056
        """
 
1057
        transform, root = self.get_transform()
 
1058
        parent1 = transform.new_directory('parent1', root)
 
1059
        child1 = transform.new_file('child1', parent1, 'contents')
 
1060
        child2 = transform.new_file('child2', parent1, 'contents')
 
1061
        try:
 
1062
            transform.cancel_creation(parent1)
 
1063
        except OSError:
 
1064
            self.fail('Failed to move child1 before deleting parent1')
 
1065
        transform.cancel_creation(child2)
 
1066
        transform.create_directory(parent1)
 
1067
        try:
 
1068
            transform.cancel_creation(parent1)
 
1069
        # If the transform incorrectly believes that child2 is still in
 
1070
        # parent1's limbo directory, it will try to rename it and fail
 
1071
        # because was already moved by the first cancel_creation.
 
1072
        except OSError:
 
1073
            self.fail('Transform still thinks child2 is a child of parent1')
 
1074
        parent2 = transform.new_directory('parent2', root)
 
1075
        transform.adjust_path('child1', parent2, child1)
 
1076
        transform.apply()
 
1077
        self.failIfExists(self.wt.abspath('parent1'))
 
1078
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1079
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1080
        self.failUnlessEqual(2, transform.rename_count)
 
1081
 
 
1082
    def test_adjust_and_cancel(self):
 
1083
        """Make sure adjust_path keeps track of limbo children properly"""
 
1084
        transform, root = self.get_transform()
 
1085
        parent1 = transform.new_directory('parent1', root)
 
1086
        child1 = transform.new_file('child1', parent1, 'contents')
 
1087
        parent2 = transform.new_directory('parent2', root)
 
1088
        transform.adjust_path('child1', parent2, child1)
 
1089
        transform.cancel_creation(child1)
 
1090
        try:
 
1091
            transform.cancel_creation(parent1)
 
1092
        # if the transform thinks child1 is still in parent1's limbo
 
1093
        # directory, it will attempt to move it and fail.
 
1094
        except OSError:
 
1095
            self.fail('Transform still thinks child1 is a child of parent1')
 
1096
        transform.finalize()
 
1097
 
 
1098
    def test_noname_contents(self):
 
1099
        """TreeTransform should permit deferring naming files."""
 
1100
        transform, root = self.get_transform()
 
1101
        parent = transform.trans_id_file_id('parent-id')
 
1102
        try:
 
1103
            transform.create_directory(parent)
 
1104
        except KeyError:
 
1105
            self.fail("Can't handle contents with no name")
 
1106
        transform.finalize()
 
1107
 
 
1108
    def test_noname_contents_nested(self):
 
1109
        """TreeTransform should permit deferring naming files."""
 
1110
        transform, root = self.get_transform()
 
1111
        parent = transform.trans_id_file_id('parent-id')
 
1112
        try:
 
1113
            transform.create_directory(parent)
 
1114
        except KeyError:
 
1115
            self.fail("Can't handle contents with no name")
 
1116
        child = transform.new_directory('child', parent)
 
1117
        transform.adjust_path('parent', root, parent)
 
1118
        transform.apply()
 
1119
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1120
        self.assertEqual(1, transform.rename_count)
 
1121
 
 
1122
    def test_reuse_name(self):
 
1123
        """Avoid reusing the same limbo name for different files"""
 
1124
        transform, root = self.get_transform()
 
1125
        parent = transform.new_directory('parent', root)
 
1126
        child1 = transform.new_directory('child', parent)
 
1127
        try:
 
1128
            child2 = transform.new_directory('child', parent)
 
1129
        except OSError:
 
1130
            self.fail('Tranform tried to use the same limbo name twice')
 
1131
        transform.adjust_path('child2', parent, child2)
 
1132
        transform.apply()
 
1133
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1134
        # child2 is put into top-level limbo because child1 has already
 
1135
        # claimed the direct limbo path when child2 is created.  There is no
 
1136
        # advantage in renaming files once they're in top-level limbo, except
 
1137
        # as part of apply.
 
1138
        self.assertEqual(2, transform.rename_count)
 
1139
 
 
1140
    def test_reuse_when_first_moved(self):
 
1141
        """Don't avoid direct paths when it is safe to use them"""
 
1142
        transform, root = self.get_transform()
 
1143
        parent = transform.new_directory('parent', root)
 
1144
        child1 = transform.new_directory('child', parent)
 
1145
        transform.adjust_path('child1', parent, child1)
 
1146
        child2 = transform.new_directory('child', parent)
 
1147
        transform.apply()
 
1148
        # limbo/new-1 => parent
 
1149
        self.assertEqual(1, transform.rename_count)
 
1150
 
 
1151
    def test_reuse_after_cancel(self):
 
1152
        """Don't avoid direct paths when it is safe to use them"""
 
1153
        transform, root = self.get_transform()
 
1154
        parent2 = transform.new_directory('parent2', root)
 
1155
        child1 = transform.new_directory('child1', parent2)
 
1156
        transform.cancel_creation(parent2)
 
1157
        transform.create_directory(parent2)
 
1158
        child2 = transform.new_directory('child1', parent2)
 
1159
        transform.adjust_path('child2', parent2, child1)
 
1160
        transform.apply()
 
1161
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1162
        self.assertEqual(2, transform.rename_count)
 
1163
 
 
1164
    def test_finalize_order(self):
 
1165
        """Finalize must be done in child-to-parent order"""
 
1166
        transform, root = self.get_transform()
 
1167
        parent = transform.new_directory('parent', root)
 
1168
        child = transform.new_directory('child', parent)
 
1169
        try:
 
1170
            transform.finalize()
 
1171
        except OSError:
 
1172
            self.fail('Tried to remove parent before child1')
 
1173
 
 
1174
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1175
        transform, root = self.get_transform()
 
1176
        parent = transform.new_directory('parent', root)
 
1177
        child = transform.new_directory('child', parent)
 
1178
        transform.cancel_creation(child)
 
1179
        transform.cancel_creation(parent)
 
1180
        transform.finalize()
 
1181
 
 
1182
    def test_rollback_on_directory_clash(self):
 
1183
        def tt_helper():
 
1184
            wt = self.make_branch_and_tree('.')
 
1185
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1186
            try:
 
1187
                foo = tt.new_directory('foo', tt.root)
 
1188
                tt.new_file('bar', foo, 'foobar')
 
1189
                baz = tt.new_directory('baz', tt.root)
 
1190
                tt.new_file('qux', baz, 'quux')
 
1191
                # Ask for a rename 'foo' -> 'baz'
 
1192
                tt.adjust_path('baz', tt.root, foo)
 
1193
                # Lie to tt that we've already resolved all conflicts.
 
1194
                tt.apply(no_conflicts=True)
 
1195
            except:
 
1196
                wt.unlock()
 
1197
                raise
 
1198
        # The rename will fail because the target directory is not empty (but
 
1199
        # raises FileExists anyway).
 
1200
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1201
        self.assertContainsRe(str(err),
 
1202
            "^File exists: .+/baz")
 
1203
 
 
1204
    def test_two_directories_clash(self):
 
1205
        def tt_helper():
 
1206
            wt = self.make_branch_and_tree('.')
 
1207
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1208
            try:
 
1209
                foo_1 = tt.new_directory('foo', tt.root)
 
1210
                tt.new_directory('bar', foo_1)
 
1211
                # Adding the same directory with a different content
 
1212
                foo_2 = tt.new_directory('foo', tt.root)
 
1213
                tt.new_directory('baz', foo_2)
 
1214
                # Lie to tt that we've already resolved all conflicts.
 
1215
                tt.apply(no_conflicts=True)
 
1216
            except:
 
1217
                wt.unlock()
 
1218
                raise
 
1219
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1220
        self.assertContainsRe(str(err),
 
1221
            "^File exists: .+/foo")
 
1222
 
 
1223
    def test_two_directories_clash_finalize(self):
 
1224
        def tt_helper():
 
1225
            wt = self.make_branch_and_tree('.')
 
1226
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1227
            try:
 
1228
                foo_1 = tt.new_directory('foo', tt.root)
 
1229
                tt.new_directory('bar', foo_1)
 
1230
                # Adding the same directory with a different content
 
1231
                foo_2 = tt.new_directory('foo', tt.root)
 
1232
                tt.new_directory('baz', foo_2)
 
1233
                # Lie to tt that we've already resolved all conflicts.
 
1234
                tt.apply(no_conflicts=True)
 
1235
            except:
 
1236
                tt.finalize()
 
1237
                raise
 
1238
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1239
        self.assertContainsRe(str(err),
 
1240
            "^File exists: .+/foo")
 
1241
 
 
1242
    def test_file_to_directory(self):
 
1243
        wt = self.make_branch_and_tree('.')
 
1244
        self.build_tree(['foo'])
 
1245
        wt.add(['foo'])
 
1246
        wt.commit("one")
 
1247
        tt = TreeTransform(wt)
 
1248
        self.addCleanup(tt.finalize)
 
1249
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1250
        tt.delete_contents(foo_trans_id)
 
1251
        tt.create_directory(foo_trans_id)
 
1252
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1253
        tt.create_file(["aa\n"], bar_trans_id)
 
1254
        tt.version_file("bar-1", bar_trans_id)
 
1255
        tt.apply()
 
1256
        self.failUnlessExists("foo/bar")
 
1257
        wt.lock_read()
 
1258
        try:
 
1259
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1260
                    "directory")
 
1261
        finally:
 
1262
            wt.unlock()
 
1263
        wt.commit("two")
 
1264
        changes = wt.changes_from(wt.basis_tree())
 
1265
        self.assertFalse(changes.has_changed(), changes)
 
1266
 
 
1267
    def test_file_to_symlink(self):
 
1268
        self.requireFeature(SymlinkFeature)
 
1269
        wt = self.make_branch_and_tree('.')
 
1270
        self.build_tree(['foo'])
 
1271
        wt.add(['foo'])
 
1272
        wt.commit("one")
 
1273
        tt = TreeTransform(wt)
 
1274
        self.addCleanup(tt.finalize)
 
1275
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1276
        tt.delete_contents(foo_trans_id)
 
1277
        tt.create_symlink("bar", foo_trans_id)
 
1278
        tt.apply()
 
1279
        self.failUnlessExists("foo")
 
1280
        wt.lock_read()
 
1281
        self.addCleanup(wt.unlock)
 
1282
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1283
                "symlink")
 
1284
 
 
1285
    def test_dir_to_file(self):
 
1286
        wt = self.make_branch_and_tree('.')
 
1287
        self.build_tree(['foo/', 'foo/bar'])
 
1288
        wt.add(['foo', 'foo/bar'])
 
1289
        wt.commit("one")
 
1290
        tt = TreeTransform(wt)
 
1291
        self.addCleanup(tt.finalize)
 
1292
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1293
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1294
        tt.delete_contents(foo_trans_id)
 
1295
        tt.delete_versioned(bar_trans_id)
 
1296
        tt.create_file(["aa\n"], foo_trans_id)
 
1297
        tt.apply()
 
1298
        self.failUnlessExists("foo")
 
1299
        wt.lock_read()
 
1300
        self.addCleanup(wt.unlock)
 
1301
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1302
                "file")
 
1303
 
 
1304
    def test_dir_to_hardlink(self):
 
1305
        self.requireFeature(HardlinkFeature)
 
1306
        wt = self.make_branch_and_tree('.')
 
1307
        self.build_tree(['foo/', 'foo/bar'])
 
1308
        wt.add(['foo', 'foo/bar'])
 
1309
        wt.commit("one")
 
1310
        tt = TreeTransform(wt)
 
1311
        self.addCleanup(tt.finalize)
 
1312
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1313
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1314
        tt.delete_contents(foo_trans_id)
 
1315
        tt.delete_versioned(bar_trans_id)
 
1316
        self.build_tree(['baz'])
 
1317
        tt.create_hardlink("baz", foo_trans_id)
 
1318
        tt.apply()
 
1319
        self.failUnlessExists("foo")
 
1320
        self.failUnlessExists("baz")
 
1321
        wt.lock_read()
 
1322
        self.addCleanup(wt.unlock)
 
1323
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1324
                "file")
 
1325
 
 
1326
    def test_no_final_path(self):
 
1327
        transform, root = self.get_transform()
 
1328
        trans_id = transform.trans_id_file_id('foo')
 
1329
        transform.create_file('bar', trans_id)
 
1330
        transform.cancel_creation(trans_id)
 
1331
        transform.apply()
 
1332
 
 
1333
    def test_create_from_tree(self):
 
1334
        tree1 = self.make_branch_and_tree('tree1')
 
1335
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
 
1336
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
 
1337
        tree2 = self.make_branch_and_tree('tree2')
 
1338
        tt = TreeTransform(tree2)
 
1339
        foo_trans_id = tt.create_path('foo', tt.root)
 
1340
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1341
        bar_trans_id = tt.create_path('bar', tt.root)
 
1342
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
 
1343
        tt.apply()
 
1344
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
 
1345
        self.assertFileEqual('baz', 'tree2/bar')
 
1346
 
 
1347
    def test_create_from_tree_bytes(self):
 
1348
        """Provided lines are used instead of tree content."""
 
1349
        tree1 = self.make_branch_and_tree('tree1')
 
1350
        self.build_tree_contents([('tree1/foo', 'bar'),])
 
1351
        tree1.add('foo', 'foo-id')
 
1352
        tree2 = self.make_branch_and_tree('tree2')
 
1353
        tt = TreeTransform(tree2)
 
1354
        foo_trans_id = tt.create_path('foo', tt.root)
 
1355
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
 
1356
        tt.apply()
 
1357
        self.assertFileEqual('qux', 'tree2/foo')
 
1358
 
 
1359
    def test_create_from_tree_symlink(self):
 
1360
        self.requireFeature(SymlinkFeature)
 
1361
        tree1 = self.make_branch_and_tree('tree1')
 
1362
        os.symlink('bar', 'tree1/foo')
 
1363
        tree1.add('foo', 'foo-id')
 
1364
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
 
1365
        foo_trans_id = tt.create_path('foo', tt.root)
 
1366
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1367
        tt.apply()
 
1368
        self.assertEqual('bar', os.readlink('tree2/foo'))
 
1369
 
520
1370
 
521
1371
class TransformGroup(object):
522
 
    def __init__(self, dirname):
 
1372
 
 
1373
    def __init__(self, dirname, root_id):
523
1374
        self.name = dirname
524
1375
        os.mkdir(dirname)
525
1376
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1377
        self.wt.set_root_id(root_id)
526
1378
        self.b = self.wt.branch
527
1379
        self.tt = TreeTransform(self.wt)
528
1380
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
529
1381
 
 
1382
 
530
1383
def conflict_text(tree, merge):
531
1384
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
532
1385
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
533
1386
 
534
1387
 
535
1388
class TestTransformMerge(TestCaseInTempDir):
 
1389
 
536
1390
    def test_text_merge(self):
537
 
        base = TransformGroup("base")
 
1391
        root_id = generate_ids.gen_root_id()
 
1392
        base = TransformGroup("base", root_id)
538
1393
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
539
1394
        base.tt.new_file('b', base.root, 'b1', 'b')
540
1395
        base.tt.new_file('c', base.root, 'c', 'c')
544
1399
        base.tt.new_directory('g', base.root, 'g')
545
1400
        base.tt.new_directory('h', base.root, 'h')
546
1401
        base.tt.apply()
547
 
        other = TransformGroup("other")
 
1402
        other = TransformGroup("other", root_id)
548
1403
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
549
1404
        other.tt.new_file('b', other.root, 'b2', 'b')
550
1405
        other.tt.new_file('c', other.root, 'c2', 'c')
555
1410
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
556
1411
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
557
1412
        other.tt.apply()
558
 
        this = TransformGroup("this")
 
1413
        this = TransformGroup("this", root_id)
559
1414
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
560
1415
        this.tt.new_file('b', this.root, 'b', 'b')
561
1416
        this.tt.new_file('c', this.root, 'c', 'c')
567
1422
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
568
1423
        this.tt.apply()
569
1424
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1425
 
570
1426
        # textual merge
571
1427
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
572
1428
        # three-way text conflict
573
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1429
        self.assertEqual(this.wt.get_file('b').read(),
574
1430
                         conflict_text('b', 'b2'))
575
1431
        # OTHER wins
576
1432
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
580
1436
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
581
1437
        # No change
582
1438
        self.assertEqual(this.wt.get_file('f').read(), 'f')
583
 
        # Correct correct results when THIS == OTHER 
 
1439
        # Correct correct results when THIS == OTHER
584
1440
        self.assertEqual(this.wt.get_file('g').read(), 'g')
585
1441
        # Text conflict when THIS & OTHER are text and BASE is dir
586
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1442
        self.assertEqual(this.wt.get_file('h').read(),
587
1443
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
588
1444
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
589
1445
                         '1\n2\n3\n4\n')
590
1446
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
591
1447
                         'h\ni\nj\nk\n')
592
1448
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
593
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1449
        self.assertEqual(this.wt.get_file('i').read(),
594
1450
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
595
1451
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
596
1452
                         '1\n2\n3\n4\n')
607
1463
        self.assertSubset(merge_modified, modified)
608
1464
        self.assertEqual(len(merge_modified), len(modified))
609
1465
        this.wt.remove('b')
610
 
        this.wt.revert([])
 
1466
        this.wt.revert()
611
1467
 
612
1468
    def test_file_merge(self):
613
 
        if not has_symlinks():
614
 
            raise TestSkipped('Symlinks are not supported on this platform')
615
 
        base = TransformGroup("BASE")
616
 
        this = TransformGroup("THIS")
617
 
        other = TransformGroup("OTHER")
 
1469
        self.requireFeature(SymlinkFeature)
 
1470
        root_id = generate_ids.gen_root_id()
 
1471
        base = TransformGroup("BASE", root_id)
 
1472
        this = TransformGroup("THIS", root_id)
 
1473
        other = TransformGroup("OTHER", root_id)
618
1474
        for tg in this, base, other:
619
1475
            tg.tt.new_directory('a', tg.root, 'a')
620
1476
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
621
1477
            tg.tt.new_file('c', tg.root, 'c', 'c')
622
1478
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
623
 
        targets = ((base, 'base-e', 'base-f', None, None), 
624
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1479
        targets = ((base, 'base-e', 'base-f', None, None),
 
1480
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
625
1481
                   (other, 'other-e', None, 'other-g', 'other-h'))
626
1482
        for tg, e_target, f_target, g_target, h_target in targets:
627
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1483
            for link, target in (('e', e_target), ('f', f_target),
628
1484
                                 ('g', g_target), ('h', h_target)):
629
1485
                if target is not None:
630
1486
                    tg.tt.new_symlink(link, tg.root, target, link)
652
1508
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
653
1509
 
654
1510
    def test_filename_merge(self):
655
 
        base = TransformGroup("BASE")
656
 
        this = TransformGroup("THIS")
657
 
        other = TransformGroup("OTHER")
658
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1511
        root_id = generate_ids.gen_root_id()
 
1512
        base = TransformGroup("BASE", root_id)
 
1513
        this = TransformGroup("THIS", root_id)
 
1514
        other = TransformGroup("OTHER", root_id)
 
1515
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
659
1516
                                   for t in [base, this, other]]
660
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1517
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
661
1518
                                   for t in [base, this, other]]
662
1519
        base.tt.new_directory('c', base_a, 'c')
663
1520
        this.tt.new_directory('c1', this_a, 'c')
684
1541
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
685
1542
 
686
1543
    def test_filename_merge_conflicts(self):
687
 
        base = TransformGroup("BASE")
688
 
        this = TransformGroup("THIS")
689
 
        other = TransformGroup("OTHER")
690
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1544
        root_id = generate_ids.gen_root_id()
 
1545
        base = TransformGroup("BASE", root_id)
 
1546
        this = TransformGroup("THIS", root_id)
 
1547
        other = TransformGroup("OTHER", root_id)
 
1548
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
691
1549
                                   for t in [base, this, other]]
692
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1550
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
693
1551
                                   for t in [base, this, other]]
694
1552
 
695
1553
        base.tt.new_file('g', base_a, 'g', 'g')
713
1571
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
714
1572
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
715
1573
 
716
 
class TestBuildTree(TestCaseInTempDir):
717
 
    def test_build_tree(self):
718
 
        if not has_symlinks():
719
 
            raise TestSkipped('Test requires symlink support')
 
1574
 
 
1575
class TestBuildTree(tests.TestCaseWithTransport):
 
1576
 
 
1577
    def test_build_tree_with_symlinks(self):
 
1578
        self.requireFeature(SymlinkFeature)
720
1579
        os.mkdir('a')
721
1580
        a = BzrDir.create_standalone_workingtree('a')
722
1581
        os.mkdir('a/foo')
725
1584
        a.add(['foo', 'foo/bar', 'foo/baz'])
726
1585
        a.commit('initial commit')
727
1586
        b = BzrDir.create_standalone_workingtree('b')
728
 
        build_tree(a.basis_tree(), b)
 
1587
        basis = a.basis_tree()
 
1588
        basis.lock_read()
 
1589
        self.addCleanup(basis.unlock)
 
1590
        build_tree(basis, b)
729
1591
        self.assertIs(os.path.isdir('b/foo'), True)
730
1592
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
731
1593
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
732
 
        
 
1594
 
 
1595
    def test_build_with_references(self):
 
1596
        tree = self.make_branch_and_tree('source',
 
1597
            format='dirstate-with-subtree')
 
1598
        subtree = self.make_branch_and_tree('source/subtree',
 
1599
            format='dirstate-with-subtree')
 
1600
        tree.add_reference(subtree)
 
1601
        tree.commit('a revision')
 
1602
        tree.branch.create_checkout('target')
 
1603
        self.failUnlessExists('target')
 
1604
        self.failUnlessExists('target/subtree')
 
1605
 
 
1606
    def test_file_conflict_handling(self):
 
1607
        """Ensure that when building trees, conflict handling is done"""
 
1608
        source = self.make_branch_and_tree('source')
 
1609
        target = self.make_branch_and_tree('target')
 
1610
        self.build_tree(['source/file', 'target/file'])
 
1611
        source.add('file', 'new-file')
 
1612
        source.commit('added file')
 
1613
        build_tree(source.basis_tree(), target)
 
1614
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1615
                          'file.moved', 'file', None, 'new-file')],
 
1616
                         target.conflicts())
 
1617
        target2 = self.make_branch_and_tree('target2')
 
1618
        target_file = file('target2/file', 'wb')
 
1619
        try:
 
1620
            source_file = file('source/file', 'rb')
 
1621
            try:
 
1622
                target_file.write(source_file.read())
 
1623
            finally:
 
1624
                source_file.close()
 
1625
        finally:
 
1626
            target_file.close()
 
1627
        build_tree(source.basis_tree(), target2)
 
1628
        self.assertEqual([], target2.conflicts())
 
1629
 
 
1630
    def test_symlink_conflict_handling(self):
 
1631
        """Ensure that when building trees, conflict handling is done"""
 
1632
        self.requireFeature(SymlinkFeature)
 
1633
        source = self.make_branch_and_tree('source')
 
1634
        os.symlink('foo', 'source/symlink')
 
1635
        source.add('symlink', 'new-symlink')
 
1636
        source.commit('added file')
 
1637
        target = self.make_branch_and_tree('target')
 
1638
        os.symlink('bar', 'target/symlink')
 
1639
        build_tree(source.basis_tree(), target)
 
1640
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1641
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1642
            target.conflicts())
 
1643
        target = self.make_branch_and_tree('target2')
 
1644
        os.symlink('foo', 'target2/symlink')
 
1645
        build_tree(source.basis_tree(), target)
 
1646
        self.assertEqual([], target.conflicts())
 
1647
 
 
1648
    def test_directory_conflict_handling(self):
 
1649
        """Ensure that when building trees, conflict handling is done"""
 
1650
        source = self.make_branch_and_tree('source')
 
1651
        target = self.make_branch_and_tree('target')
 
1652
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1653
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1654
        source.commit('added file')
 
1655
        build_tree(source.basis_tree(), target)
 
1656
        self.assertEqual([], target.conflicts())
 
1657
        self.failUnlessExists('target/dir1/file')
 
1658
 
 
1659
        # Ensure contents are merged
 
1660
        target = self.make_branch_and_tree('target2')
 
1661
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1662
        build_tree(source.basis_tree(), target)
 
1663
        self.assertEqual([], target.conflicts())
 
1664
        self.failUnlessExists('target2/dir1/file2')
 
1665
        self.failUnlessExists('target2/dir1/file')
 
1666
 
 
1667
        # Ensure new contents are suppressed for existing branches
 
1668
        target = self.make_branch_and_tree('target3')
 
1669
        self.make_branch('target3/dir1')
 
1670
        self.build_tree(['target3/dir1/file2'])
 
1671
        build_tree(source.basis_tree(), target)
 
1672
        self.failIfExists('target3/dir1/file')
 
1673
        self.failUnlessExists('target3/dir1/file2')
 
1674
        self.failUnlessExists('target3/dir1.diverted/file')
 
1675
        self.assertEqual([DuplicateEntry('Diverted to',
 
1676
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1677
            target.conflicts())
 
1678
 
 
1679
        target = self.make_branch_and_tree('target4')
 
1680
        self.build_tree(['target4/dir1/'])
 
1681
        self.make_branch('target4/dir1/file')
 
1682
        build_tree(source.basis_tree(), target)
 
1683
        self.failUnlessExists('target4/dir1/file')
 
1684
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1685
        self.failUnlessExists('target4/dir1/file.diverted')
 
1686
        self.assertEqual([DuplicateEntry('Diverted to',
 
1687
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1688
            target.conflicts())
 
1689
 
 
1690
    def test_mixed_conflict_handling(self):
 
1691
        """Ensure that when building trees, conflict handling is done"""
 
1692
        source = self.make_branch_and_tree('source')
 
1693
        target = self.make_branch_and_tree('target')
 
1694
        self.build_tree(['source/name', 'target/name/'])
 
1695
        source.add('name', 'new-name')
 
1696
        source.commit('added file')
 
1697
        build_tree(source.basis_tree(), target)
 
1698
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1699
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1700
 
 
1701
    def test_raises_in_populated(self):
 
1702
        source = self.make_branch_and_tree('source')
 
1703
        self.build_tree(['source/name'])
 
1704
        source.add('name')
 
1705
        source.commit('added name')
 
1706
        target = self.make_branch_and_tree('target')
 
1707
        self.build_tree(['target/name'])
 
1708
        target.add('name')
 
1709
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
 
1710
            build_tree, source.basis_tree(), target)
 
1711
 
 
1712
    def test_build_tree_rename_count(self):
 
1713
        source = self.make_branch_and_tree('source')
 
1714
        self.build_tree(['source/file1', 'source/dir1/'])
 
1715
        source.add(['file1', 'dir1'])
 
1716
        source.commit('add1')
 
1717
        target1 = self.make_branch_and_tree('target1')
 
1718
        transform_result = build_tree(source.basis_tree(), target1)
 
1719
        self.assertEqual(2, transform_result.rename_count)
 
1720
 
 
1721
        self.build_tree(['source/dir1/file2'])
 
1722
        source.add(['dir1/file2'])
 
1723
        source.commit('add3')
 
1724
        target2 = self.make_branch_and_tree('target2')
 
1725
        transform_result = build_tree(source.basis_tree(), target2)
 
1726
        # children of non-root directories should not be renamed
 
1727
        self.assertEqual(2, transform_result.rename_count)
 
1728
 
 
1729
    def create_ab_tree(self):
 
1730
        """Create a committed test tree with two files"""
 
1731
        source = self.make_branch_and_tree('source')
 
1732
        self.build_tree_contents([('source/file1', 'A')])
 
1733
        self.build_tree_contents([('source/file2', 'B')])
 
1734
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1735
        source.commit('commit files')
 
1736
        source.lock_write()
 
1737
        self.addCleanup(source.unlock)
 
1738
        return source
 
1739
 
 
1740
    def test_build_tree_accelerator_tree(self):
 
1741
        source = self.create_ab_tree()
 
1742
        self.build_tree_contents([('source/file2', 'C')])
 
1743
        calls = []
 
1744
        real_source_get_file = source.get_file
 
1745
        def get_file(file_id, path=None):
 
1746
            calls.append(file_id)
 
1747
            return real_source_get_file(file_id, path)
 
1748
        source.get_file = get_file
 
1749
        target = self.make_branch_and_tree('target')
 
1750
        revision_tree = source.basis_tree()
 
1751
        revision_tree.lock_read()
 
1752
        self.addCleanup(revision_tree.unlock)
 
1753
        build_tree(revision_tree, target, source)
 
1754
        self.assertEqual(['file1-id'], calls)
 
1755
        target.lock_read()
 
1756
        self.addCleanup(target.unlock)
 
1757
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1758
 
 
1759
    def test_build_tree_accelerator_tree_missing_file(self):
 
1760
        source = self.create_ab_tree()
 
1761
        os.unlink('source/file1')
 
1762
        source.remove(['file2'])
 
1763
        target = self.make_branch_and_tree('target')
 
1764
        revision_tree = source.basis_tree()
 
1765
        revision_tree.lock_read()
 
1766
        self.addCleanup(revision_tree.unlock)
 
1767
        build_tree(revision_tree, target, source)
 
1768
        target.lock_read()
 
1769
        self.addCleanup(target.unlock)
 
1770
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1771
 
 
1772
    def test_build_tree_accelerator_wrong_kind(self):
 
1773
        self.requireFeature(SymlinkFeature)
 
1774
        source = self.make_branch_and_tree('source')
 
1775
        self.build_tree_contents([('source/file1', '')])
 
1776
        self.build_tree_contents([('source/file2', '')])
 
1777
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1778
        source.commit('commit files')
 
1779
        os.unlink('source/file2')
 
1780
        self.build_tree_contents([('source/file2/', 'C')])
 
1781
        os.unlink('source/file1')
 
1782
        os.symlink('file2', 'source/file1')
 
1783
        calls = []
 
1784
        real_source_get_file = source.get_file
 
1785
        def get_file(file_id, path=None):
 
1786
            calls.append(file_id)
 
1787
            return real_source_get_file(file_id, path)
 
1788
        source.get_file = get_file
 
1789
        target = self.make_branch_and_tree('target')
 
1790
        revision_tree = source.basis_tree()
 
1791
        revision_tree.lock_read()
 
1792
        self.addCleanup(revision_tree.unlock)
 
1793
        build_tree(revision_tree, target, source)
 
1794
        self.assertEqual([], calls)
 
1795
        target.lock_read()
 
1796
        self.addCleanup(target.unlock)
 
1797
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1798
 
 
1799
    def test_build_tree_hardlink(self):
 
1800
        self.requireFeature(HardlinkFeature)
 
1801
        source = self.create_ab_tree()
 
1802
        target = self.make_branch_and_tree('target')
 
1803
        revision_tree = source.basis_tree()
 
1804
        revision_tree.lock_read()
 
1805
        self.addCleanup(revision_tree.unlock)
 
1806
        build_tree(revision_tree, target, source, hardlink=True)
 
1807
        target.lock_read()
 
1808
        self.addCleanup(target.unlock)
 
1809
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1810
        source_stat = os.stat('source/file1')
 
1811
        target_stat = os.stat('target/file1')
 
1812
        self.assertEqual(source_stat, target_stat)
 
1813
 
 
1814
        # Explicitly disallowing hardlinks should prevent them.
 
1815
        target2 = self.make_branch_and_tree('target2')
 
1816
        build_tree(revision_tree, target2, source, hardlink=False)
 
1817
        target2.lock_read()
 
1818
        self.addCleanup(target2.unlock)
 
1819
        self.assertEqual([], list(target2.iter_changes(revision_tree)))
 
1820
        source_stat = os.stat('source/file1')
 
1821
        target2_stat = os.stat('target2/file1')
 
1822
        self.assertNotEqual(source_stat, target2_stat)
 
1823
 
 
1824
    def test_build_tree_accelerator_tree_moved(self):
 
1825
        source = self.make_branch_and_tree('source')
 
1826
        self.build_tree_contents([('source/file1', 'A')])
 
1827
        source.add(['file1'], ['file1-id'])
 
1828
        source.commit('commit files')
 
1829
        source.rename_one('file1', 'file2')
 
1830
        source.lock_read()
 
1831
        self.addCleanup(source.unlock)
 
1832
        target = self.make_branch_and_tree('target')
 
1833
        revision_tree = source.basis_tree()
 
1834
        revision_tree.lock_read()
 
1835
        self.addCleanup(revision_tree.unlock)
 
1836
        build_tree(revision_tree, target, source)
 
1837
        target.lock_read()
 
1838
        self.addCleanup(target.unlock)
 
1839
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1840
 
 
1841
    def test_build_tree_hardlinks_preserve_execute(self):
 
1842
        self.requireFeature(HardlinkFeature)
 
1843
        source = self.create_ab_tree()
 
1844
        tt = TreeTransform(source)
 
1845
        trans_id = tt.trans_id_tree_file_id('file1-id')
 
1846
        tt.set_executability(True, trans_id)
 
1847
        tt.apply()
 
1848
        self.assertTrue(source.is_executable('file1-id'))
 
1849
        target = self.make_branch_and_tree('target')
 
1850
        revision_tree = source.basis_tree()
 
1851
        revision_tree.lock_read()
 
1852
        self.addCleanup(revision_tree.unlock)
 
1853
        build_tree(revision_tree, target, source, hardlink=True)
 
1854
        target.lock_read()
 
1855
        self.addCleanup(target.unlock)
 
1856
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1857
        self.assertTrue(source.is_executable('file1-id'))
 
1858
 
 
1859
    def test_case_insensitive_build_tree_inventory(self):
 
1860
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1861
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1862
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
 
1863
        source = self.make_branch_and_tree('source')
 
1864
        self.build_tree(['source/file', 'source/FILE'])
 
1865
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
 
1866
        source.commit('added files')
 
1867
        # Don't try this at home, kids!
 
1868
        # Force the tree to report that it is case insensitive
 
1869
        target = self.make_branch_and_tree('target')
 
1870
        target.case_sensitive = False
 
1871
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
1872
        self.assertEqual('file.moved', target.id2path('lower-id'))
 
1873
        self.assertEqual('FILE', target.id2path('upper-id'))
 
1874
 
 
1875
 
733
1876
class MockTransform(object):
734
1877
 
735
1878
    def has_named_child(self, by_parent, parent_id, name):
741
1884
                return True
742
1885
        return False
743
1886
 
 
1887
 
744
1888
class MockEntry(object):
745
1889
    def __init__(self):
746
1890
        object.__init__(self)
747
1891
        self.name = "name"
748
1892
 
 
1893
 
749
1894
class TestGetBackupName(TestCase):
750
1895
    def test_get_backup_name(self):
751
1896
        tt = MockTransform()
759
1904
        self.assertEqual(name, 'name.~1~')
760
1905
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
761
1906
        self.assertEqual(name, 'name.~4~')
 
1907
 
 
1908
 
 
1909
class TestFileMover(tests.TestCaseWithTransport):
 
1910
 
 
1911
    def test_file_mover(self):
 
1912
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1913
        mover = _FileMover()
 
1914
        mover.rename('a', 'q')
 
1915
        self.failUnlessExists('q')
 
1916
        self.failIfExists('a')
 
1917
        self.failUnlessExists('q/b')
 
1918
        self.failUnlessExists('c')
 
1919
        self.failUnlessExists('c/d')
 
1920
 
 
1921
    def test_pre_delete_rollback(self):
 
1922
        self.build_tree(['a/'])
 
1923
        mover = _FileMover()
 
1924
        mover.pre_delete('a', 'q')
 
1925
        self.failUnlessExists('q')
 
1926
        self.failIfExists('a')
 
1927
        mover.rollback()
 
1928
        self.failIfExists('q')
 
1929
        self.failUnlessExists('a')
 
1930
 
 
1931
    def test_apply_deletions(self):
 
1932
        self.build_tree(['a/', 'b/'])
 
1933
        mover = _FileMover()
 
1934
        mover.pre_delete('a', 'q')
 
1935
        mover.pre_delete('b', 'r')
 
1936
        self.failUnlessExists('q')
 
1937
        self.failUnlessExists('r')
 
1938
        self.failIfExists('a')
 
1939
        self.failIfExists('b')
 
1940
        mover.apply_deletions()
 
1941
        self.failIfExists('q')
 
1942
        self.failIfExists('r')
 
1943
        self.failIfExists('a')
 
1944
        self.failIfExists('b')
 
1945
 
 
1946
    def test_file_mover_rollback(self):
 
1947
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1948
        mover = _FileMover()
 
1949
        mover.rename('c/d', 'c/f')
 
1950
        mover.rename('c/e', 'c/d')
 
1951
        try:
 
1952
            mover.rename('a', 'c')
 
1953
        except errors.FileExists, e:
 
1954
            mover.rollback()
 
1955
        self.failUnlessExists('a')
 
1956
        self.failUnlessExists('c/d')
 
1957
 
 
1958
 
 
1959
class Bogus(Exception):
 
1960
    pass
 
1961
 
 
1962
 
 
1963
class TestTransformRollback(tests.TestCaseWithTransport):
 
1964
 
 
1965
    class ExceptionFileMover(_FileMover):
 
1966
 
 
1967
        def __init__(self, bad_source=None, bad_target=None):
 
1968
            _FileMover.__init__(self)
 
1969
            self.bad_source = bad_source
 
1970
            self.bad_target = bad_target
 
1971
 
 
1972
        def rename(self, source, target):
 
1973
            if (self.bad_source is not None and
 
1974
                source.endswith(self.bad_source)):
 
1975
                raise Bogus
 
1976
            elif (self.bad_target is not None and
 
1977
                target.endswith(self.bad_target)):
 
1978
                raise Bogus
 
1979
            else:
 
1980
                _FileMover.rename(self, source, target)
 
1981
 
 
1982
    def test_rollback_rename(self):
 
1983
        tree = self.make_branch_and_tree('.')
 
1984
        self.build_tree(['a/', 'a/b'])
 
1985
        tt = TreeTransform(tree)
 
1986
        self.addCleanup(tt.finalize)
 
1987
        a_id = tt.trans_id_tree_path('a')
 
1988
        tt.adjust_path('c', tt.root, a_id)
 
1989
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1990
        self.assertRaises(Bogus, tt.apply,
 
1991
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1992
        self.failUnlessExists('a')
 
1993
        self.failUnlessExists('a/b')
 
1994
        tt.apply()
 
1995
        self.failUnlessExists('c')
 
1996
        self.failUnlessExists('c/d')
 
1997
 
 
1998
    def test_rollback_rename_into_place(self):
 
1999
        tree = self.make_branch_and_tree('.')
 
2000
        self.build_tree(['a/', 'a/b'])
 
2001
        tt = TreeTransform(tree)
 
2002
        self.addCleanup(tt.finalize)
 
2003
        a_id = tt.trans_id_tree_path('a')
 
2004
        tt.adjust_path('c', tt.root, a_id)
 
2005
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
2006
        self.assertRaises(Bogus, tt.apply,
 
2007
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
2008
        self.failUnlessExists('a')
 
2009
        self.failUnlessExists('a/b')
 
2010
        tt.apply()
 
2011
        self.failUnlessExists('c')
 
2012
        self.failUnlessExists('c/d')
 
2013
 
 
2014
    def test_rollback_deletion(self):
 
2015
        tree = self.make_branch_and_tree('.')
 
2016
        self.build_tree(['a/', 'a/b'])
 
2017
        tt = TreeTransform(tree)
 
2018
        self.addCleanup(tt.finalize)
 
2019
        a_id = tt.trans_id_tree_path('a')
 
2020
        tt.delete_contents(a_id)
 
2021
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
2022
        self.assertRaises(Bogus, tt.apply,
 
2023
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
2024
        self.failUnlessExists('a')
 
2025
        self.failUnlessExists('a/b')
 
2026
 
 
2027
    def test_resolve_no_parent(self):
 
2028
        wt = self.make_branch_and_tree('.')
 
2029
        tt = TreeTransform(wt)
 
2030
        self.addCleanup(tt.finalize)
 
2031
        parent = tt.trans_id_file_id('parent-id')
 
2032
        tt.new_file('file', parent, 'Contents')
 
2033
        resolve_conflicts(tt)
 
2034
 
 
2035
 
 
2036
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2037
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2038
                  (False, False))
 
2039
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2040
              ('', ''), ('directory', 'directory'), (False, None))
 
2041
 
 
2042
 
 
2043
class TestTransformPreview(tests.TestCaseWithTransport):
 
2044
 
 
2045
    def create_tree(self):
 
2046
        tree = self.make_branch_and_tree('.')
 
2047
        self.build_tree_contents([('a', 'content 1')])
 
2048
        tree.add('a', 'a-id')
 
2049
        tree.commit('rev1', rev_id='rev1')
 
2050
        return tree.branch.repository.revision_tree('rev1')
 
2051
 
 
2052
    def get_empty_preview(self):
 
2053
        repository = self.make_repository('repo')
 
2054
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2055
        preview = TransformPreview(tree)
 
2056
        self.addCleanup(preview.finalize)
 
2057
        return preview
 
2058
 
 
2059
    def test_transform_preview(self):
 
2060
        revision_tree = self.create_tree()
 
2061
        preview = TransformPreview(revision_tree)
 
2062
        self.addCleanup(preview.finalize)
 
2063
 
 
2064
    def test_transform_preview_tree(self):
 
2065
        revision_tree = self.create_tree()
 
2066
        preview = TransformPreview(revision_tree)
 
2067
        self.addCleanup(preview.finalize)
 
2068
        preview.get_preview_tree()
 
2069
 
 
2070
    def test_transform_new_file(self):
 
2071
        revision_tree = self.create_tree()
 
2072
        preview = TransformPreview(revision_tree)
 
2073
        self.addCleanup(preview.finalize)
 
2074
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2075
        preview_tree = preview.get_preview_tree()
 
2076
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2077
        self.assertEqual(
 
2078
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2079
 
 
2080
    def test_diff_preview_tree(self):
 
2081
        revision_tree = self.create_tree()
 
2082
        preview = TransformPreview(revision_tree)
 
2083
        self.addCleanup(preview.finalize)
 
2084
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2085
        preview_tree = preview.get_preview_tree()
 
2086
        out = StringIO()
 
2087
        show_diff_trees(revision_tree, preview_tree, out)
 
2088
        lines = out.getvalue().splitlines()
 
2089
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2090
        # 3 lines of diff administrivia
 
2091
        self.assertEqual(lines[4], "+content B")
 
2092
 
 
2093
    def test_transform_conflicts(self):
 
2094
        revision_tree = self.create_tree()
 
2095
        preview = TransformPreview(revision_tree)
 
2096
        self.addCleanup(preview.finalize)
 
2097
        preview.new_file('a', preview.root, 'content 2')
 
2098
        resolve_conflicts(preview)
 
2099
        trans_id = preview.trans_id_file_id('a-id')
 
2100
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2101
 
 
2102
    def get_tree_and_preview_tree(self):
 
2103
        revision_tree = self.create_tree()
 
2104
        preview = TransformPreview(revision_tree)
 
2105
        self.addCleanup(preview.finalize)
 
2106
        a_trans_id = preview.trans_id_file_id('a-id')
 
2107
        preview.delete_contents(a_trans_id)
 
2108
        preview.create_file('b content', a_trans_id)
 
2109
        preview_tree = preview.get_preview_tree()
 
2110
        return revision_tree, preview_tree
 
2111
 
 
2112
    def test_iter_changes(self):
 
2113
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2114
        root = revision_tree.inventory.root.file_id
 
2115
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2116
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2117
                          (False, False))],
 
2118
                          list(preview_tree.iter_changes(revision_tree)))
 
2119
 
 
2120
    def test_include_unchanged_succeeds(self):
 
2121
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2122
        changes = preview_tree.iter_changes(revision_tree,
 
2123
                                            include_unchanged=True)
 
2124
        root = revision_tree.inventory.root.file_id
 
2125
 
 
2126
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2127
 
 
2128
    def test_specific_files(self):
 
2129
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2130
        changes = preview_tree.iter_changes(revision_tree,
 
2131
                                            specific_files=[''])
 
2132
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2133
 
 
2134
    def test_want_unversioned(self):
 
2135
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2136
        changes = preview_tree.iter_changes(revision_tree,
 
2137
                                            want_unversioned=True)
 
2138
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2139
 
 
2140
    def test_ignore_extra_trees_no_specific_files(self):
 
2141
        # extra_trees is harmless without specific_files, so we'll silently
 
2142
        # accept it, even though we won't use it.
 
2143
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2144
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2145
 
 
2146
    def test_ignore_require_versioned_no_specific_files(self):
 
2147
        # require_versioned is meaningless without specific_files.
 
2148
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2149
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2150
 
 
2151
    def test_ignore_pb(self):
 
2152
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2153
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2154
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2155
 
 
2156
    def test_kind(self):
 
2157
        revision_tree = self.create_tree()
 
2158
        preview = TransformPreview(revision_tree)
 
2159
        self.addCleanup(preview.finalize)
 
2160
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2161
        preview.new_directory('directory', preview.root, 'dir-id')
 
2162
        preview_tree = preview.get_preview_tree()
 
2163
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2164
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2165
 
 
2166
    def test_get_file_mtime(self):
 
2167
        preview = self.get_empty_preview()
 
2168
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2169
                                         'file-id')
 
2170
        limbo_path = preview._limbo_name(file_trans_id)
 
2171
        preview_tree = preview.get_preview_tree()
 
2172
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2173
                         preview_tree.get_file_mtime('file-id'))
 
2174
 
 
2175
    def test_get_file(self):
 
2176
        preview = self.get_empty_preview()
 
2177
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2178
        preview_tree = preview.get_preview_tree()
 
2179
        tree_file = preview_tree.get_file('file-id')
 
2180
        try:
 
2181
            self.assertEqual('contents', tree_file.read())
 
2182
        finally:
 
2183
            tree_file.close()
 
2184
 
 
2185
    def test_get_symlink_target(self):
 
2186
        self.requireFeature(SymlinkFeature)
 
2187
        preview = self.get_empty_preview()
 
2188
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2189
        preview_tree = preview.get_preview_tree()
 
2190
        self.assertEqual('target',
 
2191
                         preview_tree.get_symlink_target('symlink-id'))
 
2192
 
 
2193
    def test_all_file_ids(self):
 
2194
        tree = self.make_branch_and_tree('tree')
 
2195
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2196
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2197
        preview = TransformPreview(tree)
 
2198
        self.addCleanup(preview.finalize)
 
2199
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2200
        c_trans_id = preview.trans_id_file_id('c-id')
 
2201
        preview.unversion_file(c_trans_id)
 
2202
        preview.version_file('c-id', c_trans_id)
 
2203
        preview_tree = preview.get_preview_tree()
 
2204
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2205
                         preview_tree.all_file_ids())
 
2206
 
 
2207
    def test_path2id_deleted_unchanged(self):
 
2208
        tree = self.make_branch_and_tree('tree')
 
2209
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2210
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2211
        preview = TransformPreview(tree)
 
2212
        self.addCleanup(preview.finalize)
 
2213
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2214
        preview_tree = preview.get_preview_tree()
 
2215
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2216
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2217
 
 
2218
    def test_path2id_created(self):
 
2219
        tree = self.make_branch_and_tree('tree')
 
2220
        self.build_tree(['tree/unchanged'])
 
2221
        tree.add(['unchanged'], ['unchanged-id'])
 
2222
        preview = TransformPreview(tree)
 
2223
        self.addCleanup(preview.finalize)
 
2224
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2225
            'contents', 'new-id')
 
2226
        preview_tree = preview.get_preview_tree()
 
2227
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2228
 
 
2229
    def test_path2id_moved(self):
 
2230
        tree = self.make_branch_and_tree('tree')
 
2231
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2232
        tree.add(['old_parent', 'old_parent/child'],
 
2233
                 ['old_parent-id', 'child-id'])
 
2234
        preview = TransformPreview(tree)
 
2235
        self.addCleanup(preview.finalize)
 
2236
        new_parent = preview.new_directory('new_parent', preview.root,
 
2237
                                           'new_parent-id')
 
2238
        preview.adjust_path('child', new_parent,
 
2239
                            preview.trans_id_file_id('child-id'))
 
2240
        preview_tree = preview.get_preview_tree()
 
2241
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2242
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2243
 
 
2244
    def test_path2id_renamed_parent(self):
 
2245
        tree = self.make_branch_and_tree('tree')
 
2246
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2247
        tree.add(['old_name', 'old_name/child'],
 
2248
                 ['parent-id', 'child-id'])
 
2249
        preview = TransformPreview(tree)
 
2250
        self.addCleanup(preview.finalize)
 
2251
        preview.adjust_path('new_name', preview.root,
 
2252
                            preview.trans_id_file_id('parent-id'))
 
2253
        preview_tree = preview.get_preview_tree()
 
2254
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2255
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2256
 
 
2257
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2258
        preview_tree = tt.get_preview_tree()
 
2259
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2260
                              specific_file_ids))
 
2261
        tree = tt._tree
 
2262
        tt.apply()
 
2263
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2264
        self.assertEqual(actual_result, preview_result)
 
2265
 
 
2266
    def test_iter_entries_by_dir_new(self):
 
2267
        tree = self.make_branch_and_tree('tree')
 
2268
        tt = TreeTransform(tree)
 
2269
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2270
        self.assertMatchingIterEntries(tt)
 
2271
 
 
2272
    def test_iter_entries_by_dir_deleted(self):
 
2273
        tree = self.make_branch_and_tree('tree')
 
2274
        self.build_tree(['tree/deleted'])
 
2275
        tree.add('deleted', 'deleted-id')
 
2276
        tt = TreeTransform(tree)
 
2277
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2278
        self.assertMatchingIterEntries(tt)
 
2279
 
 
2280
    def test_iter_entries_by_dir_unversioned(self):
 
2281
        tree = self.make_branch_and_tree('tree')
 
2282
        self.build_tree(['tree/removed'])
 
2283
        tree.add('removed', 'removed-id')
 
2284
        tt = TreeTransform(tree)
 
2285
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2286
        self.assertMatchingIterEntries(tt)
 
2287
 
 
2288
    def test_iter_entries_by_dir_moved(self):
 
2289
        tree = self.make_branch_and_tree('tree')
 
2290
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2291
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2292
        tt = TreeTransform(tree)
 
2293
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2294
                       tt.trans_id_file_id('moved-id'))
 
2295
        self.assertMatchingIterEntries(tt)
 
2296
 
 
2297
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2298
        tree = self.make_branch_and_tree('tree')
 
2299
        tree.set_root_id('tree-root-id')
 
2300
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2301
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2302
        tt = TreeTransform(tree)
 
2303
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2304
 
 
2305
    def test_symlink_content_summary(self):
 
2306
        self.requireFeature(SymlinkFeature)
 
2307
        preview = self.get_empty_preview()
 
2308
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2309
        summary = preview.get_preview_tree().path_content_summary('path')
 
2310
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2311
 
 
2312
    def test_missing_content_summary(self):
 
2313
        preview = self.get_empty_preview()
 
2314
        summary = preview.get_preview_tree().path_content_summary('path')
 
2315
        self.assertEqual(('missing', None, None, None), summary)
 
2316
 
 
2317
    def test_deleted_content_summary(self):
 
2318
        tree = self.make_branch_and_tree('tree')
 
2319
        self.build_tree(['tree/path/'])
 
2320
        tree.add('path')
 
2321
        preview = TransformPreview(tree)
 
2322
        self.addCleanup(preview.finalize)
 
2323
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2324
        summary = preview.get_preview_tree().path_content_summary('path')
 
2325
        self.assertEqual(('missing', None, None, None), summary)
 
2326
 
 
2327
    def test_file_content_summary_executable(self):
 
2328
        if not osutils.supports_executable():
 
2329
            raise TestNotApplicable()
 
2330
        preview = self.get_empty_preview()
 
2331
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2332
        preview.set_executability(True, path_id)
 
2333
        summary = preview.get_preview_tree().path_content_summary('path')
 
2334
        self.assertEqual(4, len(summary))
 
2335
        self.assertEqual('file', summary[0])
 
2336
        # size must be known
 
2337
        self.assertEqual(len('contents'), summary[1])
 
2338
        # executable
 
2339
        self.assertEqual(True, summary[2])
 
2340
        # will not have hash (not cheap to determine)
 
2341
        self.assertIs(None, summary[3])
 
2342
 
 
2343
    def test_change_executability(self):
 
2344
        if not osutils.supports_executable():
 
2345
            raise TestNotApplicable()
 
2346
        tree = self.make_branch_and_tree('tree')
 
2347
        self.build_tree(['tree/path'])
 
2348
        tree.add('path')
 
2349
        preview = TransformPreview(tree)
 
2350
        self.addCleanup(preview.finalize)
 
2351
        path_id = preview.trans_id_tree_path('path')
 
2352
        preview.set_executability(True, path_id)
 
2353
        summary = preview.get_preview_tree().path_content_summary('path')
 
2354
        self.assertEqual(True, summary[2])
 
2355
 
 
2356
    def test_file_content_summary_non_exec(self):
 
2357
        preview = self.get_empty_preview()
 
2358
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
2359
        summary = preview.get_preview_tree().path_content_summary('path')
 
2360
        self.assertEqual(4, len(summary))
 
2361
        self.assertEqual('file', summary[0])
 
2362
        # size must be known
 
2363
        self.assertEqual(len('contents'), summary[1])
 
2364
        # not executable
 
2365
        if osutils.supports_executable():
 
2366
            self.assertEqual(False, summary[2])
 
2367
        else:
 
2368
            self.assertEqual(None, summary[2])
 
2369
        # will not have hash (not cheap to determine)
 
2370
        self.assertIs(None, summary[3])
 
2371
 
 
2372
    def test_dir_content_summary(self):
 
2373
        preview = self.get_empty_preview()
 
2374
        preview.new_directory('path', preview.root, 'path-id')
 
2375
        summary = preview.get_preview_tree().path_content_summary('path')
 
2376
        self.assertEqual(('directory', None, None, None), summary)
 
2377
 
 
2378
    def test_tree_content_summary(self):
 
2379
        preview = self.get_empty_preview()
 
2380
        path = preview.new_directory('path', preview.root, 'path-id')
 
2381
        preview.set_tree_reference('rev-1', path)
 
2382
        summary = preview.get_preview_tree().path_content_summary('path')
 
2383
        self.assertEqual(4, len(summary))
 
2384
        self.assertEqual('tree-reference', summary[0])
 
2385
 
 
2386
    def test_annotate(self):
 
2387
        tree = self.make_branch_and_tree('tree')
 
2388
        self.build_tree_contents([('tree/file', 'a\n')])
 
2389
        tree.add('file', 'file-id')
 
2390
        tree.commit('a', rev_id='one')
 
2391
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2392
        preview = TransformPreview(tree)
 
2393
        self.addCleanup(preview.finalize)
 
2394
        file_trans_id = preview.trans_id_file_id('file-id')
 
2395
        preview.delete_contents(file_trans_id)
 
2396
        preview.create_file('a\nb\nc\n', file_trans_id)
 
2397
        preview_tree = preview.get_preview_tree()
 
2398
        expected = [
 
2399
            ('one', 'a\n'),
 
2400
            ('me:', 'b\n'),
 
2401
            ('me:', 'c\n'),
 
2402
        ]
 
2403
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2404
        self.assertEqual(expected, annotation)
 
2405
 
 
2406
    def test_annotate_missing(self):
 
2407
        preview = self.get_empty_preview()
 
2408
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2409
        preview_tree = preview.get_preview_tree()
 
2410
        expected = [
 
2411
            ('me:', 'a\n'),
 
2412
            ('me:', 'b\n'),
 
2413
            ('me:', 'c\n'),
 
2414
         ]
 
2415
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2416
        self.assertEqual(expected, annotation)
 
2417
 
 
2418
    def test_annotate_rename(self):
 
2419
        tree = self.make_branch_and_tree('tree')
 
2420
        self.build_tree_contents([('tree/file', 'a\n')])
 
2421
        tree.add('file', 'file-id')
 
2422
        tree.commit('a', rev_id='one')
 
2423
        preview = TransformPreview(tree)
 
2424
        self.addCleanup(preview.finalize)
 
2425
        file_trans_id = preview.trans_id_file_id('file-id')
 
2426
        preview.adjust_path('newname', preview.root, file_trans_id)
 
2427
        preview_tree = preview.get_preview_tree()
 
2428
        expected = [
 
2429
            ('one', 'a\n'),
 
2430
        ]
 
2431
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2432
        self.assertEqual(expected, annotation)
 
2433
 
 
2434
    def test_annotate_deleted(self):
 
2435
        tree = self.make_branch_and_tree('tree')
 
2436
        self.build_tree_contents([('tree/file', 'a\n')])
 
2437
        tree.add('file', 'file-id')
 
2438
        tree.commit('a', rev_id='one')
 
2439
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2440
        preview = TransformPreview(tree)
 
2441
        self.addCleanup(preview.finalize)
 
2442
        file_trans_id = preview.trans_id_file_id('file-id')
 
2443
        preview.delete_contents(file_trans_id)
 
2444
        preview_tree = preview.get_preview_tree()
 
2445
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2446
        self.assertIs(None, annotation)
 
2447
 
 
2448
    def test_stored_kind(self):
 
2449
        preview = self.get_empty_preview()
 
2450
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2451
        preview_tree = preview.get_preview_tree()
 
2452
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
2453
 
 
2454
    def test_is_executable(self):
 
2455
        preview = self.get_empty_preview()
 
2456
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2457
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
2458
        preview_tree = preview.get_preview_tree()
 
2459
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2460
 
 
2461
    def test_get_set_parent_ids(self):
 
2462
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2463
        self.assertEqual([], preview_tree.get_parent_ids())
 
2464
        preview_tree.set_parent_ids(['rev-1'])
 
2465
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2466
 
 
2467
    def test_plan_file_merge(self):
 
2468
        work_a = self.make_branch_and_tree('wta')
 
2469
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2470
        work_a.add('file', 'file-id')
 
2471
        base_id = work_a.commit('base version')
 
2472
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2473
        preview = TransformPreview(work_a)
 
2474
        self.addCleanup(preview.finalize)
 
2475
        trans_id = preview.trans_id_file_id('file-id')
 
2476
        preview.delete_contents(trans_id)
 
2477
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2478
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2479
        tree_a = preview.get_preview_tree()
 
2480
        tree_a.set_parent_ids([base_id])
 
2481
        self.assertEqual([
 
2482
            ('killed-a', 'a\n'),
 
2483
            ('killed-b', 'b\n'),
 
2484
            ('unchanged', 'c\n'),
 
2485
            ('unchanged', 'd\n'),
 
2486
            ('new-a', 'e\n'),
 
2487
            ('new-b', 'f\n'),
 
2488
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2489
 
 
2490
    def test_plan_file_merge_revision_tree(self):
 
2491
        work_a = self.make_branch_and_tree('wta')
 
2492
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2493
        work_a.add('file', 'file-id')
 
2494
        base_id = work_a.commit('base version')
 
2495
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2496
        preview = TransformPreview(work_a.basis_tree())
 
2497
        self.addCleanup(preview.finalize)
 
2498
        trans_id = preview.trans_id_file_id('file-id')
 
2499
        preview.delete_contents(trans_id)
 
2500
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2501
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2502
        tree_a = preview.get_preview_tree()
 
2503
        tree_a.set_parent_ids([base_id])
 
2504
        self.assertEqual([
 
2505
            ('killed-a', 'a\n'),
 
2506
            ('killed-b', 'b\n'),
 
2507
            ('unchanged', 'c\n'),
 
2508
            ('unchanged', 'd\n'),
 
2509
            ('new-a', 'e\n'),
 
2510
            ('new-b', 'f\n'),
 
2511
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2512
 
 
2513
    def test_walkdirs(self):
 
2514
        preview = self.get_empty_preview()
 
2515
        preview.version_file('tree-root', preview.root)
 
2516
        preview_tree = preview.get_preview_tree()
 
2517
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2518
                                         'a-id')
 
2519
        expected = [(('', 'tree-root'),
 
2520
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2521
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2522
 
 
2523
    def test_extras(self):
 
2524
        work_tree = self.make_branch_and_tree('tree')
 
2525
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2526
                         'tree/not-removed-file'])
 
2527
        work_tree.add(['removed-file', 'not-removed-file'])
 
2528
        preview = TransformPreview(work_tree)
 
2529
        self.addCleanup(preview.finalize)
 
2530
        preview.new_file('new-file', preview.root, 'contents')
 
2531
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2532
                         'new-versioned-id')
 
2533
        tree = preview.get_preview_tree()
 
2534
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2535
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2536
                         set(tree.extras()))
 
2537
 
 
2538
    def test_merge_into_preview(self):
 
2539
        work_tree = self.make_branch_and_tree('tree')
 
2540
        self.build_tree_contents([('tree/file','b\n')])
 
2541
        work_tree.add('file', 'file-id')
 
2542
        work_tree.commit('first commit')
 
2543
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2544
        self.build_tree_contents([('child/file','b\nc\n')])
 
2545
        child_tree.commit('child commit')
 
2546
        child_tree.lock_write()
 
2547
        self.addCleanup(child_tree.unlock)
 
2548
        work_tree.lock_write()
 
2549
        self.addCleanup(work_tree.unlock)
 
2550
        preview = TransformPreview(work_tree)
 
2551
        self.addCleanup(preview.finalize)
 
2552
        preview_tree = preview.get_preview_tree()
 
2553
        file_trans_id = preview.trans_id_file_id('file-id')
 
2554
        preview.delete_contents(file_trans_id)
 
2555
        preview.create_file('a\nb\n', file_trans_id)
 
2556
        pb = progress.DummyProgress()
 
2557
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2558
                                          child_tree.branch.last_revision(),
 
2559
                                          other_branch=child_tree.branch,
 
2560
                                          tree_branch=work_tree.branch)
 
2561
        merger.merge_type = Merge3Merger
 
2562
        tt = merger.make_merger().make_preview_transform()
 
2563
        self.addCleanup(tt.finalize)
 
2564
        final_tree = tt.get_preview_tree()
 
2565
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2566
 
 
2567
    def test_merge_preview_into_workingtree(self):
 
2568
        tree = self.make_branch_and_tree('tree')
 
2569
        tt = TransformPreview(tree)
 
2570
        self.addCleanup(tt.finalize)
 
2571
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2572
        tree2 = self.make_branch_and_tree('tree2')
 
2573
        pb = progress.DummyProgress()
 
2574
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2575
                                         pb, tree.basis_tree())
 
2576
        merger.merge_type = Merge3Merger
 
2577
        merger.do_merge()
 
2578
 
 
2579
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2580
        tree = self.make_branch_and_tree('tree')
 
2581
        self.build_tree_contents([('tree/foo', 'bar')])
 
2582
        tree.add('foo', 'foo-id')
 
2583
        tree.commit('foo')
 
2584
        tt = TransformPreview(tree)
 
2585
        self.addCleanup(tt.finalize)
 
2586
        trans_id = tt.trans_id_file_id('foo-id')
 
2587
        tt.delete_contents(trans_id)
 
2588
        tt.create_file('baz', trans_id)
 
2589
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2590
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2591
        pb = progress.DummyProgress()
 
2592
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2593
                                         pb, tree.basis_tree())
 
2594
        merger.merge_type = Merge3Merger
 
2595
        merger.do_merge()
 
2596
 
 
2597
    def test_is_executable(self):
 
2598
        tree = self.make_branch_and_tree('tree')
 
2599
        preview = TransformPreview(tree)
 
2600
        self.addCleanup(preview.finalize)
 
2601
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2602
        preview_tree = preview.get_preview_tree()
 
2603
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2604
                                                           'tree/foo'))
 
2605
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2606
 
 
2607
    def test_commit_preview_tree(self):
 
2608
        tree = self.make_branch_and_tree('tree')
 
2609
        rev_id = tree.commit('rev1')
 
2610
        tree.branch.lock_write()
 
2611
        self.addCleanup(tree.branch.unlock)
 
2612
        tt = TransformPreview(tree)
 
2613
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2614
        self.addCleanup(tt.finalize)
 
2615
        preview = tt.get_preview_tree()
 
2616
        preview.set_parent_ids([rev_id])
 
2617
        builder = tree.branch.get_commit_builder([rev_id])
 
2618
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2619
        builder.finish_inventory()
 
2620
        rev2_id = builder.commit('rev2')
 
2621
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2622
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2623
 
 
2624
 
 
2625
class FakeSerializer(object):
 
2626
    """Serializer implementation that simply returns the input.
 
2627
 
 
2628
    The input is returned in the order used by pack.ContainerPushParser.
 
2629
    """
 
2630
    @staticmethod
 
2631
    def bytes_record(bytes, names):
 
2632
        return names, bytes
 
2633
 
 
2634
 
 
2635
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2636
 
 
2637
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2638
 
 
2639
    def get_preview(self, tree=None):
 
2640
        if tree is None:
 
2641
            tree = self.make_branch_and_tree('tree')
 
2642
        tt = TransformPreview(tree)
 
2643
        self.addCleanup(tt.finalize)
 
2644
        return tt
 
2645
 
 
2646
    def assertSerializesTo(self, expected, tt):
 
2647
        records = list(tt.serialize(FakeSerializer()))
 
2648
        self.assertEqual(expected, records)
 
2649
 
 
2650
    @staticmethod
 
2651
    def default_attribs():
 
2652
        return {
 
2653
            '_id_number': 1,
 
2654
            '_new_name': {},
 
2655
            '_new_parent': {},
 
2656
            '_new_executability': {},
 
2657
            '_new_id': {},
 
2658
            '_tree_path_ids': {'': 'new-0'},
 
2659
            '_removed_id': [],
 
2660
            '_removed_contents': [],
 
2661
            '_non_present_ids': {},
 
2662
            }
 
2663
 
 
2664
    def make_records(self, attribs, contents):
 
2665
        records = [
 
2666
            (((('attribs'),),), bencode.bencode(attribs))]
 
2667
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2668
        return records
 
2669
 
 
2670
    def creation_records(self):
 
2671
        attribs = self.default_attribs()
 
2672
        attribs['_id_number'] = 3
 
2673
        attribs['_new_name'] = {
 
2674
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2675
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2676
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2677
        attribs['_new_executability'] = {'new-1': 1}
 
2678
        contents = [
 
2679
            ('new-1', 'file', 'i 1\nbar\n'),
 
2680
            ('new-2', 'directory', ''),
 
2681
            ]
 
2682
        return self.make_records(attribs, contents)
 
2683
 
 
2684
    def test_serialize_creation(self):
 
2685
        tt = self.get_preview()
 
2686
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2687
        tt.new_directory('qux', tt.root, 'quxx')
 
2688
        self.assertSerializesTo(self.creation_records(), tt)
 
2689
 
 
2690
    def test_deserialize_creation(self):
 
2691
        tt = self.get_preview()
 
2692
        tt.deserialize(iter(self.creation_records()))
 
2693
        self.assertEqual(3, tt._id_number)
 
2694
        self.assertEqual({'new-1': u'foo\u1234',
 
2695
                          'new-2': 'qux'}, tt._new_name)
 
2696
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2697
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2698
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2699
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2700
        self.assertEqual({'new-1': 'file',
 
2701
                          'new-2': 'directory'}, tt._new_contents)
 
2702
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2703
        try:
 
2704
            foo_content = foo_limbo.read()
 
2705
        finally:
 
2706
            foo_limbo.close()
 
2707
        self.assertEqual('bar', foo_content)
 
2708
 
 
2709
    def symlink_creation_records(self):
 
2710
        attribs = self.default_attribs()
 
2711
        attribs['_id_number'] = 2
 
2712
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2713
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2714
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2715
        return self.make_records(attribs, contents)
 
2716
 
 
2717
    def test_serialize_symlink_creation(self):
 
2718
        self.requireFeature(tests.SymlinkFeature)
 
2719
        tt = self.get_preview()
 
2720
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2721
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2722
 
 
2723
    def test_deserialize_symlink_creation(self):
 
2724
        self.requireFeature(tests.SymlinkFeature)
 
2725
        tt = self.get_preview()
 
2726
        tt.deserialize(iter(self.symlink_creation_records()))
 
2727
        abspath = tt._limbo_name('new-1')
 
2728
        foo_content = osutils.readlink(abspath)
 
2729
        self.assertEqual(u'bar\u1234', foo_content)
 
2730
 
 
2731
    def make_destruction_preview(self):
 
2732
        tree = self.make_branch_and_tree('.')
 
2733
        self.build_tree([u'foo\u1234', 'bar'])
 
2734
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2735
        return self.get_preview(tree)
 
2736
 
 
2737
    def destruction_records(self):
 
2738
        attribs = self.default_attribs()
 
2739
        attribs['_id_number'] = 3
 
2740
        attribs['_removed_id'] = ['new-1']
 
2741
        attribs['_removed_contents'] = ['new-2']
 
2742
        attribs['_tree_path_ids'] = {
 
2743
            '': 'new-0',
 
2744
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2745
            'bar': 'new-2',
 
2746
            }
 
2747
        return self.make_records(attribs, [])
 
2748
 
 
2749
    def test_serialize_destruction(self):
 
2750
        tt = self.make_destruction_preview()
 
2751
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2752
        tt.unversion_file(foo_trans_id)
 
2753
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2754
        tt.delete_contents(bar_trans_id)
 
2755
        self.assertSerializesTo(self.destruction_records(), tt)
 
2756
 
 
2757
    def test_deserialize_destruction(self):
 
2758
        tt = self.make_destruction_preview()
 
2759
        tt.deserialize(iter(self.destruction_records()))
 
2760
        self.assertEqual({u'foo\u1234': 'new-1',
 
2761
                          'bar': 'new-2',
 
2762
                          '': tt.root}, tt._tree_path_ids)
 
2763
        self.assertEqual({'new-1': u'foo\u1234',
 
2764
                          'new-2': 'bar',
 
2765
                          tt.root: ''}, tt._tree_id_paths)
 
2766
        self.assertEqual(set(['new-1']), tt._removed_id)
 
2767
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
2768
 
 
2769
    def missing_records(self):
 
2770
        attribs = self.default_attribs()
 
2771
        attribs['_id_number'] = 2
 
2772
        attribs['_non_present_ids'] = {
 
2773
            'boo': 'new-1',}
 
2774
        return self.make_records(attribs, [])
 
2775
 
 
2776
    def test_serialize_missing(self):
 
2777
        tt = self.get_preview()
 
2778
        boo_trans_id = tt.trans_id_file_id('boo')
 
2779
        self.assertSerializesTo(self.missing_records(), tt)
 
2780
 
 
2781
    def test_deserialize_missing(self):
 
2782
        tt = self.get_preview()
 
2783
        tt.deserialize(iter(self.missing_records()))
 
2784
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
2785
 
 
2786
    def make_modification_preview(self):
 
2787
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2788
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2789
        tree = self.make_branch_and_tree('tree')
 
2790
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2791
        tree.add('file', 'file-id')
 
2792
        return self.get_preview(tree), LINES_TWO
 
2793
 
 
2794
    def modification_records(self):
 
2795
        attribs = self.default_attribs()
 
2796
        attribs['_id_number'] = 2
 
2797
        attribs['_tree_path_ids'] = {
 
2798
            'file': 'new-1',
 
2799
            '': 'new-0',}
 
2800
        attribs['_removed_contents'] = ['new-1']
 
2801
        contents = [('new-1', 'file',
 
2802
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
2803
        return self.make_records(attribs, contents)
 
2804
 
 
2805
    def test_serialize_modification(self):
 
2806
        tt, LINES = self.make_modification_preview()
 
2807
        trans_id = tt.trans_id_file_id('file-id')
 
2808
        tt.delete_contents(trans_id)
 
2809
        tt.create_file(LINES, trans_id)
 
2810
        self.assertSerializesTo(self.modification_records(), tt)
 
2811
 
 
2812
    def test_deserialize_modification(self):
 
2813
        tt, LINES = self.make_modification_preview()
 
2814
        tt.deserialize(iter(self.modification_records()))
 
2815
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2816
 
 
2817
    def make_kind_change_preview(self):
 
2818
        LINES = 'a\nb\nc\nd\n'
 
2819
        tree = self.make_branch_and_tree('tree')
 
2820
        self.build_tree(['tree/foo/'])
 
2821
        tree.add('foo', 'foo-id')
 
2822
        return self.get_preview(tree), LINES
 
2823
 
 
2824
    def kind_change_records(self):
 
2825
        attribs = self.default_attribs()
 
2826
        attribs['_id_number'] = 2
 
2827
        attribs['_tree_path_ids'] = {
 
2828
            'foo': 'new-1',
 
2829
            '': 'new-0',}
 
2830
        attribs['_removed_contents'] = ['new-1']
 
2831
        contents = [('new-1', 'file',
 
2832
                     'i 4\na\nb\nc\nd\n\n')]
 
2833
        return self.make_records(attribs, contents)
 
2834
 
 
2835
    def test_serialize_kind_change(self):
 
2836
        tt, LINES = self.make_kind_change_preview()
 
2837
        trans_id = tt.trans_id_file_id('foo-id')
 
2838
        tt.delete_contents(trans_id)
 
2839
        tt.create_file(LINES, trans_id)
 
2840
        self.assertSerializesTo(self.kind_change_records(), tt)
 
2841
 
 
2842
    def test_deserialize_kind_change(self):
 
2843
        tt, LINES = self.make_kind_change_preview()
 
2844
        tt.deserialize(iter(self.kind_change_records()))
 
2845
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2846
 
 
2847
    def make_add_contents_preview(self):
 
2848
        LINES = 'a\nb\nc\nd\n'
 
2849
        tree = self.make_branch_and_tree('tree')
 
2850
        self.build_tree(['tree/foo'])
 
2851
        tree.add('foo')
 
2852
        os.unlink('tree/foo')
 
2853
        return self.get_preview(tree), LINES
 
2854
 
 
2855
    def add_contents_records(self):
 
2856
        attribs = self.default_attribs()
 
2857
        attribs['_id_number'] = 2
 
2858
        attribs['_tree_path_ids'] = {
 
2859
            'foo': 'new-1',
 
2860
            '': 'new-0',}
 
2861
        contents = [('new-1', 'file',
 
2862
                     'i 4\na\nb\nc\nd\n\n')]
 
2863
        return self.make_records(attribs, contents)
 
2864
 
 
2865
    def test_serialize_add_contents(self):
 
2866
        tt, LINES = self.make_add_contents_preview()
 
2867
        trans_id = tt.trans_id_tree_path('foo')
 
2868
        tt.create_file(LINES, trans_id)
 
2869
        self.assertSerializesTo(self.add_contents_records(), tt)
 
2870
 
 
2871
    def test_deserialize_add_contents(self):
 
2872
        tt, LINES = self.make_add_contents_preview()
 
2873
        tt.deserialize(iter(self.add_contents_records()))
 
2874
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2875
 
 
2876
    def test_get_parents_lines(self):
 
2877
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2878
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2879
        tree = self.make_branch_and_tree('tree')
 
2880
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2881
        tree.add('file', 'file-id')
 
2882
        tt = self.get_preview(tree)
 
2883
        trans_id = tt.trans_id_tree_path('file')
 
2884
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
2885
            tt._get_parents_lines(trans_id))
 
2886
 
 
2887
    def test_get_parents_texts(self):
 
2888
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2889
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2890
        tree = self.make_branch_and_tree('tree')
 
2891
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2892
        tree.add('file', 'file-id')
 
2893
        tt = self.get_preview(tree)
 
2894
        trans_id = tt.trans_id_tree_path('file')
 
2895
        self.assertEqual((LINES_ONE,),
 
2896
            tt._get_parents_texts(trans_id))