~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Ian Clatworthy
  • Date: 2009-09-09 11:43:10 UTC
  • mto: (4634.37.2 prepare-2.0)
  • mto: This revision was merged to the branch mainline in revision 4689.
  • Revision ID: ian.clatworthy@canonical.com-20090909114310-glw7tv76i5gnx9pt
put rules back in Makefile supporting plain-style docs

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
18
import stat
 
19
from StringIO import StringIO
19
20
import sys
20
21
 
21
22
from bzrlib import (
 
23
    bencode,
22
24
    errors,
23
25
    generate_ids,
 
26
    osutils,
 
27
    progress,
 
28
    revision as _mod_revision,
24
29
    symbol_versioning,
25
30
    tests,
26
31
    urlutils,
27
32
    )
28
33
from bzrlib.bzrdir import BzrDir
29
34
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
 
                              UnversionedParent, ParentLoop, DeletingParent,)
 
35
                              UnversionedParent, ParentLoop, DeletingParent,
 
36
                              NonDirectoryParent)
 
37
from bzrlib.diff import show_diff_trees
31
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
 
                           ReusingTransform, CantMoveRoot, 
 
39
                           ReusingTransform, CantMoveRoot,
33
40
                           PathsNotVersionedError, ExistingLimbo,
34
41
                           ExistingPendingDeletion, ImmortalLimbo,
35
42
                           ImmortalPendingDeletion, LockError)
36
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
37
 
from bzrlib.merge import Merge3Merger
38
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
39
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
40
 
                              resolve_conflicts, cook_conflicts, 
41
 
                              find_interesting, build_tree, get_backup_name,
42
 
                              change_entry, _FileMover)
 
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)
43
57
 
44
58
 
45
59
class TestTreeTransform(tests.TestCaseWithTransport):
51
65
 
52
66
    def get_transform(self):
53
67
        transform = TreeTransform(self.wt)
54
 
        #self.addCleanup(transform.finalize)
 
68
        self.addCleanup(transform.finalize)
55
69
        return transform, transform.root
56
70
 
57
71
    def test_existing_limbo(self):
72
86
    def test_existing_pending_deletion(self):
73
87
        transform, root = self.get_transform()
74
88
        deletion_path = self._limbodir = urlutils.local_path_from_url(
75
 
            transform._tree._control_files.controlfilename('pending-deletion'))
 
89
            transform._tree._transport.abspath('pending-deletion'))
76
90
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
77
91
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
78
92
        self.assertRaises(LockError, self.wt.unlock)
79
93
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
80
94
 
81
95
    def test_build(self):
82
 
        transform, root = self.get_transform() 
 
96
        transform, root = self.get_transform()
 
97
        self.wt.lock_tree_write()
 
98
        self.addCleanup(self.wt.unlock)
83
99
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
84
100
        imaginary_id = transform.trans_id_tree_path('imaginary')
85
101
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
113
129
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
114
130
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
115
131
        self.assertEqual(len(modified_paths), 3)
116
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
132
        tree_mod_paths = [self.wt.id2abspath(f) for f in
117
133
                          ('ozzie', 'my_pretties', 'my_pretties2')]
118
134
        self.assertSubset(tree_mod_paths, modified_paths)
119
135
        # is it safe to finalize repeatedly?
120
136
        transform.finalize()
121
137
        transform.finalize()
122
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
 
123
154
    def test_convenience(self):
124
155
        transform, root = self.get_transform()
125
 
        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',
126
159
                                      'my_pretties', True)
127
160
        oz = transform.new_directory('oz', root, 'oz-id')
128
161
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
129
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
162
        toto = transform.new_file('toto', dorothy, 'toto-contents',
130
163
                                  'toto-id', False)
131
164
 
132
165
        self.assertEqual(len(transform.find_conflicts()), 0)
156
189
 
157
190
    def test_conflicts(self):
158
191
        transform, root = self.get_transform()
159
 
        trans_id = transform.new_file('name', root, 'contents', 
 
192
        trans_id = transform.new_file('name', root, 'contents',
160
193
                                      'my_pretties')
161
194
        self.assertEqual(len(transform.find_conflicts()), 0)
162
195
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
163
 
        self.assertEqual(transform.find_conflicts(), 
 
196
        self.assertEqual(transform.find_conflicts(),
164
197
                         [('duplicate', trans_id, trans_id2, 'name')])
165
198
        self.assertRaises(MalformedTransform, transform.apply)
166
199
        transform.adjust_path('name', trans_id, trans_id2)
167
 
        self.assertEqual(transform.find_conflicts(), 
 
200
        self.assertEqual(transform.find_conflicts(),
168
201
                         [('non-directory parent', trans_id)])
169
202
        tinman_id = transform.trans_id_tree_path('tinman')
170
203
        transform.adjust_path('name', tinman_id, trans_id2)
171
 
        self.assertEqual(transform.find_conflicts(), 
172
 
                         [('unversioned parent', tinman_id), 
 
204
        self.assertEqual(transform.find_conflicts(),
 
205
                         [('unversioned parent', tinman_id),
173
206
                          ('missing parent', tinman_id)])
174
207
        lion_id = transform.create_path('lion', root)
175
 
        self.assertEqual(transform.find_conflicts(), 
176
 
                         [('unversioned parent', tinman_id), 
 
208
        self.assertEqual(transform.find_conflicts(),
 
209
                         [('unversioned parent', tinman_id),
177
210
                          ('missing parent', tinman_id)])
178
211
        transform.adjust_path('name', lion_id, trans_id2)
179
 
        self.assertEqual(transform.find_conflicts(), 
 
212
        self.assertEqual(transform.find_conflicts(),
180
213
                         [('unversioned parent', lion_id),
181
214
                          ('missing parent', lion_id)])
182
215
        transform.version_file("Courage", lion_id)
183
 
        self.assertEqual(transform.find_conflicts(), 
184
 
                         [('missing parent', lion_id), 
 
216
        self.assertEqual(transform.find_conflicts(),
 
217
                         [('missing parent', lion_id),
185
218
                          ('versioning no contents', lion_id)])
186
219
        transform.adjust_path('name2', root, trans_id2)
187
 
        self.assertEqual(transform.find_conflicts(), 
 
220
        self.assertEqual(transform.find_conflicts(),
188
221
                         [('versioning no contents', lion_id)])
189
222
        transform.create_file('Contents, okay?', lion_id)
190
223
        transform.adjust_path('name2', trans_id2, trans_id2)
191
 
        self.assertEqual(transform.find_conflicts(), 
192
 
                         [('parent loop', trans_id2), 
 
224
        self.assertEqual(transform.find_conflicts(),
 
225
                         [('parent loop', trans_id2),
193
226
                          ('non-directory parent', trans_id2)])
194
227
        transform.adjust_path('name2', root, trans_id2)
195
228
        oz_id = transform.new_directory('oz', root)
196
229
        transform.set_executability(True, oz_id)
197
 
        self.assertEqual(transform.find_conflicts(), 
 
230
        self.assertEqual(transform.find_conflicts(),
198
231
                         [('unversioned executability', oz_id)])
199
232
        transform.version_file('oz-id', oz_id)
200
 
        self.assertEqual(transform.find_conflicts(), 
 
233
        self.assertEqual(transform.find_conflicts(),
201
234
                         [('non-file executability', oz_id)])
202
235
        transform.set_executability(None, oz_id)
203
236
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
212
245
        self.assert_('oz/tip' in transform2._tree_path_ids)
213
246
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
214
247
        self.assertEqual(len(result), 2)
215
 
        self.assertEqual((result[0][0], result[0][1]), 
 
248
        self.assertEqual((result[0][0], result[0][1]),
216
249
                         ('duplicate', newtip))
217
 
        self.assertEqual((result[1][0], result[1][2]), 
 
250
        self.assertEqual((result[1][0], result[1][2]),
218
251
                         ('duplicate id', newtip))
219
252
        transform2.finalize()
220
253
        transform3 = TreeTransform(self.wt)
221
254
        self.addCleanup(transform3.finalize)
222
255
        oz_id = transform3.trans_id_tree_file_id('oz-id')
223
256
        transform3.delete_contents(oz_id)
224
 
        self.assertEqual(transform3.find_conflicts(), 
 
257
        self.assertEqual(transform3.find_conflicts(),
225
258
                         [('missing parent', oz_id)])
226
259
        root_id = transform3.root
227
260
        tip_id = transform3.trans_id_tree_file_id('tip-id')
228
261
        transform3.adjust_path('tip', root_id, tip_id)
229
262
        transform3.apply()
230
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
 
231
372
    def test_add_del(self):
232
373
        start, root = self.get_transform()
233
374
        start.new_directory('a', root, 'a')
246
387
        self.addCleanup(unversion.finalize)
247
388
        parent = unversion.trans_id_tree_path('parent')
248
389
        unversion.unversion_file(parent)
249
 
        self.assertEqual(unversion.find_conflicts(), 
 
390
        self.assertEqual(unversion.find_conflicts(),
250
391
                         [('unversioned parent', parent_id)])
251
392
        file_id = unversion.trans_id_tree_file_id('child-id')
252
393
        unversion.unversion_file(file_id)
272
413
        mangle_tree.adjust_path('name2', root, name1)
273
414
        mangle_tree.adjust_path('name1', root, name2)
274
415
 
275
 
        #tests for deleting parent directories 
 
416
        #tests for deleting parent directories
276
417
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
277
418
        mangle_tree.delete_contents(ddir)
278
419
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
307
448
        create_tree,root = self.get_transform()
308
449
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
309
450
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
310
 
        create_tree.apply()        
 
451
        create_tree.apply()
311
452
        mangle_tree,root = self.get_transform()
312
453
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
313
454
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
321
462
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
322
463
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
323
464
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
324
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
465
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
325
466
                             'test_too_much-id')
326
 
        create_tree.apply()        
 
467
        create_tree.apply()
327
468
        mangle_tree,root = self.get_transform()
328
469
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
329
470
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
330
471
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
331
472
        mangle_tree.adjust_path('selftest', bzrlib, tests)
332
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
473
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
333
474
        mangle_tree.set_executability(True, test_too_much)
334
475
        mangle_tree.apply()
335
476
 
336
477
    def test_both_rename3(self):
337
478
        create_tree,root = self.get_transform()
338
479
        tests = create_tree.new_directory('tests', root, 'tests-id')
339
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
480
        create_tree.new_file('test_too_much.py', tests, 'hello1',
340
481
                             'test_too_much-id')
341
 
        create_tree.apply()        
 
482
        create_tree.apply()
342
483
        mangle_tree,root = self.get_transform()
343
484
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
344
485
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
345
486
        mangle_tree.adjust_path('selftest', root, tests)
346
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
487
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
347
488
        mangle_tree.set_executability(True, test_too_much)
348
489
        mangle_tree.apply()
349
490
 
362
503
        newdir = move_id.new_directory('dir', root, 'newdir')
363
504
        move_id.adjust_path('name2', newdir, name1)
364
505
        move_id.apply()
365
 
        
 
506
 
366
507
    def test_replace_dangling_ie(self):
367
508
        create_tree, root = self.get_transform()
368
509
        # prepare tree
384
525
        resolve_conflicts(replace)
385
526
        replace.apply()
386
527
 
387
 
    def test_symlinks(self):
388
 
        if not has_symlinks():
389
 
            raise TestSkipped('Symlinks are not supported on this platform')
390
 
        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()
391
535
        oz_id = transform.new_directory('oz', root, 'oz-id')
392
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
536
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
393
537
                                       'wizard-id')
394
 
        wiz_id = transform.create_path('wizard2', oz_id)
395
 
        transform.create_symlink('behind_curtain', wiz_id)
396
 
        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)
397
541
        transform.set_executability(True, wiz_id)
398
 
        self.assertEqual(transform.find_conflicts(), 
 
542
        self.assertEqual(transform.find_conflicts(),
399
543
                         [('non-file executability', wiz_id)])
400
544
        transform.set_executability(None, wiz_id)
401
545
        transform.apply()
402
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
403
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
404
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
405
 
                         'behind_curtain')
406
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
407
 
                         'wizard-target')
 
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
408
584
 
409
585
    def get_conflicted(self):
410
586
        create,root = self.get_transform()
414
590
        create.apply()
415
591
        conflicts,root = self.get_transform()
416
592
        # set up duplicate entry, duplicate id
417
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
593
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
418
594
                                         'dorothy-id')
419
595
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
420
596
        oz = conflicts.trans_id_tree_file_id('oz-id')
444
620
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
445
621
        raw_conflicts = resolve_conflicts(tt)
446
622
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
447
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
623
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
448
624
                                   'dorothy', None, 'dorothy-id')
449
625
        self.assertEqual(cooked_conflicts[0], duplicate)
450
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
626
        duplicate_id = DuplicateID('Unversioned existing file',
451
627
                                   'dorothy.moved', 'dorothy', None,
452
628
                                   'dorothy-id')
453
629
        self.assertEqual(cooked_conflicts[1], duplicate_id)
461
637
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
462
638
                                               'oz-id')
463
639
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
464
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
640
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
465
641
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
466
642
        self.assertEqual(cooked_conflicts[4], deleted_parent)
467
643
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
495
671
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
496
672
                                         ' oz/emeraldcity.  Cancelled move.')
497
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
 
498
729
    def test_moving_versioned_directories(self):
499
730
        create, root = self.get_transform()
500
731
        kansas = create.new_directory('kansas', root, 'kansas-id')
533
764
        rename.set_executability(True, myfile)
534
765
        rename.apply()
535
766
 
536
 
    def test_find_interesting(self):
537
 
        create, root = self.get_transform()
538
 
        wt = create._tree
539
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
540
 
        create.new_file('uvfile', root, 'othertext')
541
 
        create.apply()
542
 
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
543
 
            find_interesting, wt, wt, ['vfile'])
544
 
        self.assertEqual(result, set(['myfile-id']))
545
 
 
546
767
    def test_set_executability_order(self):
547
768
        """Ensure that executability behaves the same, no matter what order.
548
 
        
 
769
 
549
770
        - create file and set executability simultaneously
550
771
        - create file and set executability afterward
551
772
        - unsetting the executability of a file whose executability has not been
554
775
        """
555
776
        transform, root = self.get_transform()
556
777
        wt = transform._tree
 
778
        wt.lock_read()
 
779
        self.addCleanup(wt.unlock)
557
780
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
558
781
                           True)
559
782
        sac = transform.new_file('set_after_creation', root,
573
796
        transform, root = self.get_transform()
574
797
        transform.new_file('file1', root, 'contents', 'file1-id', True)
575
798
        transform.apply()
 
799
        self.wt.lock_write()
 
800
        self.addCleanup(self.wt.unlock)
576
801
        self.assertTrue(self.wt.is_executable('file1-id'))
577
802
        transform, root = self.get_transform()
578
803
        file1_id = transform.trans_id_tree_file_id('file1-id')
618
843
        transform.apply()
619
844
        transform, root = self.get_transform()
620
845
        try:
621
 
            self.assertEqual([], list(transform._iter_changes()))
 
846
            self.assertEqual([], list(transform.iter_changes()))
622
847
            old = transform.trans_id_tree_file_id('id-1')
623
848
            transform.unversion_file(old)
624
849
            self.assertEqual([('id-1', ('old', None), False, (True, False),
625
850
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
626
 
                (True, True))], list(transform._iter_changes()))
 
851
                (True, True))], list(transform.iter_changes()))
627
852
            transform.new_directory('new', root, 'id-1')
628
853
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
629
854
                ('eert_toor', 'eert_toor'), ('old', 'new'),
630
855
                ('file', 'directory'),
631
 
                (True, False))], list(transform._iter_changes()))
 
856
                (True, False))], list(transform.iter_changes()))
632
857
        finally:
633
858
            transform.finalize()
634
859
 
643
868
            transform.version_file('id-1', old)
644
869
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
645
870
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
646
 
                (False, False))], list(transform._iter_changes()))
 
871
                (False, False))], list(transform.iter_changes()))
647
872
        finally:
648
873
            transform.finalize()
649
874
 
659
884
            old = transform.trans_id_tree_path('old')
660
885
            subdir = transform.trans_id_tree_file_id('subdir-id')
661
886
            new = transform.trans_id_tree_path('new')
662
 
            self.assertEqual([], list(transform._iter_changes()))
 
887
            self.assertEqual([], list(transform.iter_changes()))
663
888
 
664
889
            #content deletion
665
890
            transform.delete_contents(old)
666
891
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
667
892
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
668
 
                (False, False))], list(transform._iter_changes()))
 
893
                (False, False))], list(transform.iter_changes()))
669
894
 
670
895
            #content change
671
896
            transform.create_file('blah', old)
672
897
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
673
898
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
674
 
                (False, False))], list(transform._iter_changes()))
 
899
                (False, False))], list(transform.iter_changes()))
675
900
            transform.cancel_deletion(old)
676
901
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
677
902
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
678
 
                (False, False))], list(transform._iter_changes()))
 
903
                (False, False))], list(transform.iter_changes()))
679
904
            transform.cancel_creation(old)
680
905
 
681
906
            # move file_id to a different file
682
 
            self.assertEqual([], list(transform._iter_changes()))
 
907
            self.assertEqual([], list(transform.iter_changes()))
683
908
            transform.unversion_file(old)
684
909
            transform.version_file('id-1', new)
685
910
            transform.adjust_path('old', root, new)
686
911
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
687
912
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
688
 
                (False, False))], list(transform._iter_changes()))
 
913
                (False, False))], list(transform.iter_changes()))
689
914
            transform.cancel_versioning(new)
690
915
            transform._removed_id = set()
691
916
 
692
917
            #execute bit
693
 
            self.assertEqual([], list(transform._iter_changes()))
 
918
            self.assertEqual([], list(transform.iter_changes()))
694
919
            transform.set_executability(True, old)
695
920
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
696
921
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
697
 
                (False, True))], list(transform._iter_changes()))
 
922
                (False, True))], list(transform.iter_changes()))
698
923
            transform.set_executability(None, old)
699
924
 
700
925
            # filename
701
 
            self.assertEqual([], list(transform._iter_changes()))
 
926
            self.assertEqual([], list(transform.iter_changes()))
702
927
            transform.adjust_path('new', root, old)
703
928
            transform._new_parent = {}
704
929
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
705
930
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
706
 
                (False, False))], list(transform._iter_changes()))
 
931
                (False, False))], list(transform.iter_changes()))
707
932
            transform._new_name = {}
708
933
 
709
934
            # parent directory
710
 
            self.assertEqual([], list(transform._iter_changes()))
 
935
            self.assertEqual([], list(transform.iter_changes()))
711
936
            transform.adjust_path('new', subdir, old)
712
937
            transform._new_name = {}
713
938
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
714
939
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
715
940
                ('file', 'file'), (False, False))],
716
 
                list(transform._iter_changes()))
 
941
                list(transform.iter_changes()))
717
942
            transform._new_path = {}
718
943
 
719
944
        finally:
741
966
                ('id-2', (u'file2', u'file2'), False, (True, True),
742
967
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
743
968
                ('file', 'file'), (False, True))],
744
 
                list(transform._iter_changes()))
 
969
                list(transform.iter_changes()))
745
970
        finally:
746
971
            transform.finalize()
747
972
 
761
986
            transform.adjust_path('flitter', root, floater)
762
987
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
763
988
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
764
 
            (None, None), (False, False))], list(transform._iter_changes()))
 
989
            (None, None), (False, False))], list(transform.iter_changes()))
765
990
        finally:
766
991
            transform.finalize()
767
992
 
776
1001
        try:
777
1002
            old = transform.trans_id_tree_path('old')
778
1003
            subdir = transform.trans_id_tree_file_id('subdir-id')
779
 
            self.assertEqual([], list(transform._iter_changes()))
 
1004
            self.assertEqual([], list(transform.iter_changes()))
780
1005
            transform.delete_contents(subdir)
781
1006
            transform.create_directory(subdir)
782
1007
            transform.set_executability(False, old)
783
1008
            transform.unversion_file(old)
784
1009
            transform.version_file('id-1', old)
785
1010
            transform.adjust_path('old', root, old)
786
 
            self.assertEqual([], list(transform._iter_changes()))
 
1011
            self.assertEqual([], list(transform.iter_changes()))
787
1012
        finally:
788
1013
            transform.finalize()
789
1014
 
954
1179
        transform.cancel_creation(parent)
955
1180
        transform.finalize()
956
1181
 
957
 
    def test_change_entry(self):
958
 
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
959
 
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
960
 
            None, None, None)
 
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'))
961
1369
 
962
1370
 
963
1371
class TransformGroup(object):
 
1372
 
964
1373
    def __init__(self, dirname, root_id):
965
1374
        self.name = dirname
966
1375
        os.mkdir(dirname)
977
1386
 
978
1387
 
979
1388
class TestTransformMerge(TestCaseInTempDir):
 
1389
 
980
1390
    def test_text_merge(self):
981
1391
        root_id = generate_ids.gen_root_id()
982
1392
        base = TransformGroup("base", root_id)
1012
1422
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1013
1423
        this.tt.apply()
1014
1424
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1425
 
1015
1426
        # textual merge
1016
1427
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1017
1428
        # three-way text conflict
1018
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1429
        self.assertEqual(this.wt.get_file('b').read(),
1019
1430
                         conflict_text('b', 'b2'))
1020
1431
        # OTHER wins
1021
1432
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1025
1436
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1026
1437
        # No change
1027
1438
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1028
 
        # Correct correct results when THIS == OTHER 
 
1439
        # Correct correct results when THIS == OTHER
1029
1440
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1030
1441
        # Text conflict when THIS & OTHER are text and BASE is dir
1031
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1442
        self.assertEqual(this.wt.get_file('h').read(),
1032
1443
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1033
1444
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1034
1445
                         '1\n2\n3\n4\n')
1035
1446
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1036
1447
                         'h\ni\nj\nk\n')
1037
1448
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1038
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1449
        self.assertEqual(this.wt.get_file('i').read(),
1039
1450
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1040
1451
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1041
1452
                         '1\n2\n3\n4\n')
1052
1463
        self.assertSubset(merge_modified, modified)
1053
1464
        self.assertEqual(len(merge_modified), len(modified))
1054
1465
        this.wt.remove('b')
1055
 
        this.wt.revert([])
 
1466
        this.wt.revert()
1056
1467
 
1057
1468
    def test_file_merge(self):
1058
 
        if not has_symlinks():
1059
 
            raise TestSkipped('Symlinks are not supported on this platform')
 
1469
        self.requireFeature(SymlinkFeature)
1060
1470
        root_id = generate_ids.gen_root_id()
1061
1471
        base = TransformGroup("BASE", root_id)
1062
1472
        this = TransformGroup("THIS", root_id)
1066
1476
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1067
1477
            tg.tt.new_file('c', tg.root, 'c', 'c')
1068
1478
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1069
 
        targets = ((base, 'base-e', 'base-f', None, None), 
1070
 
                   (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'),
1071
1481
                   (other, 'other-e', None, 'other-g', 'other-h'))
1072
1482
        for tg, e_target, f_target, g_target, h_target in targets:
1073
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1483
            for link, target in (('e', e_target), ('f', f_target),
1074
1484
                                 ('g', g_target), ('h', h_target)):
1075
1485
                if target is not None:
1076
1486
                    tg.tt.new_symlink(link, tg.root, target, link)
1102
1512
        base = TransformGroup("BASE", root_id)
1103
1513
        this = TransformGroup("THIS", root_id)
1104
1514
        other = TransformGroup("OTHER", root_id)
1105
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1515
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1106
1516
                                   for t in [base, this, other]]
1107
 
        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')
1108
1518
                                   for t in [base, this, other]]
1109
1519
        base.tt.new_directory('c', base_a, 'c')
1110
1520
        this.tt.new_directory('c1', this_a, 'c')
1135
1545
        base = TransformGroup("BASE", root_id)
1136
1546
        this = TransformGroup("THIS", root_id)
1137
1547
        other = TransformGroup("OTHER", root_id)
1138
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1548
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1139
1549
                                   for t in [base, this, other]]
1140
 
        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')
1141
1551
                                   for t in [base, this, other]]
1142
1552
 
1143
1553
        base.tt.new_file('g', base_a, 'g', 'g')
1164
1574
 
1165
1575
class TestBuildTree(tests.TestCaseWithTransport):
1166
1576
 
1167
 
    def test_build_tree(self):
1168
 
        if not has_symlinks():
1169
 
            raise TestSkipped('Test requires symlink support')
 
1577
    def test_build_tree_with_symlinks(self):
 
1578
        self.requireFeature(SymlinkFeature)
1170
1579
        os.mkdir('a')
1171
1580
        a = BzrDir.create_standalone_workingtree('a')
1172
1581
        os.mkdir('a/foo')
1220
1629
 
1221
1630
    def test_symlink_conflict_handling(self):
1222
1631
        """Ensure that when building trees, conflict handling is done"""
1223
 
        if not has_symlinks():
1224
 
            raise TestSkipped('Test requires symlink support')
 
1632
        self.requireFeature(SymlinkFeature)
1225
1633
        source = self.make_branch_and_tree('source')
1226
1634
        os.symlink('foo', 'source/symlink')
1227
1635
        source.add('symlink', 'new-symlink')
1236
1644
        os.symlink('foo', 'target2/symlink')
1237
1645
        build_tree(source.basis_tree(), target)
1238
1646
        self.assertEqual([], target.conflicts())
1239
 
        
 
1647
 
1240
1648
    def test_directory_conflict_handling(self):
1241
1649
        """Ensure that when building trees, conflict handling is done"""
1242
1650
        source = self.make_branch_and_tree('source')
1298
1706
        target = self.make_branch_and_tree('target')
1299
1707
        self.build_tree(['target/name'])
1300
1708
        target.add('name')
1301
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1709
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1302
1710
            build_tree, source.basis_tree(), target)
1303
1711
 
1304
1712
    def test_build_tree_rename_count(self):
1318
1726
        # children of non-root directories should not be renamed
1319
1727
        self.assertEqual(2, transform_result.rename_count)
1320
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
 
 
1876
class TestCommitTransform(tests.TestCaseWithTransport):
 
1877
 
 
1878
    def get_branch(self):
 
1879
        tree = self.make_branch_and_tree('tree')
 
1880
        tree.lock_write()
 
1881
        self.addCleanup(tree.unlock)
 
1882
        tree.commit('empty commit')
 
1883
        return tree.branch
 
1884
 
 
1885
    def get_branch_and_transform(self):
 
1886
        branch = self.get_branch()
 
1887
        tt = TransformPreview(branch.basis_tree())
 
1888
        self.addCleanup(tt.finalize)
 
1889
        return branch, tt
 
1890
 
 
1891
    def test_commit_wrong_basis(self):
 
1892
        branch = self.get_branch()
 
1893
        basis = branch.repository.revision_tree(
 
1894
            _mod_revision.NULL_REVISION)
 
1895
        tt = TransformPreview(basis)
 
1896
        self.addCleanup(tt.finalize)
 
1897
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
1898
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
1899
                         str(e))
 
1900
 
 
1901
    def test_empy_commit(self):
 
1902
        branch, tt = self.get_branch_and_transform()
 
1903
        rev = tt.commit(branch, 'my message')
 
1904
        self.assertEqual(2, branch.revno())
 
1905
        repo = branch.repository
 
1906
        self.assertEqual('my message', repo.get_revision(rev).message)
 
1907
 
 
1908
    def test_merge_parents(self):
 
1909
        branch, tt = self.get_branch_and_transform()
 
1910
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
1911
        self.assertEqual(['rev1b', 'rev1c'],
 
1912
                         branch.basis_tree().get_parent_ids()[1:])
 
1913
 
 
1914
    def test_first_commit(self):
 
1915
        branch = self.make_branch('branch')
 
1916
        branch.lock_write()
 
1917
        self.addCleanup(branch.unlock)
 
1918
        tt = TransformPreview(branch.basis_tree())
 
1919
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
1920
        rev = tt.commit(branch, 'my message')
 
1921
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
1922
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
1923
                            branch.last_revision())
 
1924
 
 
1925
    def test_first_commit_with_merge_parents(self):
 
1926
        branch = self.make_branch('branch')
 
1927
        branch.lock_write()
 
1928
        self.addCleanup(branch.unlock)
 
1929
        tt = TransformPreview(branch.basis_tree())
 
1930
        e = self.assertRaises(ValueError, tt.commit, branch,
 
1931
                          'my message', ['rev1b-id'])
 
1932
        self.assertEqual('Cannot supply merge parents for first commit.',
 
1933
                         str(e))
 
1934
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
1935
 
 
1936
    def test_add_files(self):
 
1937
        branch, tt = self.get_branch_and_transform()
 
1938
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
1939
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
1940
        tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
1941
        rev = tt.commit(branch, 'message')
 
1942
        tree = branch.basis_tree()
 
1943
        self.assertEqual('file', tree.id2path('file-id'))
 
1944
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
1945
        self.assertEqual('dir', tree.id2path('dir-id'))
 
1946
        self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
1947
        self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
1948
 
 
1949
    def test_add_unversioned(self):
 
1950
        branch, tt = self.get_branch_and_transform()
 
1951
        tt.new_file('file', tt.root, 'contents')
 
1952
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
1953
                          'message', strict=True)
 
1954
 
 
1955
    def test_modify_strict(self):
 
1956
        branch, tt = self.get_branch_and_transform()
 
1957
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
1958
        tt.commit(branch, 'message', strict=True)
 
1959
        tt = TransformPreview(branch.basis_tree())
 
1960
        trans_id = tt.trans_id_file_id('file-id')
 
1961
        tt.delete_contents(trans_id)
 
1962
        tt.create_file('contents', trans_id)
 
1963
        tt.commit(branch, 'message', strict=True)
 
1964
 
 
1965
    def test_commit_malformed(self):
 
1966
        """Committing a malformed transform should raise an exception.
 
1967
 
 
1968
        In this case, we are adding a file without adding its parent.
 
1969
        """
 
1970
        branch, tt = self.get_branch_and_transform()
 
1971
        parent_id = tt.trans_id_file_id('parent-id')
 
1972
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
1973
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
1974
                          'message')
 
1975
 
1321
1976
 
1322
1977
class MockTransform(object):
1323
1978
 
1336
1991
        object.__init__(self)
1337
1992
        self.name = "name"
1338
1993
 
 
1994
 
1339
1995
class TestGetBackupName(TestCase):
1340
1996
    def test_get_backup_name(self):
1341
1997
        tt = MockTransform()
1395
2051
        mover.rename('c/e', 'c/d')
1396
2052
        try:
1397
2053
            mover.rename('a', 'c')
1398
 
        except OSError, e:
 
2054
        except errors.FileExists, e:
1399
2055
            mover.rollback()
1400
2056
        self.failUnlessExists('a')
1401
2057
        self.failUnlessExists('c/d')
1468
2124
                          _mover=self.ExceptionFileMover(bad_target='d'))
1469
2125
        self.failUnlessExists('a')
1470
2126
        self.failUnlessExists('a/b')
 
2127
 
 
2128
    def test_resolve_no_parent(self):
 
2129
        wt = self.make_branch_and_tree('.')
 
2130
        tt = TreeTransform(wt)
 
2131
        self.addCleanup(tt.finalize)
 
2132
        parent = tt.trans_id_file_id('parent-id')
 
2133
        tt.new_file('file', parent, 'Contents')
 
2134
        resolve_conflicts(tt)
 
2135
 
 
2136
 
 
2137
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2138
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2139
                  (False, False))
 
2140
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2141
              ('', ''), ('directory', 'directory'), (False, None))
 
2142
 
 
2143
 
 
2144
class TestTransformPreview(tests.TestCaseWithTransport):
 
2145
 
 
2146
    def create_tree(self):
 
2147
        tree = self.make_branch_and_tree('.')
 
2148
        self.build_tree_contents([('a', 'content 1')])
 
2149
        tree.set_root_id('TREE_ROOT')
 
2150
        tree.add('a', 'a-id')
 
2151
        tree.commit('rev1', rev_id='rev1')
 
2152
        return tree.branch.repository.revision_tree('rev1')
 
2153
 
 
2154
    def get_empty_preview(self):
 
2155
        repository = self.make_repository('repo')
 
2156
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2157
        preview = TransformPreview(tree)
 
2158
        self.addCleanup(preview.finalize)
 
2159
        return preview
 
2160
 
 
2161
    def test_transform_preview(self):
 
2162
        revision_tree = self.create_tree()
 
2163
        preview = TransformPreview(revision_tree)
 
2164
        self.addCleanup(preview.finalize)
 
2165
 
 
2166
    def test_transform_preview_tree(self):
 
2167
        revision_tree = self.create_tree()
 
2168
        preview = TransformPreview(revision_tree)
 
2169
        self.addCleanup(preview.finalize)
 
2170
        preview.get_preview_tree()
 
2171
 
 
2172
    def test_transform_new_file(self):
 
2173
        revision_tree = self.create_tree()
 
2174
        preview = TransformPreview(revision_tree)
 
2175
        self.addCleanup(preview.finalize)
 
2176
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2177
        preview_tree = preview.get_preview_tree()
 
2178
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2179
        self.assertEqual(
 
2180
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2181
 
 
2182
    def test_diff_preview_tree(self):
 
2183
        revision_tree = self.create_tree()
 
2184
        preview = TransformPreview(revision_tree)
 
2185
        self.addCleanup(preview.finalize)
 
2186
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2187
        preview_tree = preview.get_preview_tree()
 
2188
        out = StringIO()
 
2189
        show_diff_trees(revision_tree, preview_tree, out)
 
2190
        lines = out.getvalue().splitlines()
 
2191
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2192
        # 3 lines of diff administrivia
 
2193
        self.assertEqual(lines[4], "+content B")
 
2194
 
 
2195
    def test_transform_conflicts(self):
 
2196
        revision_tree = self.create_tree()
 
2197
        preview = TransformPreview(revision_tree)
 
2198
        self.addCleanup(preview.finalize)
 
2199
        preview.new_file('a', preview.root, 'content 2')
 
2200
        resolve_conflicts(preview)
 
2201
        trans_id = preview.trans_id_file_id('a-id')
 
2202
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2203
 
 
2204
    def get_tree_and_preview_tree(self):
 
2205
        revision_tree = self.create_tree()
 
2206
        preview = TransformPreview(revision_tree)
 
2207
        self.addCleanup(preview.finalize)
 
2208
        a_trans_id = preview.trans_id_file_id('a-id')
 
2209
        preview.delete_contents(a_trans_id)
 
2210
        preview.create_file('b content', a_trans_id)
 
2211
        preview_tree = preview.get_preview_tree()
 
2212
        return revision_tree, preview_tree
 
2213
 
 
2214
    def test_iter_changes(self):
 
2215
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2216
        root = revision_tree.inventory.root.file_id
 
2217
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2218
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2219
                          (False, False))],
 
2220
                          list(preview_tree.iter_changes(revision_tree)))
 
2221
 
 
2222
    def test_include_unchanged_succeeds(self):
 
2223
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2224
        changes = preview_tree.iter_changes(revision_tree,
 
2225
                                            include_unchanged=True)
 
2226
        root = revision_tree.inventory.root.file_id
 
2227
 
 
2228
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2229
 
 
2230
    def test_specific_files(self):
 
2231
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2232
        changes = preview_tree.iter_changes(revision_tree,
 
2233
                                            specific_files=[''])
 
2234
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2235
 
 
2236
    def test_want_unversioned(self):
 
2237
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2238
        changes = preview_tree.iter_changes(revision_tree,
 
2239
                                            want_unversioned=True)
 
2240
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2241
 
 
2242
    def test_ignore_extra_trees_no_specific_files(self):
 
2243
        # extra_trees is harmless without specific_files, so we'll silently
 
2244
        # accept it, even though we won't use it.
 
2245
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2246
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2247
 
 
2248
    def test_ignore_require_versioned_no_specific_files(self):
 
2249
        # require_versioned is meaningless without specific_files.
 
2250
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2251
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2252
 
 
2253
    def test_ignore_pb(self):
 
2254
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2255
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2256
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2257
 
 
2258
    def test_kind(self):
 
2259
        revision_tree = self.create_tree()
 
2260
        preview = TransformPreview(revision_tree)
 
2261
        self.addCleanup(preview.finalize)
 
2262
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2263
        preview.new_directory('directory', preview.root, 'dir-id')
 
2264
        preview_tree = preview.get_preview_tree()
 
2265
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2266
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2267
 
 
2268
    def test_get_file_mtime(self):
 
2269
        preview = self.get_empty_preview()
 
2270
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2271
                                         'file-id')
 
2272
        limbo_path = preview._limbo_name(file_trans_id)
 
2273
        preview_tree = preview.get_preview_tree()
 
2274
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2275
                         preview_tree.get_file_mtime('file-id'))
 
2276
 
 
2277
    def test_get_file(self):
 
2278
        preview = self.get_empty_preview()
 
2279
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2280
        preview_tree = preview.get_preview_tree()
 
2281
        tree_file = preview_tree.get_file('file-id')
 
2282
        try:
 
2283
            self.assertEqual('contents', tree_file.read())
 
2284
        finally:
 
2285
            tree_file.close()
 
2286
 
 
2287
    def test_get_symlink_target(self):
 
2288
        self.requireFeature(SymlinkFeature)
 
2289
        preview = self.get_empty_preview()
 
2290
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2291
        preview_tree = preview.get_preview_tree()
 
2292
        self.assertEqual('target',
 
2293
                         preview_tree.get_symlink_target('symlink-id'))
 
2294
 
 
2295
    def test_all_file_ids(self):
 
2296
        tree = self.make_branch_and_tree('tree')
 
2297
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2298
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2299
        preview = TransformPreview(tree)
 
2300
        self.addCleanup(preview.finalize)
 
2301
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2302
        c_trans_id = preview.trans_id_file_id('c-id')
 
2303
        preview.unversion_file(c_trans_id)
 
2304
        preview.version_file('c-id', c_trans_id)
 
2305
        preview_tree = preview.get_preview_tree()
 
2306
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2307
                         preview_tree.all_file_ids())
 
2308
 
 
2309
    def test_path2id_deleted_unchanged(self):
 
2310
        tree = self.make_branch_and_tree('tree')
 
2311
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2312
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2313
        preview = TransformPreview(tree)
 
2314
        self.addCleanup(preview.finalize)
 
2315
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2316
        preview_tree = preview.get_preview_tree()
 
2317
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2318
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2319
 
 
2320
    def test_path2id_created(self):
 
2321
        tree = self.make_branch_and_tree('tree')
 
2322
        self.build_tree(['tree/unchanged'])
 
2323
        tree.add(['unchanged'], ['unchanged-id'])
 
2324
        preview = TransformPreview(tree)
 
2325
        self.addCleanup(preview.finalize)
 
2326
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2327
            'contents', 'new-id')
 
2328
        preview_tree = preview.get_preview_tree()
 
2329
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2330
 
 
2331
    def test_path2id_moved(self):
 
2332
        tree = self.make_branch_and_tree('tree')
 
2333
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2334
        tree.add(['old_parent', 'old_parent/child'],
 
2335
                 ['old_parent-id', 'child-id'])
 
2336
        preview = TransformPreview(tree)
 
2337
        self.addCleanup(preview.finalize)
 
2338
        new_parent = preview.new_directory('new_parent', preview.root,
 
2339
                                           'new_parent-id')
 
2340
        preview.adjust_path('child', new_parent,
 
2341
                            preview.trans_id_file_id('child-id'))
 
2342
        preview_tree = preview.get_preview_tree()
 
2343
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2344
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2345
 
 
2346
    def test_path2id_renamed_parent(self):
 
2347
        tree = self.make_branch_and_tree('tree')
 
2348
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2349
        tree.add(['old_name', 'old_name/child'],
 
2350
                 ['parent-id', 'child-id'])
 
2351
        preview = TransformPreview(tree)
 
2352
        self.addCleanup(preview.finalize)
 
2353
        preview.adjust_path('new_name', preview.root,
 
2354
                            preview.trans_id_file_id('parent-id'))
 
2355
        preview_tree = preview.get_preview_tree()
 
2356
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2357
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2358
 
 
2359
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2360
        preview_tree = tt.get_preview_tree()
 
2361
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2362
                              specific_file_ids))
 
2363
        tree = tt._tree
 
2364
        tt.apply()
 
2365
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2366
        self.assertEqual(actual_result, preview_result)
 
2367
 
 
2368
    def test_iter_entries_by_dir_new(self):
 
2369
        tree = self.make_branch_and_tree('tree')
 
2370
        tt = TreeTransform(tree)
 
2371
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2372
        self.assertMatchingIterEntries(tt)
 
2373
 
 
2374
    def test_iter_entries_by_dir_deleted(self):
 
2375
        tree = self.make_branch_and_tree('tree')
 
2376
        self.build_tree(['tree/deleted'])
 
2377
        tree.add('deleted', 'deleted-id')
 
2378
        tt = TreeTransform(tree)
 
2379
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2380
        self.assertMatchingIterEntries(tt)
 
2381
 
 
2382
    def test_iter_entries_by_dir_unversioned(self):
 
2383
        tree = self.make_branch_and_tree('tree')
 
2384
        self.build_tree(['tree/removed'])
 
2385
        tree.add('removed', 'removed-id')
 
2386
        tt = TreeTransform(tree)
 
2387
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2388
        self.assertMatchingIterEntries(tt)
 
2389
 
 
2390
    def test_iter_entries_by_dir_moved(self):
 
2391
        tree = self.make_branch_and_tree('tree')
 
2392
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2393
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2394
        tt = TreeTransform(tree)
 
2395
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2396
                       tt.trans_id_file_id('moved-id'))
 
2397
        self.assertMatchingIterEntries(tt)
 
2398
 
 
2399
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2400
        tree = self.make_branch_and_tree('tree')
 
2401
        tree.set_root_id('tree-root-id')
 
2402
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2403
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2404
        tt = TreeTransform(tree)
 
2405
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2406
 
 
2407
    def test_symlink_content_summary(self):
 
2408
        self.requireFeature(SymlinkFeature)
 
2409
        preview = self.get_empty_preview()
 
2410
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2411
        summary = preview.get_preview_tree().path_content_summary('path')
 
2412
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2413
 
 
2414
    def test_missing_content_summary(self):
 
2415
        preview = self.get_empty_preview()
 
2416
        summary = preview.get_preview_tree().path_content_summary('path')
 
2417
        self.assertEqual(('missing', None, None, None), summary)
 
2418
 
 
2419
    def test_deleted_content_summary(self):
 
2420
        tree = self.make_branch_and_tree('tree')
 
2421
        self.build_tree(['tree/path/'])
 
2422
        tree.add('path')
 
2423
        preview = TransformPreview(tree)
 
2424
        self.addCleanup(preview.finalize)
 
2425
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2426
        summary = preview.get_preview_tree().path_content_summary('path')
 
2427
        self.assertEqual(('missing', None, None, None), summary)
 
2428
 
 
2429
    def test_file_content_summary_executable(self):
 
2430
        if not osutils.supports_executable():
 
2431
            raise TestNotApplicable()
 
2432
        preview = self.get_empty_preview()
 
2433
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2434
        preview.set_executability(True, path_id)
 
2435
        summary = preview.get_preview_tree().path_content_summary('path')
 
2436
        self.assertEqual(4, len(summary))
 
2437
        self.assertEqual('file', summary[0])
 
2438
        # size must be known
 
2439
        self.assertEqual(len('contents'), summary[1])
 
2440
        # executable
 
2441
        self.assertEqual(True, summary[2])
 
2442
        # will not have hash (not cheap to determine)
 
2443
        self.assertIs(None, summary[3])
 
2444
 
 
2445
    def test_change_executability(self):
 
2446
        if not osutils.supports_executable():
 
2447
            raise TestNotApplicable()
 
2448
        tree = self.make_branch_and_tree('tree')
 
2449
        self.build_tree(['tree/path'])
 
2450
        tree.add('path')
 
2451
        preview = TransformPreview(tree)
 
2452
        self.addCleanup(preview.finalize)
 
2453
        path_id = preview.trans_id_tree_path('path')
 
2454
        preview.set_executability(True, path_id)
 
2455
        summary = preview.get_preview_tree().path_content_summary('path')
 
2456
        self.assertEqual(True, summary[2])
 
2457
 
 
2458
    def test_file_content_summary_non_exec(self):
 
2459
        preview = self.get_empty_preview()
 
2460
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
2461
        summary = preview.get_preview_tree().path_content_summary('path')
 
2462
        self.assertEqual(4, len(summary))
 
2463
        self.assertEqual('file', summary[0])
 
2464
        # size must be known
 
2465
        self.assertEqual(len('contents'), summary[1])
 
2466
        # not executable
 
2467
        if osutils.supports_executable():
 
2468
            self.assertEqual(False, summary[2])
 
2469
        else:
 
2470
            self.assertEqual(None, summary[2])
 
2471
        # will not have hash (not cheap to determine)
 
2472
        self.assertIs(None, summary[3])
 
2473
 
 
2474
    def test_dir_content_summary(self):
 
2475
        preview = self.get_empty_preview()
 
2476
        preview.new_directory('path', preview.root, 'path-id')
 
2477
        summary = preview.get_preview_tree().path_content_summary('path')
 
2478
        self.assertEqual(('directory', None, None, None), summary)
 
2479
 
 
2480
    def test_tree_content_summary(self):
 
2481
        preview = self.get_empty_preview()
 
2482
        path = preview.new_directory('path', preview.root, 'path-id')
 
2483
        preview.set_tree_reference('rev-1', path)
 
2484
        summary = preview.get_preview_tree().path_content_summary('path')
 
2485
        self.assertEqual(4, len(summary))
 
2486
        self.assertEqual('tree-reference', summary[0])
 
2487
 
 
2488
    def test_annotate(self):
 
2489
        tree = self.make_branch_and_tree('tree')
 
2490
        self.build_tree_contents([('tree/file', 'a\n')])
 
2491
        tree.add('file', 'file-id')
 
2492
        tree.commit('a', rev_id='one')
 
2493
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2494
        preview = TransformPreview(tree)
 
2495
        self.addCleanup(preview.finalize)
 
2496
        file_trans_id = preview.trans_id_file_id('file-id')
 
2497
        preview.delete_contents(file_trans_id)
 
2498
        preview.create_file('a\nb\nc\n', file_trans_id)
 
2499
        preview_tree = preview.get_preview_tree()
 
2500
        expected = [
 
2501
            ('one', 'a\n'),
 
2502
            ('me:', 'b\n'),
 
2503
            ('me:', 'c\n'),
 
2504
        ]
 
2505
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2506
        self.assertEqual(expected, annotation)
 
2507
 
 
2508
    def test_annotate_missing(self):
 
2509
        preview = self.get_empty_preview()
 
2510
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2511
        preview_tree = preview.get_preview_tree()
 
2512
        expected = [
 
2513
            ('me:', 'a\n'),
 
2514
            ('me:', 'b\n'),
 
2515
            ('me:', 'c\n'),
 
2516
         ]
 
2517
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2518
        self.assertEqual(expected, annotation)
 
2519
 
 
2520
    def test_annotate_rename(self):
 
2521
        tree = self.make_branch_and_tree('tree')
 
2522
        self.build_tree_contents([('tree/file', 'a\n')])
 
2523
        tree.add('file', 'file-id')
 
2524
        tree.commit('a', rev_id='one')
 
2525
        preview = TransformPreview(tree)
 
2526
        self.addCleanup(preview.finalize)
 
2527
        file_trans_id = preview.trans_id_file_id('file-id')
 
2528
        preview.adjust_path('newname', preview.root, file_trans_id)
 
2529
        preview_tree = preview.get_preview_tree()
 
2530
        expected = [
 
2531
            ('one', 'a\n'),
 
2532
        ]
 
2533
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2534
        self.assertEqual(expected, annotation)
 
2535
 
 
2536
    def test_annotate_deleted(self):
 
2537
        tree = self.make_branch_and_tree('tree')
 
2538
        self.build_tree_contents([('tree/file', 'a\n')])
 
2539
        tree.add('file', 'file-id')
 
2540
        tree.commit('a', rev_id='one')
 
2541
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2542
        preview = TransformPreview(tree)
 
2543
        self.addCleanup(preview.finalize)
 
2544
        file_trans_id = preview.trans_id_file_id('file-id')
 
2545
        preview.delete_contents(file_trans_id)
 
2546
        preview_tree = preview.get_preview_tree()
 
2547
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2548
        self.assertIs(None, annotation)
 
2549
 
 
2550
    def test_stored_kind(self):
 
2551
        preview = self.get_empty_preview()
 
2552
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2553
        preview_tree = preview.get_preview_tree()
 
2554
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
2555
 
 
2556
    def test_is_executable(self):
 
2557
        preview = self.get_empty_preview()
 
2558
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2559
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
2560
        preview_tree = preview.get_preview_tree()
 
2561
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2562
 
 
2563
    def test_get_set_parent_ids(self):
 
2564
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2565
        self.assertEqual([], preview_tree.get_parent_ids())
 
2566
        preview_tree.set_parent_ids(['rev-1'])
 
2567
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2568
 
 
2569
    def test_plan_file_merge(self):
 
2570
        work_a = self.make_branch_and_tree('wta')
 
2571
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2572
        work_a.add('file', 'file-id')
 
2573
        base_id = work_a.commit('base version')
 
2574
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2575
        preview = TransformPreview(work_a)
 
2576
        self.addCleanup(preview.finalize)
 
2577
        trans_id = preview.trans_id_file_id('file-id')
 
2578
        preview.delete_contents(trans_id)
 
2579
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2580
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2581
        tree_a = preview.get_preview_tree()
 
2582
        tree_a.set_parent_ids([base_id])
 
2583
        self.assertEqual([
 
2584
            ('killed-a', 'a\n'),
 
2585
            ('killed-b', 'b\n'),
 
2586
            ('unchanged', 'c\n'),
 
2587
            ('unchanged', 'd\n'),
 
2588
            ('new-a', 'e\n'),
 
2589
            ('new-b', 'f\n'),
 
2590
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2591
 
 
2592
    def test_plan_file_merge_revision_tree(self):
 
2593
        work_a = self.make_branch_and_tree('wta')
 
2594
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2595
        work_a.add('file', 'file-id')
 
2596
        base_id = work_a.commit('base version')
 
2597
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2598
        preview = TransformPreview(work_a.basis_tree())
 
2599
        self.addCleanup(preview.finalize)
 
2600
        trans_id = preview.trans_id_file_id('file-id')
 
2601
        preview.delete_contents(trans_id)
 
2602
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2603
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2604
        tree_a = preview.get_preview_tree()
 
2605
        tree_a.set_parent_ids([base_id])
 
2606
        self.assertEqual([
 
2607
            ('killed-a', 'a\n'),
 
2608
            ('killed-b', 'b\n'),
 
2609
            ('unchanged', 'c\n'),
 
2610
            ('unchanged', 'd\n'),
 
2611
            ('new-a', 'e\n'),
 
2612
            ('new-b', 'f\n'),
 
2613
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2614
 
 
2615
    def test_walkdirs(self):
 
2616
        preview = self.get_empty_preview()
 
2617
        preview.version_file('tree-root', preview.root)
 
2618
        preview_tree = preview.get_preview_tree()
 
2619
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2620
                                         'a-id')
 
2621
        expected = [(('', 'tree-root'),
 
2622
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2623
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2624
 
 
2625
    def test_extras(self):
 
2626
        work_tree = self.make_branch_and_tree('tree')
 
2627
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2628
                         'tree/not-removed-file'])
 
2629
        work_tree.add(['removed-file', 'not-removed-file'])
 
2630
        preview = TransformPreview(work_tree)
 
2631
        self.addCleanup(preview.finalize)
 
2632
        preview.new_file('new-file', preview.root, 'contents')
 
2633
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2634
                         'new-versioned-id')
 
2635
        tree = preview.get_preview_tree()
 
2636
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2637
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2638
                         set(tree.extras()))
 
2639
 
 
2640
    def test_merge_into_preview(self):
 
2641
        work_tree = self.make_branch_and_tree('tree')
 
2642
        self.build_tree_contents([('tree/file','b\n')])
 
2643
        work_tree.add('file', 'file-id')
 
2644
        work_tree.commit('first commit')
 
2645
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2646
        self.build_tree_contents([('child/file','b\nc\n')])
 
2647
        child_tree.commit('child commit')
 
2648
        child_tree.lock_write()
 
2649
        self.addCleanup(child_tree.unlock)
 
2650
        work_tree.lock_write()
 
2651
        self.addCleanup(work_tree.unlock)
 
2652
        preview = TransformPreview(work_tree)
 
2653
        self.addCleanup(preview.finalize)
 
2654
        preview_tree = preview.get_preview_tree()
 
2655
        file_trans_id = preview.trans_id_file_id('file-id')
 
2656
        preview.delete_contents(file_trans_id)
 
2657
        preview.create_file('a\nb\n', file_trans_id)
 
2658
        pb = progress.DummyProgress()
 
2659
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2660
                                          child_tree.branch.last_revision(),
 
2661
                                          other_branch=child_tree.branch,
 
2662
                                          tree_branch=work_tree.branch)
 
2663
        merger.merge_type = Merge3Merger
 
2664
        tt = merger.make_merger().make_preview_transform()
 
2665
        self.addCleanup(tt.finalize)
 
2666
        final_tree = tt.get_preview_tree()
 
2667
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2668
 
 
2669
    def test_merge_preview_into_workingtree(self):
 
2670
        tree = self.make_branch_and_tree('tree')
 
2671
        tree.set_root_id('TREE_ROOT')
 
2672
        tt = TransformPreview(tree)
 
2673
        self.addCleanup(tt.finalize)
 
2674
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2675
        tree2 = self.make_branch_and_tree('tree2')
 
2676
        tree2.set_root_id('TREE_ROOT')
 
2677
        pb = progress.DummyProgress()
 
2678
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2679
                                         pb, tree.basis_tree())
 
2680
        merger.merge_type = Merge3Merger
 
2681
        merger.do_merge()
 
2682
 
 
2683
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2684
        tree = self.make_branch_and_tree('tree')
 
2685
        self.build_tree_contents([('tree/foo', 'bar')])
 
2686
        tree.add('foo', 'foo-id')
 
2687
        tree.commit('foo')
 
2688
        tt = TransformPreview(tree)
 
2689
        self.addCleanup(tt.finalize)
 
2690
        trans_id = tt.trans_id_file_id('foo-id')
 
2691
        tt.delete_contents(trans_id)
 
2692
        tt.create_file('baz', trans_id)
 
2693
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2694
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2695
        pb = progress.DummyProgress()
 
2696
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2697
                                         pb, tree.basis_tree())
 
2698
        merger.merge_type = Merge3Merger
 
2699
        merger.do_merge()
 
2700
 
 
2701
    def test_is_executable(self):
 
2702
        tree = self.make_branch_and_tree('tree')
 
2703
        preview = TransformPreview(tree)
 
2704
        self.addCleanup(preview.finalize)
 
2705
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2706
        preview_tree = preview.get_preview_tree()
 
2707
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2708
                                                           'tree/foo'))
 
2709
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2710
 
 
2711
    def test_commit_preview_tree(self):
 
2712
        tree = self.make_branch_and_tree('tree')
 
2713
        rev_id = tree.commit('rev1')
 
2714
        tree.branch.lock_write()
 
2715
        self.addCleanup(tree.branch.unlock)
 
2716
        tt = TransformPreview(tree)
 
2717
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2718
        self.addCleanup(tt.finalize)
 
2719
        preview = tt.get_preview_tree()
 
2720
        preview.set_parent_ids([rev_id])
 
2721
        builder = tree.branch.get_commit_builder([rev_id])
 
2722
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2723
        builder.finish_inventory()
 
2724
        rev2_id = builder.commit('rev2')
 
2725
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2726
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2727
 
 
2728
 
 
2729
class FakeSerializer(object):
 
2730
    """Serializer implementation that simply returns the input.
 
2731
 
 
2732
    The input is returned in the order used by pack.ContainerPushParser.
 
2733
    """
 
2734
    @staticmethod
 
2735
    def bytes_record(bytes, names):
 
2736
        return names, bytes
 
2737
 
 
2738
 
 
2739
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2740
 
 
2741
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2742
 
 
2743
    def get_preview(self, tree=None):
 
2744
        if tree is None:
 
2745
            tree = self.make_branch_and_tree('tree')
 
2746
        tt = TransformPreview(tree)
 
2747
        self.addCleanup(tt.finalize)
 
2748
        return tt
 
2749
 
 
2750
    def assertSerializesTo(self, expected, tt):
 
2751
        records = list(tt.serialize(FakeSerializer()))
 
2752
        self.assertEqual(expected, records)
 
2753
 
 
2754
    @staticmethod
 
2755
    def default_attribs():
 
2756
        return {
 
2757
            '_id_number': 1,
 
2758
            '_new_name': {},
 
2759
            '_new_parent': {},
 
2760
            '_new_executability': {},
 
2761
            '_new_id': {},
 
2762
            '_tree_path_ids': {'': 'new-0'},
 
2763
            '_removed_id': [],
 
2764
            '_removed_contents': [],
 
2765
            '_non_present_ids': {},
 
2766
            }
 
2767
 
 
2768
    def make_records(self, attribs, contents):
 
2769
        records = [
 
2770
            (((('attribs'),),), bencode.bencode(attribs))]
 
2771
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2772
        return records
 
2773
 
 
2774
    def creation_records(self):
 
2775
        attribs = self.default_attribs()
 
2776
        attribs['_id_number'] = 3
 
2777
        attribs['_new_name'] = {
 
2778
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2779
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2780
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2781
        attribs['_new_executability'] = {'new-1': 1}
 
2782
        contents = [
 
2783
            ('new-1', 'file', 'i 1\nbar\n'),
 
2784
            ('new-2', 'directory', ''),
 
2785
            ]
 
2786
        return self.make_records(attribs, contents)
 
2787
 
 
2788
    def test_serialize_creation(self):
 
2789
        tt = self.get_preview()
 
2790
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2791
        tt.new_directory('qux', tt.root, 'quxx')
 
2792
        self.assertSerializesTo(self.creation_records(), tt)
 
2793
 
 
2794
    def test_deserialize_creation(self):
 
2795
        tt = self.get_preview()
 
2796
        tt.deserialize(iter(self.creation_records()))
 
2797
        self.assertEqual(3, tt._id_number)
 
2798
        self.assertEqual({'new-1': u'foo\u1234',
 
2799
                          'new-2': 'qux'}, tt._new_name)
 
2800
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2801
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2802
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2803
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2804
        self.assertEqual({'new-1': 'file',
 
2805
                          'new-2': 'directory'}, tt._new_contents)
 
2806
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2807
        try:
 
2808
            foo_content = foo_limbo.read()
 
2809
        finally:
 
2810
            foo_limbo.close()
 
2811
        self.assertEqual('bar', foo_content)
 
2812
 
 
2813
    def symlink_creation_records(self):
 
2814
        attribs = self.default_attribs()
 
2815
        attribs['_id_number'] = 2
 
2816
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2817
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2818
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2819
        return self.make_records(attribs, contents)
 
2820
 
 
2821
    def test_serialize_symlink_creation(self):
 
2822
        self.requireFeature(tests.SymlinkFeature)
 
2823
        tt = self.get_preview()
 
2824
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2825
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2826
 
 
2827
    def test_deserialize_symlink_creation(self):
 
2828
        self.requireFeature(tests.SymlinkFeature)
 
2829
        tt = self.get_preview()
 
2830
        tt.deserialize(iter(self.symlink_creation_records()))
 
2831
        abspath = tt._limbo_name('new-1')
 
2832
        foo_content = osutils.readlink(abspath)
 
2833
        self.assertEqual(u'bar\u1234', foo_content)
 
2834
 
 
2835
    def make_destruction_preview(self):
 
2836
        tree = self.make_branch_and_tree('.')
 
2837
        self.build_tree([u'foo\u1234', 'bar'])
 
2838
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2839
        return self.get_preview(tree)
 
2840
 
 
2841
    def destruction_records(self):
 
2842
        attribs = self.default_attribs()
 
2843
        attribs['_id_number'] = 3
 
2844
        attribs['_removed_id'] = ['new-1']
 
2845
        attribs['_removed_contents'] = ['new-2']
 
2846
        attribs['_tree_path_ids'] = {
 
2847
            '': 'new-0',
 
2848
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2849
            'bar': 'new-2',
 
2850
            }
 
2851
        return self.make_records(attribs, [])
 
2852
 
 
2853
    def test_serialize_destruction(self):
 
2854
        tt = self.make_destruction_preview()
 
2855
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2856
        tt.unversion_file(foo_trans_id)
 
2857
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2858
        tt.delete_contents(bar_trans_id)
 
2859
        self.assertSerializesTo(self.destruction_records(), tt)
 
2860
 
 
2861
    def test_deserialize_destruction(self):
 
2862
        tt = self.make_destruction_preview()
 
2863
        tt.deserialize(iter(self.destruction_records()))
 
2864
        self.assertEqual({u'foo\u1234': 'new-1',
 
2865
                          'bar': 'new-2',
 
2866
                          '': tt.root}, tt._tree_path_ids)
 
2867
        self.assertEqual({'new-1': u'foo\u1234',
 
2868
                          'new-2': 'bar',
 
2869
                          tt.root: ''}, tt._tree_id_paths)
 
2870
        self.assertEqual(set(['new-1']), tt._removed_id)
 
2871
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
2872
 
 
2873
    def missing_records(self):
 
2874
        attribs = self.default_attribs()
 
2875
        attribs['_id_number'] = 2
 
2876
        attribs['_non_present_ids'] = {
 
2877
            'boo': 'new-1',}
 
2878
        return self.make_records(attribs, [])
 
2879
 
 
2880
    def test_serialize_missing(self):
 
2881
        tt = self.get_preview()
 
2882
        boo_trans_id = tt.trans_id_file_id('boo')
 
2883
        self.assertSerializesTo(self.missing_records(), tt)
 
2884
 
 
2885
    def test_deserialize_missing(self):
 
2886
        tt = self.get_preview()
 
2887
        tt.deserialize(iter(self.missing_records()))
 
2888
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
2889
 
 
2890
    def make_modification_preview(self):
 
2891
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2892
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2893
        tree = self.make_branch_and_tree('tree')
 
2894
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2895
        tree.add('file', 'file-id')
 
2896
        return self.get_preview(tree), LINES_TWO
 
2897
 
 
2898
    def modification_records(self):
 
2899
        attribs = self.default_attribs()
 
2900
        attribs['_id_number'] = 2
 
2901
        attribs['_tree_path_ids'] = {
 
2902
            'file': 'new-1',
 
2903
            '': 'new-0',}
 
2904
        attribs['_removed_contents'] = ['new-1']
 
2905
        contents = [('new-1', 'file',
 
2906
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
2907
        return self.make_records(attribs, contents)
 
2908
 
 
2909
    def test_serialize_modification(self):
 
2910
        tt, LINES = self.make_modification_preview()
 
2911
        trans_id = tt.trans_id_file_id('file-id')
 
2912
        tt.delete_contents(trans_id)
 
2913
        tt.create_file(LINES, trans_id)
 
2914
        self.assertSerializesTo(self.modification_records(), tt)
 
2915
 
 
2916
    def test_deserialize_modification(self):
 
2917
        tt, LINES = self.make_modification_preview()
 
2918
        tt.deserialize(iter(self.modification_records()))
 
2919
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2920
 
 
2921
    def make_kind_change_preview(self):
 
2922
        LINES = 'a\nb\nc\nd\n'
 
2923
        tree = self.make_branch_and_tree('tree')
 
2924
        self.build_tree(['tree/foo/'])
 
2925
        tree.add('foo', 'foo-id')
 
2926
        return self.get_preview(tree), LINES
 
2927
 
 
2928
    def kind_change_records(self):
 
2929
        attribs = self.default_attribs()
 
2930
        attribs['_id_number'] = 2
 
2931
        attribs['_tree_path_ids'] = {
 
2932
            'foo': 'new-1',
 
2933
            '': 'new-0',}
 
2934
        attribs['_removed_contents'] = ['new-1']
 
2935
        contents = [('new-1', 'file',
 
2936
                     'i 4\na\nb\nc\nd\n\n')]
 
2937
        return self.make_records(attribs, contents)
 
2938
 
 
2939
    def test_serialize_kind_change(self):
 
2940
        tt, LINES = self.make_kind_change_preview()
 
2941
        trans_id = tt.trans_id_file_id('foo-id')
 
2942
        tt.delete_contents(trans_id)
 
2943
        tt.create_file(LINES, trans_id)
 
2944
        self.assertSerializesTo(self.kind_change_records(), tt)
 
2945
 
 
2946
    def test_deserialize_kind_change(self):
 
2947
        tt, LINES = self.make_kind_change_preview()
 
2948
        tt.deserialize(iter(self.kind_change_records()))
 
2949
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2950
 
 
2951
    def make_add_contents_preview(self):
 
2952
        LINES = 'a\nb\nc\nd\n'
 
2953
        tree = self.make_branch_and_tree('tree')
 
2954
        self.build_tree(['tree/foo'])
 
2955
        tree.add('foo')
 
2956
        os.unlink('tree/foo')
 
2957
        return self.get_preview(tree), LINES
 
2958
 
 
2959
    def add_contents_records(self):
 
2960
        attribs = self.default_attribs()
 
2961
        attribs['_id_number'] = 2
 
2962
        attribs['_tree_path_ids'] = {
 
2963
            'foo': 'new-1',
 
2964
            '': 'new-0',}
 
2965
        contents = [('new-1', 'file',
 
2966
                     'i 4\na\nb\nc\nd\n\n')]
 
2967
        return self.make_records(attribs, contents)
 
2968
 
 
2969
    def test_serialize_add_contents(self):
 
2970
        tt, LINES = self.make_add_contents_preview()
 
2971
        trans_id = tt.trans_id_tree_path('foo')
 
2972
        tt.create_file(LINES, trans_id)
 
2973
        self.assertSerializesTo(self.add_contents_records(), tt)
 
2974
 
 
2975
    def test_deserialize_add_contents(self):
 
2976
        tt, LINES = self.make_add_contents_preview()
 
2977
        tt.deserialize(iter(self.add_contents_records()))
 
2978
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2979
 
 
2980
    def test_get_parents_lines(self):
 
2981
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2982
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2983
        tree = self.make_branch_and_tree('tree')
 
2984
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2985
        tree.add('file', 'file-id')
 
2986
        tt = self.get_preview(tree)
 
2987
        trans_id = tt.trans_id_tree_path('file')
 
2988
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
2989
            tt._get_parents_lines(trans_id))
 
2990
 
 
2991
    def test_get_parents_texts(self):
 
2992
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2993
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2994
        tree = self.make_branch_and_tree('tree')
 
2995
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2996
        tree.add('file', 'file-id')
 
2997
        tt = self.get_preview(tree)
 
2998
        trans_id = tt.trans_id_tree_path('file')
 
2999
        self.assertEqual((LINES_ONE,),
 
3000
            tt._get_parents_texts(trans_id))