~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: John Arbash Meinel
  • Date: 2008-09-09 15:09:12 UTC
  • mto: This revision was merged to the branch mainline in revision 3699.
  • Revision ID: john@arbash-meinel.com-20080909150912-wyttm8he1zsls2ck
Use the right timing function on win32

Show diffs side-by-side

added added

removed removed

Lines of Context:
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
18
18
import stat
20
20
import sys
21
21
 
22
22
from bzrlib import (
23
 
    bencode,
24
23
    errors,
25
24
    generate_ids,
26
25
    osutils,
36
35
                              NonDirectoryParent)
37
36
from bzrlib.diff import show_diff_trees
38
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
39
 
                           ReusingTransform, CantMoveRoot,
 
38
                           ReusingTransform, CantMoveRoot, 
40
39
                           PathsNotVersionedError, ExistingLimbo,
41
40
                           ExistingPendingDeletion, ImmortalLimbo,
42
41
                           ImmortalPendingDeletion, LockError)
43
42
from bzrlib.osutils import file_kind, pathjoin
44
 
from bzrlib.merge import Merge3Merger, Merger
 
43
from bzrlib.merge import Merge3Merger
45
44
from bzrlib.tests import (
 
45
    CaseInsensitiveFilesystemFeature,
46
46
    HardlinkFeature,
47
47
    SymlinkFeature,
48
48
    TestCase,
49
49
    TestCaseInTempDir,
50
50
    TestSkipped,
51
51
    )
52
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
53
 
                              resolve_conflicts, cook_conflicts,
 
52
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
 
53
                              resolve_conflicts, cook_conflicts, 
54
54
                              build_tree, get_backup_name,
55
55
                              _FileMover, resolve_checkout,
56
 
                              TransformPreview, create_from_tree)
57
 
 
 
56
                              TransformPreview)
58
57
 
59
58
class TestTreeTransform(tests.TestCaseWithTransport):
60
59
 
129
128
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
130
129
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
131
130
        self.assertEqual(len(modified_paths), 3)
132
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in
 
131
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
133
132
                          ('ozzie', 'my_pretties', 'my_pretties2')]
134
133
        self.assertSubset(tree_mod_paths, modified_paths)
135
134
        # is it safe to finalize repeatedly?
155
154
        transform, root = self.get_transform()
156
155
        self.wt.lock_tree_write()
157
156
        self.addCleanup(self.wt.unlock)
158
 
        trans_id = transform.new_file('name', root, 'contents',
 
157
        trans_id = transform.new_file('name', root, 'contents', 
159
158
                                      'my_pretties', True)
160
159
        oz = transform.new_directory('oz', root, 'oz-id')
161
160
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
162
 
        toto = transform.new_file('toto', dorothy, 'toto-contents',
 
161
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
163
162
                                  'toto-id', False)
164
163
 
165
164
        self.assertEqual(len(transform.find_conflicts()), 0)
189
188
 
190
189
    def test_conflicts(self):
191
190
        transform, root = self.get_transform()
192
 
        trans_id = transform.new_file('name', root, 'contents',
 
191
        trans_id = transform.new_file('name', root, 'contents', 
193
192
                                      'my_pretties')
194
193
        self.assertEqual(len(transform.find_conflicts()), 0)
195
194
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
196
 
        self.assertEqual(transform.find_conflicts(),
 
195
        self.assertEqual(transform.find_conflicts(), 
197
196
                         [('duplicate', trans_id, trans_id2, 'name')])
198
197
        self.assertRaises(MalformedTransform, transform.apply)
199
198
        transform.adjust_path('name', trans_id, trans_id2)
200
 
        self.assertEqual(transform.find_conflicts(),
 
199
        self.assertEqual(transform.find_conflicts(), 
201
200
                         [('non-directory parent', trans_id)])
202
201
        tinman_id = transform.trans_id_tree_path('tinman')
203
202
        transform.adjust_path('name', tinman_id, trans_id2)
204
 
        self.assertEqual(transform.find_conflicts(),
205
 
                         [('unversioned parent', tinman_id),
 
203
        self.assertEqual(transform.find_conflicts(), 
 
204
                         [('unversioned parent', tinman_id), 
206
205
                          ('missing parent', tinman_id)])
207
206
        lion_id = transform.create_path('lion', root)
208
 
        self.assertEqual(transform.find_conflicts(),
209
 
                         [('unversioned parent', tinman_id),
 
207
        self.assertEqual(transform.find_conflicts(), 
 
208
                         [('unversioned parent', tinman_id), 
210
209
                          ('missing parent', tinman_id)])
211
210
        transform.adjust_path('name', lion_id, trans_id2)
212
 
        self.assertEqual(transform.find_conflicts(),
 
211
        self.assertEqual(transform.find_conflicts(), 
213
212
                         [('unversioned parent', lion_id),
214
213
                          ('missing parent', lion_id)])
215
214
        transform.version_file("Courage", lion_id)
216
 
        self.assertEqual(transform.find_conflicts(),
217
 
                         [('missing parent', lion_id),
 
215
        self.assertEqual(transform.find_conflicts(), 
 
216
                         [('missing parent', lion_id), 
218
217
                          ('versioning no contents', lion_id)])
219
218
        transform.adjust_path('name2', root, trans_id2)
220
 
        self.assertEqual(transform.find_conflicts(),
 
219
        self.assertEqual(transform.find_conflicts(), 
221
220
                         [('versioning no contents', lion_id)])
222
221
        transform.create_file('Contents, okay?', lion_id)
223
222
        transform.adjust_path('name2', trans_id2, trans_id2)
224
 
        self.assertEqual(transform.find_conflicts(),
225
 
                         [('parent loop', trans_id2),
 
223
        self.assertEqual(transform.find_conflicts(), 
 
224
                         [('parent loop', trans_id2), 
226
225
                          ('non-directory parent', trans_id2)])
227
226
        transform.adjust_path('name2', root, trans_id2)
228
227
        oz_id = transform.new_directory('oz', root)
229
228
        transform.set_executability(True, oz_id)
230
 
        self.assertEqual(transform.find_conflicts(),
 
229
        self.assertEqual(transform.find_conflicts(), 
231
230
                         [('unversioned executability', oz_id)])
232
231
        transform.version_file('oz-id', oz_id)
233
 
        self.assertEqual(transform.find_conflicts(),
 
232
        self.assertEqual(transform.find_conflicts(), 
234
233
                         [('non-file executability', oz_id)])
235
234
        transform.set_executability(None, oz_id)
236
235
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
245
244
        self.assert_('oz/tip' in transform2._tree_path_ids)
246
245
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
247
246
        self.assertEqual(len(result), 2)
248
 
        self.assertEqual((result[0][0], result[0][1]),
 
247
        self.assertEqual((result[0][0], result[0][1]), 
249
248
                         ('duplicate', newtip))
250
 
        self.assertEqual((result[1][0], result[1][2]),
 
249
        self.assertEqual((result[1][0], result[1][2]), 
251
250
                         ('duplicate id', newtip))
252
251
        transform2.finalize()
253
252
        transform3 = TreeTransform(self.wt)
254
253
        self.addCleanup(transform3.finalize)
255
254
        oz_id = transform3.trans_id_tree_file_id('oz-id')
256
255
        transform3.delete_contents(oz_id)
257
 
        self.assertEqual(transform3.find_conflicts(),
 
256
        self.assertEqual(transform3.find_conflicts(), 
258
257
                         [('missing parent', oz_id)])
259
258
        root_id = transform3.root
260
259
        tip_id = transform3.trans_id_tree_file_id('tip-id')
387
386
        self.addCleanup(unversion.finalize)
388
387
        parent = unversion.trans_id_tree_path('parent')
389
388
        unversion.unversion_file(parent)
390
 
        self.assertEqual(unversion.find_conflicts(),
 
389
        self.assertEqual(unversion.find_conflicts(), 
391
390
                         [('unversioned parent', parent_id)])
392
391
        file_id = unversion.trans_id_tree_file_id('child-id')
393
392
        unversion.unversion_file(file_id)
413
412
        mangle_tree.adjust_path('name2', root, name1)
414
413
        mangle_tree.adjust_path('name1', root, name2)
415
414
 
416
 
        #tests for deleting parent directories
 
415
        #tests for deleting parent directories 
417
416
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
418
417
        mangle_tree.delete_contents(ddir)
419
418
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
448
447
        create_tree,root = self.get_transform()
449
448
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
450
449
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
451
 
        create_tree.apply()
 
450
        create_tree.apply()        
452
451
        mangle_tree,root = self.get_transform()
453
452
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
454
453
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
462
461
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
463
462
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
464
463
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
465
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
 
464
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
466
465
                             'test_too_much-id')
467
 
        create_tree.apply()
 
466
        create_tree.apply()        
468
467
        mangle_tree,root = self.get_transform()
469
468
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
470
469
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
471
470
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
472
471
        mangle_tree.adjust_path('selftest', bzrlib, tests)
473
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
 
472
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
474
473
        mangle_tree.set_executability(True, test_too_much)
475
474
        mangle_tree.apply()
476
475
 
477
476
    def test_both_rename3(self):
478
477
        create_tree,root = self.get_transform()
479
478
        tests = create_tree.new_directory('tests', root, 'tests-id')
480
 
        create_tree.new_file('test_too_much.py', tests, 'hello1',
 
479
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
481
480
                             'test_too_much-id')
482
 
        create_tree.apply()
 
481
        create_tree.apply()        
483
482
        mangle_tree,root = self.get_transform()
484
483
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
485
484
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
486
485
        mangle_tree.adjust_path('selftest', root, tests)
487
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
 
486
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
488
487
        mangle_tree.set_executability(True, test_too_much)
489
488
        mangle_tree.apply()
490
489
 
503
502
        newdir = move_id.new_directory('dir', root, 'newdir')
504
503
        move_id.adjust_path('name2', newdir, name1)
505
504
        move_id.apply()
506
 
 
 
505
        
507
506
    def test_replace_dangling_ie(self):
508
507
        create_tree, root = self.get_transform()
509
508
        # prepare tree
525
524
        resolve_conflicts(replace)
526
525
        replace.apply()
527
526
 
528
 
    def _test_symlinks(self, link_name1,link_target1,
529
 
                       link_name2, link_target2):
530
 
 
531
 
        def ozpath(p): return 'oz/' + p
532
 
 
 
527
    def test_symlinks(self):
533
528
        self.requireFeature(SymlinkFeature)
534
 
        transform, root = self.get_transform()
 
529
        transform,root = self.get_transform()
535
530
        oz_id = transform.new_directory('oz', root, 'oz-id')
536
 
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
 
531
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
537
532
                                       'wizard-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)
 
533
        wiz_id = transform.create_path('wizard2', oz_id)
 
534
        transform.create_symlink('behind_curtain', wiz_id)
 
535
        transform.version_file('wiz-id2', wiz_id)            
541
536
        transform.set_executability(True, wiz_id)
542
 
        self.assertEqual(transform.find_conflicts(),
 
537
        self.assertEqual(transform.find_conflicts(), 
543
538
                         [('non-file executability', wiz_id)])
544
539
        transform.set_executability(None, wiz_id)
545
540
        transform.apply()
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')
 
541
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
 
542
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
 
543
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
 
544
                         'behind_curtain')
 
545
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
 
546
                         'wizard-target')
564
547
 
565
548
    def test_unable_create_symlink(self):
566
549
        def tt_helper():
590
573
        create.apply()
591
574
        conflicts,root = self.get_transform()
592
575
        # set up duplicate entry, duplicate id
593
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
 
576
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
594
577
                                         'dorothy-id')
595
578
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
596
579
        oz = conflicts.trans_id_tree_file_id('oz-id')
620
603
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
621
604
        raw_conflicts = resolve_conflicts(tt)
622
605
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
623
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
 
606
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
624
607
                                   'dorothy', None, 'dorothy-id')
625
608
        self.assertEqual(cooked_conflicts[0], duplicate)
626
 
        duplicate_id = DuplicateID('Unversioned existing file',
 
609
        duplicate_id = DuplicateID('Unversioned existing file', 
627
610
                                   'dorothy.moved', 'dorothy', None,
628
611
                                   'dorothy-id')
629
612
        self.assertEqual(cooked_conflicts[1], duplicate_id)
637
620
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
638
621
                                               'oz-id')
639
622
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
640
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
 
623
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
641
624
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
642
625
        self.assertEqual(cooked_conflicts[4], deleted_parent)
643
626
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
766
749
 
767
750
    def test_set_executability_order(self):
768
751
        """Ensure that executability behaves the same, no matter what order.
769
 
 
 
752
        
770
753
        - create file and set executability simultaneously
771
754
        - create file and set executability afterward
772
755
        - unsetting the executability of a file whose executability has not been
1179
1162
        transform.cancel_creation(parent)
1180
1163
        transform.finalize()
1181
1164
 
1182
 
    def test_rollback_on_directory_clash(self):
 
1165
    def test_case_insensitive_clash(self):
 
1166
        self.requireFeature(CaseInsensitiveFilesystemFeature)
1183
1167
        def tt_helper():
1184
1168
            wt = self.make_branch_and_tree('.')
1185
1169
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1186
1170
            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)
 
1171
                tt.new_file('foo', tt.root, 'bar')
 
1172
                tt.new_file('Foo', tt.root, 'spam')
1193
1173
                # Lie to tt that we've already resolved all conflicts.
1194
1174
                tt.apply(no_conflicts=True)
1195
1175
            except:
1196
1176
                wt.unlock()
1197
1177
                raise
1198
 
        # The rename will fail because the target directory is not empty (but
1199
 
        # raises FileExists anyway).
1200
1178
        err = self.assertRaises(errors.FileExists, tt_helper)
1201
1179
        self.assertContainsRe(str(err),
1202
 
            "^File exists: .+/baz")
 
1180
            "^File exists: .+/foo")
1203
1181
 
1204
1182
    def test_two_directories_clash(self):
1205
1183
        def tt_helper():
1208
1186
            try:
1209
1187
                foo_1 = tt.new_directory('foo', tt.root)
1210
1188
                tt.new_directory('bar', foo_1)
1211
 
                # Adding the same directory with a different content
1212
1189
                foo_2 = tt.new_directory('foo', tt.root)
1213
1190
                tt.new_directory('baz', foo_2)
1214
1191
                # Lie to tt that we've already resolved all conflicts.
1227
1204
            try:
1228
1205
                foo_1 = tt.new_directory('foo', tt.root)
1229
1206
                tt.new_directory('bar', foo_1)
1230
 
                # Adding the same directory with a different content
1231
1207
                foo_2 = tt.new_directory('foo', tt.root)
1232
1208
                tt.new_directory('baz', foo_2)
1233
1209
                # Lie to tt that we've already resolved all conflicts.
1330
1306
        transform.cancel_creation(trans_id)
1331
1307
        transform.apply()
1332
1308
 
1333
 
    def test_create_from_tree(self):
1334
 
        tree1 = self.make_branch_and_tree('tree1')
1335
 
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1336
 
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1337
 
        tree2 = self.make_branch_and_tree('tree2')
1338
 
        tt = TreeTransform(tree2)
1339
 
        foo_trans_id = tt.create_path('foo', tt.root)
1340
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1341
 
        bar_trans_id = tt.create_path('bar', tt.root)
1342
 
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1343
 
        tt.apply()
1344
 
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1345
 
        self.assertFileEqual('baz', 'tree2/bar')
1346
 
 
1347
 
    def test_create_from_tree_bytes(self):
1348
 
        """Provided lines are used instead of tree content."""
1349
 
        tree1 = self.make_branch_and_tree('tree1')
1350
 
        self.build_tree_contents([('tree1/foo', 'bar'),])
1351
 
        tree1.add('foo', 'foo-id')
1352
 
        tree2 = self.make_branch_and_tree('tree2')
1353
 
        tt = TreeTransform(tree2)
1354
 
        foo_trans_id = tt.create_path('foo', tt.root)
1355
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1356
 
        tt.apply()
1357
 
        self.assertFileEqual('qux', 'tree2/foo')
1358
 
 
1359
 
    def test_create_from_tree_symlink(self):
1360
 
        self.requireFeature(SymlinkFeature)
1361
 
        tree1 = self.make_branch_and_tree('tree1')
1362
 
        os.symlink('bar', 'tree1/foo')
1363
 
        tree1.add('foo', 'foo-id')
1364
 
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
1365
 
        foo_trans_id = tt.create_path('foo', tt.root)
1366
 
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1367
 
        tt.apply()
1368
 
        self.assertEqual('bar', os.readlink('tree2/foo'))
1369
 
 
1370
1309
 
1371
1310
class TransformGroup(object):
1372
1311
 
1426
1365
        # textual merge
1427
1366
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1428
1367
        # three-way text conflict
1429
 
        self.assertEqual(this.wt.get_file('b').read(),
 
1368
        self.assertEqual(this.wt.get_file('b').read(), 
1430
1369
                         conflict_text('b', 'b2'))
1431
1370
        # OTHER wins
1432
1371
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1436
1375
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1437
1376
        # No change
1438
1377
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1439
 
        # Correct correct results when THIS == OTHER
 
1378
        # Correct correct results when THIS == OTHER 
1440
1379
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1441
1380
        # Text conflict when THIS & OTHER are text and BASE is dir
1442
 
        self.assertEqual(this.wt.get_file('h').read(),
 
1381
        self.assertEqual(this.wt.get_file('h').read(), 
1443
1382
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1444
1383
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1445
1384
                         '1\n2\n3\n4\n')
1446
1385
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1447
1386
                         'h\ni\nj\nk\n')
1448
1387
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1449
 
        self.assertEqual(this.wt.get_file('i').read(),
 
1388
        self.assertEqual(this.wt.get_file('i').read(), 
1450
1389
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1451
1390
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1452
1391
                         '1\n2\n3\n4\n')
1476
1415
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1477
1416
            tg.tt.new_file('c', tg.root, 'c', 'c')
1478
1417
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1479
 
        targets = ((base, 'base-e', 'base-f', None, None),
1480
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
 
1418
        targets = ((base, 'base-e', 'base-f', None, None), 
 
1419
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
1481
1420
                   (other, 'other-e', None, 'other-g', 'other-h'))
1482
1421
        for tg, e_target, f_target, g_target, h_target in targets:
1483
 
            for link, target in (('e', e_target), ('f', f_target),
 
1422
            for link, target in (('e', e_target), ('f', f_target), 
1484
1423
                                 ('g', g_target), ('h', h_target)):
1485
1424
                if target is not None:
1486
1425
                    tg.tt.new_symlink(link, tg.root, target, link)
1512
1451
        base = TransformGroup("BASE", root_id)
1513
1452
        this = TransformGroup("THIS", root_id)
1514
1453
        other = TransformGroup("OTHER", root_id)
1515
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
 
1454
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1516
1455
                                   for t in [base, this, other]]
1517
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
 
1456
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1518
1457
                                   for t in [base, this, other]]
1519
1458
        base.tt.new_directory('c', base_a, 'c')
1520
1459
        this.tt.new_directory('c1', this_a, 'c')
1545
1484
        base = TransformGroup("BASE", root_id)
1546
1485
        this = TransformGroup("THIS", root_id)
1547
1486
        other = TransformGroup("OTHER", root_id)
1548
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
 
1487
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1549
1488
                                   for t in [base, this, other]]
1550
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
 
1489
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1551
1490
                                   for t in [base, this, other]]
1552
1491
 
1553
1492
        base.tt.new_file('g', base_a, 'g', 'g')
1644
1583
        os.symlink('foo', 'target2/symlink')
1645
1584
        build_tree(source.basis_tree(), target)
1646
1585
        self.assertEqual([], target.conflicts())
1647
 
 
 
1586
        
1648
1587
    def test_directory_conflict_handling(self):
1649
1588
        """Ensure that when building trees, conflict handling is done"""
1650
1589
        source = self.make_branch_and_tree('source')
1706
1645
        target = self.make_branch_and_tree('target')
1707
1646
        self.build_tree(['target/name'])
1708
1647
        target.add('name')
1709
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
 
1648
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
1710
1649
            build_tree, source.basis_tree(), target)
1711
1650
 
1712
1651
    def test_build_tree_rename_count(self):
1857
1796
        self.assertTrue(source.is_executable('file1-id'))
1858
1797
 
1859
1798
    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
1799
        source = self.make_branch_and_tree('source')
1864
1800
        self.build_tree(['source/file', 'source/FILE'])
1865
1801
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1873
1809
        self.assertEqual('FILE', target.id2path('upper-id'))
1874
1810
 
1875
1811
 
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
 
 
1976
 
 
1977
1812
class MockTransform(object):
1978
1813
 
1979
1814
    def has_named_child(self, by_parent, parent_id, name):
2134
1969
        resolve_conflicts(tt)
2135
1970
 
2136
1971
 
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
1972
class TestTransformPreview(tests.TestCaseWithTransport):
2145
1973
 
2146
1974
    def create_tree(self):
2147
1975
        tree = self.make_branch_and_tree('.')
2148
1976
        self.build_tree_contents([('a', 'content 1')])
2149
 
        tree.set_root_id('TREE_ROOT')
2150
1977
        tree.add('a', 'a-id')
2151
1978
        tree.commit('rev1', rev_id='rev1')
2152
1979
        return tree.branch.repository.revision_tree('rev1')
2219
2046
                          (False, False))],
2220
2047
                          list(preview_tree.iter_changes(revision_tree)))
2221
2048
 
2222
 
    def test_include_unchanged_succeeds(self):
 
2049
    def test_wrong_tree_value_error(self):
2223
2050
        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
 
2051
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2052
                              preview_tree)
 
2053
        self.assertEqual('from_tree must be transform source tree.', str(e))
2227
2054
 
2228
 
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2055
    def test_include_unchanged_value_error(self):
 
2056
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2057
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2058
                              revision_tree, include_unchanged=True)
 
2059
        self.assertEqual('include_unchanged is not supported', str(e))
2229
2060
 
2230
2061
    def test_specific_files(self):
2231
2062
        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))
 
2063
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2064
                              revision_tree, specific_files=['pete'])
 
2065
        self.assertEqual('specific_files is not supported', str(e))
2235
2066
 
2236
 
    def test_want_unversioned(self):
 
2067
    def test_want_unversioned_value_error(self):
2237
2068
        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))
 
2069
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
 
2070
                              revision_tree, want_unversioned=True)
 
2071
        self.assertEqual('want_unversioned is not supported', str(e))
2241
2072
 
2242
2073
    def test_ignore_extra_trees_no_specific_files(self):
2243
2074
        # extra_trees is harmless without specific_files, so we'll silently
2621
2452
        expected = [(('', 'tree-root'),
2622
2453
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
2623
2454
        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))