~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Danny van Heumen
  • Date: 2010-03-09 21:42:11 UTC
  • mto: (4634.139.5 2.0)
  • mto: This revision was merged to the branch mainline in revision 5160.
  • Revision ID: danny@dannyvanheumen.nl-20100309214211-iqh42x6qcikgd9p3
Reverted now-useless TODO list.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
20
20
import sys
21
21
 
22
22
from bzrlib import (
 
23
    bencode,
23
24
    errors,
24
25
    generate_ids,
25
26
    osutils,
35
36
                              NonDirectoryParent)
36
37
from bzrlib.diff import show_diff_trees
37
38
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
 
                           ReusingTransform, CantMoveRoot, 
 
39
                           ReusingTransform, CantMoveRoot,
39
40
                           PathsNotVersionedError, ExistingLimbo,
40
41
                           ExistingPendingDeletion, ImmortalLimbo,
41
42
                           ImmortalPendingDeletion, LockError)
42
43
from bzrlib.osutils import file_kind, pathjoin
43
 
from bzrlib.merge import Merge3Merger
 
44
from bzrlib.merge import Merge3Merger, Merger
44
45
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)
 
56
                              TransformPreview, create_from_tree)
 
57
 
57
58
 
58
59
class TestTreeTransform(tests.TestCaseWithTransport):
59
60
 
128
129
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
129
130
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
130
131
        self.assertEqual(len(modified_paths), 3)
131
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
132
        tree_mod_paths = [self.wt.id2abspath(f) for f in
132
133
                          ('ozzie', 'my_pretties', 'my_pretties2')]
133
134
        self.assertSubset(tree_mod_paths, modified_paths)
134
135
        # is it safe to finalize repeatedly?
135
136
        transform.finalize()
136
137
        transform.finalize()
137
138
 
 
139
    def test_change_root_id(self):
 
140
        transform, root = self.get_transform()
 
141
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
142
        transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
143
        transform.delete_contents(root)
 
144
        transform.unversion_file(root)
 
145
        transform.fixup_new_roots()
 
146
        transform.apply()
 
147
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
148
 
 
149
    def test_change_root_id_add_files(self):
 
150
        transform, root = self.get_transform()
 
151
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
152
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
153
        transform.new_file('file', new_trans_id, ['new-contents\n'],
 
154
                           'new-file-id')
 
155
        transform.delete_contents(root)
 
156
        transform.unversion_file(root)
 
157
        transform.fixup_new_roots()
 
158
        transform.apply()
 
159
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
160
        self.assertEqual('new-file-id', self.wt.path2id('file'))
 
161
        self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
 
162
 
 
163
    def test_add_two_roots(self):
 
164
        transform, root = self.get_transform()
 
165
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
166
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
 
167
        self.assertRaises(ValueError, transform.fixup_new_roots)
 
168
 
138
169
    def test_hardlink(self):
139
170
        self.requireFeature(HardlinkFeature)
140
171
        transform, root = self.get_transform()
154
185
        transform, root = self.get_transform()
155
186
        self.wt.lock_tree_write()
156
187
        self.addCleanup(self.wt.unlock)
157
 
        trans_id = transform.new_file('name', root, 'contents', 
 
188
        trans_id = transform.new_file('name', root, 'contents',
158
189
                                      'my_pretties', True)
159
190
        oz = transform.new_directory('oz', root, 'oz-id')
160
191
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
161
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
192
        toto = transform.new_file('toto', dorothy, 'toto-contents',
162
193
                                  'toto-id', False)
163
194
 
164
195
        self.assertEqual(len(transform.find_conflicts()), 0)
188
219
 
189
220
    def test_conflicts(self):
190
221
        transform, root = self.get_transform()
191
 
        trans_id = transform.new_file('name', root, 'contents', 
 
222
        trans_id = transform.new_file('name', root, 'contents',
192
223
                                      'my_pretties')
193
224
        self.assertEqual(len(transform.find_conflicts()), 0)
194
225
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
195
 
        self.assertEqual(transform.find_conflicts(), 
 
226
        self.assertEqual(transform.find_conflicts(),
196
227
                         [('duplicate', trans_id, trans_id2, 'name')])
197
228
        self.assertRaises(MalformedTransform, transform.apply)
198
229
        transform.adjust_path('name', trans_id, trans_id2)
199
 
        self.assertEqual(transform.find_conflicts(), 
 
230
        self.assertEqual(transform.find_conflicts(),
200
231
                         [('non-directory parent', trans_id)])
201
232
        tinman_id = transform.trans_id_tree_path('tinman')
202
233
        transform.adjust_path('name', tinman_id, trans_id2)
203
 
        self.assertEqual(transform.find_conflicts(), 
204
 
                         [('unversioned parent', tinman_id), 
 
234
        self.assertEqual(transform.find_conflicts(),
 
235
                         [('unversioned parent', tinman_id),
205
236
                          ('missing parent', tinman_id)])
206
237
        lion_id = transform.create_path('lion', root)
207
 
        self.assertEqual(transform.find_conflicts(), 
208
 
                         [('unversioned parent', tinman_id), 
 
238
        self.assertEqual(transform.find_conflicts(),
 
239
                         [('unversioned parent', tinman_id),
209
240
                          ('missing parent', tinman_id)])
210
241
        transform.adjust_path('name', lion_id, trans_id2)
211
 
        self.assertEqual(transform.find_conflicts(), 
 
242
        self.assertEqual(transform.find_conflicts(),
212
243
                         [('unversioned parent', lion_id),
213
244
                          ('missing parent', lion_id)])
214
245
        transform.version_file("Courage", lion_id)
215
 
        self.assertEqual(transform.find_conflicts(), 
216
 
                         [('missing parent', lion_id), 
 
246
        self.assertEqual(transform.find_conflicts(),
 
247
                         [('missing parent', lion_id),
217
248
                          ('versioning no contents', lion_id)])
218
249
        transform.adjust_path('name2', root, trans_id2)
219
 
        self.assertEqual(transform.find_conflicts(), 
 
250
        self.assertEqual(transform.find_conflicts(),
220
251
                         [('versioning no contents', lion_id)])
221
252
        transform.create_file('Contents, okay?', lion_id)
222
253
        transform.adjust_path('name2', trans_id2, trans_id2)
223
 
        self.assertEqual(transform.find_conflicts(), 
224
 
                         [('parent loop', trans_id2), 
 
254
        self.assertEqual(transform.find_conflicts(),
 
255
                         [('parent loop', trans_id2),
225
256
                          ('non-directory parent', trans_id2)])
226
257
        transform.adjust_path('name2', root, trans_id2)
227
258
        oz_id = transform.new_directory('oz', root)
228
259
        transform.set_executability(True, oz_id)
229
 
        self.assertEqual(transform.find_conflicts(), 
 
260
        self.assertEqual(transform.find_conflicts(),
230
261
                         [('unversioned executability', oz_id)])
231
262
        transform.version_file('oz-id', oz_id)
232
 
        self.assertEqual(transform.find_conflicts(), 
 
263
        self.assertEqual(transform.find_conflicts(),
233
264
                         [('non-file executability', oz_id)])
234
265
        transform.set_executability(None, oz_id)
235
266
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
244
275
        self.assert_('oz/tip' in transform2._tree_path_ids)
245
276
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
246
277
        self.assertEqual(len(result), 2)
247
 
        self.assertEqual((result[0][0], result[0][1]), 
 
278
        self.assertEqual((result[0][0], result[0][1]),
248
279
                         ('duplicate', newtip))
249
 
        self.assertEqual((result[1][0], result[1][2]), 
 
280
        self.assertEqual((result[1][0], result[1][2]),
250
281
                         ('duplicate id', newtip))
251
282
        transform2.finalize()
252
283
        transform3 = TreeTransform(self.wt)
253
284
        self.addCleanup(transform3.finalize)
254
285
        oz_id = transform3.trans_id_tree_file_id('oz-id')
255
286
        transform3.delete_contents(oz_id)
256
 
        self.assertEqual(transform3.find_conflicts(), 
 
287
        self.assertEqual(transform3.find_conflicts(),
257
288
                         [('missing parent', oz_id)])
258
289
        root_id = transform3.root
259
290
        tip_id = transform3.trans_id_tree_file_id('tip-id')
386
417
        self.addCleanup(unversion.finalize)
387
418
        parent = unversion.trans_id_tree_path('parent')
388
419
        unversion.unversion_file(parent)
389
 
        self.assertEqual(unversion.find_conflicts(), 
 
420
        self.assertEqual(unversion.find_conflicts(),
390
421
                         [('unversioned parent', parent_id)])
391
422
        file_id = unversion.trans_id_tree_file_id('child-id')
392
423
        unversion.unversion_file(file_id)
412
443
        mangle_tree.adjust_path('name2', root, name1)
413
444
        mangle_tree.adjust_path('name1', root, name2)
414
445
 
415
 
        #tests for deleting parent directories 
 
446
        #tests for deleting parent directories
416
447
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
417
448
        mangle_tree.delete_contents(ddir)
418
449
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
447
478
        create_tree,root = self.get_transform()
448
479
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
449
480
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
450
 
        create_tree.apply()        
 
481
        create_tree.apply()
451
482
        mangle_tree,root = self.get_transform()
452
483
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
453
484
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
461
492
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
462
493
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
463
494
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
464
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
495
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
465
496
                             'test_too_much-id')
466
 
        create_tree.apply()        
 
497
        create_tree.apply()
467
498
        mangle_tree,root = self.get_transform()
468
499
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
469
500
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
470
501
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
471
502
        mangle_tree.adjust_path('selftest', bzrlib, tests)
472
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
503
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
473
504
        mangle_tree.set_executability(True, test_too_much)
474
505
        mangle_tree.apply()
475
506
 
476
507
    def test_both_rename3(self):
477
508
        create_tree,root = self.get_transform()
478
509
        tests = create_tree.new_directory('tests', root, 'tests-id')
479
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
510
        create_tree.new_file('test_too_much.py', tests, 'hello1',
480
511
                             'test_too_much-id')
481
 
        create_tree.apply()        
 
512
        create_tree.apply()
482
513
        mangle_tree,root = self.get_transform()
483
514
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
484
515
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
485
516
        mangle_tree.adjust_path('selftest', root, tests)
486
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
517
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
487
518
        mangle_tree.set_executability(True, test_too_much)
488
519
        mangle_tree.apply()
489
520
 
502
533
        newdir = move_id.new_directory('dir', root, 'newdir')
503
534
        move_id.adjust_path('name2', newdir, name1)
504
535
        move_id.apply()
505
 
        
 
536
 
506
537
    def test_replace_dangling_ie(self):
507
538
        create_tree, root = self.get_transform()
508
539
        # prepare tree
524
555
        resolve_conflicts(replace)
525
556
        replace.apply()
526
557
 
527
 
    def test_symlinks(self):
 
558
    def _test_symlinks(self, link_name1,link_target1,
 
559
                       link_name2, link_target2):
 
560
 
 
561
        def ozpath(p): return 'oz/' + p
 
562
 
528
563
        self.requireFeature(SymlinkFeature)
529
 
        transform,root = self.get_transform()
 
564
        transform, root = self.get_transform()
530
565
        oz_id = transform.new_directory('oz', root, 'oz-id')
531
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
566
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
532
567
                                       'wizard-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)            
 
568
        wiz_id = transform.create_path(link_name2, oz_id)
 
569
        transform.create_symlink(link_target2, wiz_id)
 
570
        transform.version_file('wiz-id2', wiz_id)
536
571
        transform.set_executability(True, wiz_id)
537
 
        self.assertEqual(transform.find_conflicts(), 
 
572
        self.assertEqual(transform.find_conflicts(),
538
573
                         [('non-file executability', wiz_id)])
539
574
        transform.set_executability(None, wiz_id)
540
575
        transform.apply()
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')
 
576
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
 
577
        self.assertEqual('symlink',
 
578
                         file_kind(self.wt.abspath(ozpath(link_name1))))
 
579
        self.assertEqual(link_target2,
 
580
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
 
581
        self.assertEqual(link_target1,
 
582
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
 
583
 
 
584
    def test_symlinks(self):
 
585
        self._test_symlinks('wizard', 'wizard-target',
 
586
                            'wizard2', 'behind_curtain')
 
587
 
 
588
    def test_symlinks_unicode(self):
 
589
        self.requireFeature(tests.UnicodeFilenameFeature)
 
590
        self._test_symlinks(u'\N{Euro Sign}wizard',
 
591
                            u'wizard-targ\N{Euro Sign}t',
 
592
                            u'\N{Euro Sign}wizard2',
 
593
                            u'b\N{Euro Sign}hind_curtain')
547
594
 
548
595
    def test_unable_create_symlink(self):
549
596
        def tt_helper():
573
620
        create.apply()
574
621
        conflicts,root = self.get_transform()
575
622
        # set up duplicate entry, duplicate id
576
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
623
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
577
624
                                         'dorothy-id')
578
625
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
579
626
        oz = conflicts.trans_id_tree_file_id('oz-id')
603
650
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
604
651
        raw_conflicts = resolve_conflicts(tt)
605
652
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
606
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
653
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
607
654
                                   'dorothy', None, 'dorothy-id')
608
655
        self.assertEqual(cooked_conflicts[0], duplicate)
609
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
656
        duplicate_id = DuplicateID('Unversioned existing file',
610
657
                                   'dorothy.moved', 'dorothy', None,
611
658
                                   'dorothy-id')
612
659
        self.assertEqual(cooked_conflicts[1], duplicate_id)
620
667
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
621
668
                                               'oz-id')
622
669
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
623
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
670
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
624
671
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
625
672
        self.assertEqual(cooked_conflicts[4], deleted_parent)
626
673
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
729
776
        create.apply()
730
777
        transform, root = self.get_transform()
731
778
        transform.adjust_root_path('oldroot', fun)
732
 
        new_root=transform.trans_id_tree_path('')
 
779
        new_root = transform.trans_id_tree_path('')
733
780
        transform.version_file('new-root', new_root)
734
781
        transform.apply()
735
782
 
749
796
 
750
797
    def test_set_executability_order(self):
751
798
        """Ensure that executability behaves the same, no matter what order.
752
 
        
 
799
 
753
800
        - create file and set executability simultaneously
754
801
        - create file and set executability afterward
755
802
        - unsetting the executability of a file whose executability has not been
1162
1209
        transform.cancel_creation(parent)
1163
1210
        transform.finalize()
1164
1211
 
1165
 
    def test_case_insensitive_clash(self):
1166
 
        self.requireFeature(CaseInsensitiveFilesystemFeature)
 
1212
    def test_rollback_on_directory_clash(self):
1167
1213
        def tt_helper():
1168
1214
            wt = self.make_branch_and_tree('.')
1169
1215
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1170
1216
            try:
1171
 
                tt.new_file('foo', tt.root, 'bar')
1172
 
                tt.new_file('Foo', tt.root, 'spam')
 
1217
                foo = tt.new_directory('foo', tt.root)
 
1218
                tt.new_file('bar', foo, 'foobar')
 
1219
                baz = tt.new_directory('baz', tt.root)
 
1220
                tt.new_file('qux', baz, 'quux')
 
1221
                # Ask for a rename 'foo' -> 'baz'
 
1222
                tt.adjust_path('baz', tt.root, foo)
1173
1223
                # Lie to tt that we've already resolved all conflicts.
1174
1224
                tt.apply(no_conflicts=True)
1175
1225
            except:
1176
1226
                wt.unlock()
1177
1227
                raise
 
1228
        # The rename will fail because the target directory is not empty (but
 
1229
        # raises FileExists anyway).
1178
1230
        err = self.assertRaises(errors.FileExists, tt_helper)
1179
1231
        self.assertContainsRe(str(err),
1180
 
            "^File exists: .+/foo")
 
1232
            "^File exists: .+/baz")
1181
1233
 
1182
1234
    def test_two_directories_clash(self):
1183
1235
        def tt_helper():
1186
1238
            try:
1187
1239
                foo_1 = tt.new_directory('foo', tt.root)
1188
1240
                tt.new_directory('bar', foo_1)
 
1241
                # Adding the same directory with a different content
1189
1242
                foo_2 = tt.new_directory('foo', tt.root)
1190
1243
                tt.new_directory('baz', foo_2)
1191
1244
                # Lie to tt that we've already resolved all conflicts.
1204
1257
            try:
1205
1258
                foo_1 = tt.new_directory('foo', tt.root)
1206
1259
                tt.new_directory('bar', foo_1)
 
1260
                # Adding the same directory with a different content
1207
1261
                foo_2 = tt.new_directory('foo', tt.root)
1208
1262
                tt.new_directory('baz', foo_2)
1209
1263
                # Lie to tt that we've already resolved all conflicts.
1215
1269
        self.assertContainsRe(str(err),
1216
1270
            "^File exists: .+/foo")
1217
1271
 
 
1272
    def test_file_to_directory(self):
 
1273
        wt = self.make_branch_and_tree('.')
 
1274
        self.build_tree(['foo'])
 
1275
        wt.add(['foo'])
 
1276
        wt.commit("one")
 
1277
        tt = TreeTransform(wt)
 
1278
        self.addCleanup(tt.finalize)
 
1279
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1280
        tt.delete_contents(foo_trans_id)
 
1281
        tt.create_directory(foo_trans_id)
 
1282
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1283
        tt.create_file(["aa\n"], bar_trans_id)
 
1284
        tt.version_file("bar-1", bar_trans_id)
 
1285
        tt.apply()
 
1286
        self.failUnlessExists("foo/bar")
 
1287
        wt.lock_read()
 
1288
        try:
 
1289
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1290
                    "directory")
 
1291
        finally:
 
1292
            wt.unlock()
 
1293
        wt.commit("two")
 
1294
        changes = wt.changes_from(wt.basis_tree())
 
1295
        self.assertFalse(changes.has_changed(), changes)
 
1296
 
 
1297
    def test_file_to_symlink(self):
 
1298
        self.requireFeature(SymlinkFeature)
 
1299
        wt = self.make_branch_and_tree('.')
 
1300
        self.build_tree(['foo'])
 
1301
        wt.add(['foo'])
 
1302
        wt.commit("one")
 
1303
        tt = TreeTransform(wt)
 
1304
        self.addCleanup(tt.finalize)
 
1305
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1306
        tt.delete_contents(foo_trans_id)
 
1307
        tt.create_symlink("bar", foo_trans_id)
 
1308
        tt.apply()
 
1309
        self.failUnlessExists("foo")
 
1310
        wt.lock_read()
 
1311
        self.addCleanup(wt.unlock)
 
1312
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1313
                "symlink")
 
1314
 
 
1315
    def test_dir_to_file(self):
 
1316
        wt = self.make_branch_and_tree('.')
 
1317
        self.build_tree(['foo/', 'foo/bar'])
 
1318
        wt.add(['foo', 'foo/bar'])
 
1319
        wt.commit("one")
 
1320
        tt = TreeTransform(wt)
 
1321
        self.addCleanup(tt.finalize)
 
1322
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1323
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1324
        tt.delete_contents(foo_trans_id)
 
1325
        tt.delete_versioned(bar_trans_id)
 
1326
        tt.create_file(["aa\n"], foo_trans_id)
 
1327
        tt.apply()
 
1328
        self.failUnlessExists("foo")
 
1329
        wt.lock_read()
 
1330
        self.addCleanup(wt.unlock)
 
1331
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1332
                "file")
 
1333
 
 
1334
    def test_dir_to_hardlink(self):
 
1335
        self.requireFeature(HardlinkFeature)
 
1336
        wt = self.make_branch_and_tree('.')
 
1337
        self.build_tree(['foo/', 'foo/bar'])
 
1338
        wt.add(['foo', 'foo/bar'])
 
1339
        wt.commit("one")
 
1340
        tt = TreeTransform(wt)
 
1341
        self.addCleanup(tt.finalize)
 
1342
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1343
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1344
        tt.delete_contents(foo_trans_id)
 
1345
        tt.delete_versioned(bar_trans_id)
 
1346
        self.build_tree(['baz'])
 
1347
        tt.create_hardlink("baz", foo_trans_id)
 
1348
        tt.apply()
 
1349
        self.failUnlessExists("foo")
 
1350
        self.failUnlessExists("baz")
 
1351
        wt.lock_read()
 
1352
        self.addCleanup(wt.unlock)
 
1353
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1354
                "file")
 
1355
 
 
1356
    def test_no_final_path(self):
 
1357
        transform, root = self.get_transform()
 
1358
        trans_id = transform.trans_id_file_id('foo')
 
1359
        transform.create_file('bar', trans_id)
 
1360
        transform.cancel_creation(trans_id)
 
1361
        transform.apply()
 
1362
 
 
1363
    def test_create_from_tree(self):
 
1364
        tree1 = self.make_branch_and_tree('tree1')
 
1365
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
 
1366
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
 
1367
        tree2 = self.make_branch_and_tree('tree2')
 
1368
        tt = TreeTransform(tree2)
 
1369
        foo_trans_id = tt.create_path('foo', tt.root)
 
1370
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1371
        bar_trans_id = tt.create_path('bar', tt.root)
 
1372
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
 
1373
        tt.apply()
 
1374
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
 
1375
        self.assertFileEqual('baz', 'tree2/bar')
 
1376
 
 
1377
    def test_create_from_tree_bytes(self):
 
1378
        """Provided lines are used instead of tree content."""
 
1379
        tree1 = self.make_branch_and_tree('tree1')
 
1380
        self.build_tree_contents([('tree1/foo', 'bar'),])
 
1381
        tree1.add('foo', 'foo-id')
 
1382
        tree2 = self.make_branch_and_tree('tree2')
 
1383
        tt = TreeTransform(tree2)
 
1384
        foo_trans_id = tt.create_path('foo', tt.root)
 
1385
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
 
1386
        tt.apply()
 
1387
        self.assertFileEqual('qux', 'tree2/foo')
 
1388
 
 
1389
    def test_create_from_tree_symlink(self):
 
1390
        self.requireFeature(SymlinkFeature)
 
1391
        tree1 = self.make_branch_and_tree('tree1')
 
1392
        os.symlink('bar', 'tree1/foo')
 
1393
        tree1.add('foo', 'foo-id')
 
1394
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
 
1395
        foo_trans_id = tt.create_path('foo', tt.root)
 
1396
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1397
        tt.apply()
 
1398
        self.assertEqual('bar', os.readlink('tree2/foo'))
 
1399
 
1218
1400
 
1219
1401
class TransformGroup(object):
1220
1402
 
1274
1456
        # textual merge
1275
1457
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1276
1458
        # three-way text conflict
1277
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1459
        self.assertEqual(this.wt.get_file('b').read(),
1278
1460
                         conflict_text('b', 'b2'))
1279
1461
        # OTHER wins
1280
1462
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1284
1466
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1285
1467
        # No change
1286
1468
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1287
 
        # Correct correct results when THIS == OTHER 
 
1469
        # Correct correct results when THIS == OTHER
1288
1470
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1289
1471
        # Text conflict when THIS & OTHER are text and BASE is dir
1290
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1472
        self.assertEqual(this.wt.get_file('h').read(),
1291
1473
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1292
1474
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1293
1475
                         '1\n2\n3\n4\n')
1294
1476
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1295
1477
                         'h\ni\nj\nk\n')
1296
1478
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1297
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1479
        self.assertEqual(this.wt.get_file('i').read(),
1298
1480
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1299
1481
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1300
1482
                         '1\n2\n3\n4\n')
1324
1506
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1325
1507
            tg.tt.new_file('c', tg.root, 'c', 'c')
1326
1508
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1327
 
        targets = ((base, 'base-e', 'base-f', None, None), 
1328
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1509
        targets = ((base, 'base-e', 'base-f', None, None),
 
1510
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
1329
1511
                   (other, 'other-e', None, 'other-g', 'other-h'))
1330
1512
        for tg, e_target, f_target, g_target, h_target in targets:
1331
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1513
            for link, target in (('e', e_target), ('f', f_target),
1332
1514
                                 ('g', g_target), ('h', h_target)):
1333
1515
                if target is not None:
1334
1516
                    tg.tt.new_symlink(link, tg.root, target, link)
1360
1542
        base = TransformGroup("BASE", root_id)
1361
1543
        this = TransformGroup("THIS", root_id)
1362
1544
        other = TransformGroup("OTHER", root_id)
1363
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1545
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1364
1546
                                   for t in [base, this, other]]
1365
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1547
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1366
1548
                                   for t in [base, this, other]]
1367
1549
        base.tt.new_directory('c', base_a, 'c')
1368
1550
        this.tt.new_directory('c1', this_a, 'c')
1393
1575
        base = TransformGroup("BASE", root_id)
1394
1576
        this = TransformGroup("THIS", root_id)
1395
1577
        other = TransformGroup("OTHER", root_id)
1396
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1578
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1397
1579
                                   for t in [base, this, other]]
1398
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1580
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1399
1581
                                   for t in [base, this, other]]
1400
1582
 
1401
1583
        base.tt.new_file('g', base_a, 'g', 'g')
1492
1674
        os.symlink('foo', 'target2/symlink')
1493
1675
        build_tree(source.basis_tree(), target)
1494
1676
        self.assertEqual([], target.conflicts())
1495
 
        
 
1677
 
1496
1678
    def test_directory_conflict_handling(self):
1497
1679
        """Ensure that when building trees, conflict handling is done"""
1498
1680
        source = self.make_branch_and_tree('source')
1554
1736
        target = self.make_branch_and_tree('target')
1555
1737
        self.build_tree(['target/name'])
1556
1738
        target.add('name')
1557
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1739
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1558
1740
            build_tree, source.basis_tree(), target)
1559
1741
 
1560
1742
    def test_build_tree_rename_count(self):
1705
1887
        self.assertTrue(source.is_executable('file1-id'))
1706
1888
 
1707
1889
    def test_case_insensitive_build_tree_inventory(self):
 
1890
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1891
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1892
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1708
1893
        source = self.make_branch_and_tree('source')
1709
1894
        self.build_tree(['source/file', 'source/FILE'])
1710
1895
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1718
1903
        self.assertEqual('FILE', target.id2path('upper-id'))
1719
1904
 
1720
1905
 
 
1906
class TestCommitTransform(tests.TestCaseWithTransport):
 
1907
 
 
1908
    def get_branch(self):
 
1909
        tree = self.make_branch_and_tree('tree')
 
1910
        tree.lock_write()
 
1911
        self.addCleanup(tree.unlock)
 
1912
        tree.commit('empty commit')
 
1913
        return tree.branch
 
1914
 
 
1915
    def get_branch_and_transform(self):
 
1916
        branch = self.get_branch()
 
1917
        tt = TransformPreview(branch.basis_tree())
 
1918
        self.addCleanup(tt.finalize)
 
1919
        return branch, tt
 
1920
 
 
1921
    def test_commit_wrong_basis(self):
 
1922
        branch = self.get_branch()
 
1923
        basis = branch.repository.revision_tree(
 
1924
            _mod_revision.NULL_REVISION)
 
1925
        tt = TransformPreview(basis)
 
1926
        self.addCleanup(tt.finalize)
 
1927
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
1928
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
1929
                         str(e))
 
1930
 
 
1931
    def test_empy_commit(self):
 
1932
        branch, tt = self.get_branch_and_transform()
 
1933
        rev = tt.commit(branch, 'my message')
 
1934
        self.assertEqual(2, branch.revno())
 
1935
        repo = branch.repository
 
1936
        self.assertEqual('my message', repo.get_revision(rev).message)
 
1937
 
 
1938
    def test_merge_parents(self):
 
1939
        branch, tt = self.get_branch_and_transform()
 
1940
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
1941
        self.assertEqual(['rev1b', 'rev1c'],
 
1942
                         branch.basis_tree().get_parent_ids()[1:])
 
1943
 
 
1944
    def test_first_commit(self):
 
1945
        branch = self.make_branch('branch')
 
1946
        branch.lock_write()
 
1947
        self.addCleanup(branch.unlock)
 
1948
        tt = TransformPreview(branch.basis_tree())
 
1949
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
1950
        rev = tt.commit(branch, 'my message')
 
1951
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
1952
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
1953
                            branch.last_revision())
 
1954
 
 
1955
    def test_first_commit_with_merge_parents(self):
 
1956
        branch = self.make_branch('branch')
 
1957
        branch.lock_write()
 
1958
        self.addCleanup(branch.unlock)
 
1959
        tt = TransformPreview(branch.basis_tree())
 
1960
        e = self.assertRaises(ValueError, tt.commit, branch,
 
1961
                          'my message', ['rev1b-id'])
 
1962
        self.assertEqual('Cannot supply merge parents for first commit.',
 
1963
                         str(e))
 
1964
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
1965
 
 
1966
    def test_add_files(self):
 
1967
        branch, tt = self.get_branch_and_transform()
 
1968
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
1969
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
1970
        tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
1971
        rev = tt.commit(branch, 'message')
 
1972
        tree = branch.basis_tree()
 
1973
        self.assertEqual('file', tree.id2path('file-id'))
 
1974
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
1975
        self.assertEqual('dir', tree.id2path('dir-id'))
 
1976
        self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
1977
        self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
1978
 
 
1979
    def test_add_unversioned(self):
 
1980
        branch, tt = self.get_branch_and_transform()
 
1981
        tt.new_file('file', tt.root, 'contents')
 
1982
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
1983
                          'message', strict=True)
 
1984
 
 
1985
    def test_modify_strict(self):
 
1986
        branch, tt = self.get_branch_and_transform()
 
1987
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
1988
        tt.commit(branch, 'message', strict=True)
 
1989
        tt = TransformPreview(branch.basis_tree())
 
1990
        trans_id = tt.trans_id_file_id('file-id')
 
1991
        tt.delete_contents(trans_id)
 
1992
        tt.create_file('contents', trans_id)
 
1993
        tt.commit(branch, 'message', strict=True)
 
1994
 
 
1995
    def test_commit_malformed(self):
 
1996
        """Committing a malformed transform should raise an exception.
 
1997
 
 
1998
        In this case, we are adding a file without adding its parent.
 
1999
        """
 
2000
        branch, tt = self.get_branch_and_transform()
 
2001
        parent_id = tt.trans_id_file_id('parent-id')
 
2002
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2003
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2004
                          'message')
 
2005
 
 
2006
 
1721
2007
class MockTransform(object):
1722
2008
 
1723
2009
    def has_named_child(self, by_parent, parent_id, name):
1878
2164
        resolve_conflicts(tt)
1879
2165
 
1880
2166
 
 
2167
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2168
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2169
                  (False, False))
 
2170
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2171
              ('', ''), ('directory', 'directory'), (False, None))
 
2172
 
 
2173
 
1881
2174
class TestTransformPreview(tests.TestCaseWithTransport):
1882
2175
 
1883
2176
    def create_tree(self):
1884
2177
        tree = self.make_branch_and_tree('.')
1885
2178
        self.build_tree_contents([('a', 'content 1')])
 
2179
        tree.set_root_id('TREE_ROOT')
1886
2180
        tree.add('a', 'a-id')
1887
2181
        tree.commit('rev1', rev_id='rev1')
1888
2182
        return tree.branch.repository.revision_tree('rev1')
1955
2249
                          (False, False))],
1956
2250
                          list(preview_tree.iter_changes(revision_tree)))
1957
2251
 
1958
 
    def test_wrong_tree_value_error(self):
 
2252
    def test_include_unchanged_succeeds(self):
1959
2253
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1960
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1961
 
                              preview_tree)
1962
 
        self.assertEqual('from_tree must be transform source tree.', str(e))
 
2254
        changes = preview_tree.iter_changes(revision_tree,
 
2255
                                            include_unchanged=True)
 
2256
        root = revision_tree.inventory.root.file_id
1963
2257
 
1964
 
    def test_include_unchanged_value_error(self):
1965
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1966
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1967
 
                              revision_tree, include_unchanged=True)
1968
 
        self.assertEqual('include_unchanged is not supported', str(e))
 
2258
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
1969
2259
 
1970
2260
    def test_specific_files(self):
1971
2261
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1972
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1973
 
                              revision_tree, specific_files=['pete'])
1974
 
        self.assertEqual('specific_files is not supported', str(e))
 
2262
        changes = preview_tree.iter_changes(revision_tree,
 
2263
                                            specific_files=[''])
 
2264
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
1975
2265
 
1976
 
    def test_want_unversioned_value_error(self):
 
2266
    def test_want_unversioned(self):
1977
2267
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1978
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1979
 
                              revision_tree, want_unversioned=True)
1980
 
        self.assertEqual('want_unversioned is not supported', str(e))
 
2268
        changes = preview_tree.iter_changes(revision_tree,
 
2269
                                            want_unversioned=True)
 
2270
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
1981
2271
 
1982
2272
    def test_ignore_extra_trees_no_specific_files(self):
1983
2273
        # extra_trees is harmless without specific_files, so we'll silently
2168
2458
 
2169
2459
    def test_file_content_summary_executable(self):
2170
2460
        if not osutils.supports_executable():
2171
 
            raise TestNotApplicable()
 
2461
            raise tests.TestNotApplicable()
2172
2462
        preview = self.get_empty_preview()
2173
2463
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2174
2464
        preview.set_executability(True, path_id)
2183
2473
        self.assertIs(None, summary[3])
2184
2474
 
2185
2475
    def test_change_executability(self):
2186
 
        if not osutils.supports_executable():
2187
 
            raise TestNotApplicable()
2188
2476
        tree = self.make_branch_and_tree('tree')
2189
2477
        self.build_tree(['tree/path'])
2190
2478
        tree.add('path')
2299
2587
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
2300
2588
        preview_tree = preview.get_preview_tree()
2301
2589
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2590
 
 
2591
    def test_get_set_parent_ids(self):
 
2592
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2593
        self.assertEqual([], preview_tree.get_parent_ids())
 
2594
        preview_tree.set_parent_ids(['rev-1'])
 
2595
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2596
 
 
2597
    def test_plan_file_merge(self):
 
2598
        work_a = self.make_branch_and_tree('wta')
 
2599
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2600
        work_a.add('file', 'file-id')
 
2601
        base_id = work_a.commit('base version')
 
2602
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2603
        preview = TransformPreview(work_a)
 
2604
        self.addCleanup(preview.finalize)
 
2605
        trans_id = preview.trans_id_file_id('file-id')
 
2606
        preview.delete_contents(trans_id)
 
2607
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2608
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2609
        tree_a = preview.get_preview_tree()
 
2610
        tree_a.set_parent_ids([base_id])
 
2611
        self.assertEqual([
 
2612
            ('killed-a', 'a\n'),
 
2613
            ('killed-b', 'b\n'),
 
2614
            ('unchanged', 'c\n'),
 
2615
            ('unchanged', 'd\n'),
 
2616
            ('new-a', 'e\n'),
 
2617
            ('new-b', 'f\n'),
 
2618
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2619
 
 
2620
    def test_plan_file_merge_revision_tree(self):
 
2621
        work_a = self.make_branch_and_tree('wta')
 
2622
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2623
        work_a.add('file', 'file-id')
 
2624
        base_id = work_a.commit('base version')
 
2625
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2626
        preview = TransformPreview(work_a.basis_tree())
 
2627
        self.addCleanup(preview.finalize)
 
2628
        trans_id = preview.trans_id_file_id('file-id')
 
2629
        preview.delete_contents(trans_id)
 
2630
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2631
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2632
        tree_a = preview.get_preview_tree()
 
2633
        tree_a.set_parent_ids([base_id])
 
2634
        self.assertEqual([
 
2635
            ('killed-a', 'a\n'),
 
2636
            ('killed-b', 'b\n'),
 
2637
            ('unchanged', 'c\n'),
 
2638
            ('unchanged', 'd\n'),
 
2639
            ('new-a', 'e\n'),
 
2640
            ('new-b', 'f\n'),
 
2641
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2642
 
 
2643
    def test_walkdirs(self):
 
2644
        preview = self.get_empty_preview()
 
2645
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2646
        # FIXME: new_directory should mark root.
 
2647
        preview.fixup_new_roots()
 
2648
        preview_tree = preview.get_preview_tree()
 
2649
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2650
                                         'a-id')
 
2651
        expected = [(('', 'tree-root'),
 
2652
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2653
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2654
 
 
2655
    def test_extras(self):
 
2656
        work_tree = self.make_branch_and_tree('tree')
 
2657
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2658
                         'tree/not-removed-file'])
 
2659
        work_tree.add(['removed-file', 'not-removed-file'])
 
2660
        preview = TransformPreview(work_tree)
 
2661
        self.addCleanup(preview.finalize)
 
2662
        preview.new_file('new-file', preview.root, 'contents')
 
2663
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2664
                         'new-versioned-id')
 
2665
        tree = preview.get_preview_tree()
 
2666
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2667
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2668
                         set(tree.extras()))
 
2669
 
 
2670
    def test_merge_into_preview(self):
 
2671
        work_tree = self.make_branch_and_tree('tree')
 
2672
        self.build_tree_contents([('tree/file','b\n')])
 
2673
        work_tree.add('file', 'file-id')
 
2674
        work_tree.commit('first commit')
 
2675
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2676
        self.build_tree_contents([('child/file','b\nc\n')])
 
2677
        child_tree.commit('child commit')
 
2678
        child_tree.lock_write()
 
2679
        self.addCleanup(child_tree.unlock)
 
2680
        work_tree.lock_write()
 
2681
        self.addCleanup(work_tree.unlock)
 
2682
        preview = TransformPreview(work_tree)
 
2683
        self.addCleanup(preview.finalize)
 
2684
        file_trans_id = preview.trans_id_file_id('file-id')
 
2685
        preview.delete_contents(file_trans_id)
 
2686
        preview.create_file('a\nb\n', file_trans_id)
 
2687
        pb = progress.DummyProgress()
 
2688
        preview_tree = preview.get_preview_tree()
 
2689
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2690
                                          child_tree.branch.last_revision(),
 
2691
                                          other_branch=child_tree.branch,
 
2692
                                          tree_branch=work_tree.branch)
 
2693
        merger.merge_type = Merge3Merger
 
2694
        tt = merger.make_merger().make_preview_transform()
 
2695
        self.addCleanup(tt.finalize)
 
2696
        final_tree = tt.get_preview_tree()
 
2697
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2698
 
 
2699
    def test_merge_preview_into_workingtree(self):
 
2700
        tree = self.make_branch_and_tree('tree')
 
2701
        tree.set_root_id('TREE_ROOT')
 
2702
        tt = TransformPreview(tree)
 
2703
        self.addCleanup(tt.finalize)
 
2704
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2705
        tree2 = self.make_branch_and_tree('tree2')
 
2706
        tree2.set_root_id('TREE_ROOT')
 
2707
        pb = progress.DummyProgress()
 
2708
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2709
                                         pb, tree.basis_tree())
 
2710
        merger.merge_type = Merge3Merger
 
2711
        merger.do_merge()
 
2712
 
 
2713
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2714
        tree = self.make_branch_and_tree('tree')
 
2715
        self.build_tree_contents([('tree/foo', 'bar')])
 
2716
        tree.add('foo', 'foo-id')
 
2717
        tree.commit('foo')
 
2718
        tt = TransformPreview(tree)
 
2719
        self.addCleanup(tt.finalize)
 
2720
        trans_id = tt.trans_id_file_id('foo-id')
 
2721
        tt.delete_contents(trans_id)
 
2722
        tt.create_file('baz', trans_id)
 
2723
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2724
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2725
        pb = progress.DummyProgress()
 
2726
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2727
                                         pb, tree.basis_tree())
 
2728
        merger.merge_type = Merge3Merger
 
2729
        merger.do_merge()
 
2730
 
 
2731
    def test_is_executable(self):
 
2732
        tree = self.make_branch_and_tree('tree')
 
2733
        preview = TransformPreview(tree)
 
2734
        self.addCleanup(preview.finalize)
 
2735
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2736
        preview_tree = preview.get_preview_tree()
 
2737
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2738
                                                           'tree/foo'))
 
2739
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2740
 
 
2741
    def test_commit_preview_tree(self):
 
2742
        tree = self.make_branch_and_tree('tree')
 
2743
        rev_id = tree.commit('rev1')
 
2744
        tree.branch.lock_write()
 
2745
        self.addCleanup(tree.branch.unlock)
 
2746
        tt = TransformPreview(tree)
 
2747
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2748
        self.addCleanup(tt.finalize)
 
2749
        preview = tt.get_preview_tree()
 
2750
        preview.set_parent_ids([rev_id])
 
2751
        builder = tree.branch.get_commit_builder([rev_id])
 
2752
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2753
        builder.finish_inventory()
 
2754
        rev2_id = builder.commit('rev2')
 
2755
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2756
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2757
 
 
2758
    def test_ascii_limbo_paths(self):
 
2759
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2760
        branch = self.make_branch('any')
 
2761
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2762
        tt = TransformPreview(tree)
 
2763
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2764
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2765
        limbo_path = tt._limbo_name(bar_id)
 
2766
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2767
 
 
2768
 
 
2769
class FakeSerializer(object):
 
2770
    """Serializer implementation that simply returns the input.
 
2771
 
 
2772
    The input is returned in the order used by pack.ContainerPushParser.
 
2773
    """
 
2774
    @staticmethod
 
2775
    def bytes_record(bytes, names):
 
2776
        return names, bytes
 
2777
 
 
2778
 
 
2779
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2780
 
 
2781
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2782
 
 
2783
    def get_preview(self, tree=None):
 
2784
        if tree is None:
 
2785
            tree = self.make_branch_and_tree('tree')
 
2786
        tt = TransformPreview(tree)
 
2787
        self.addCleanup(tt.finalize)
 
2788
        return tt
 
2789
 
 
2790
    def assertSerializesTo(self, expected, tt):
 
2791
        records = list(tt.serialize(FakeSerializer()))
 
2792
        self.assertEqual(expected, records)
 
2793
 
 
2794
    @staticmethod
 
2795
    def default_attribs():
 
2796
        return {
 
2797
            '_id_number': 1,
 
2798
            '_new_name': {},
 
2799
            '_new_parent': {},
 
2800
            '_new_executability': {},
 
2801
            '_new_id': {},
 
2802
            '_tree_path_ids': {'': 'new-0'},
 
2803
            '_removed_id': [],
 
2804
            '_removed_contents': [],
 
2805
            '_non_present_ids': {},
 
2806
            }
 
2807
 
 
2808
    def make_records(self, attribs, contents):
 
2809
        records = [
 
2810
            (((('attribs'),),), bencode.bencode(attribs))]
 
2811
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2812
        return records
 
2813
 
 
2814
    def creation_records(self):
 
2815
        attribs = self.default_attribs()
 
2816
        attribs['_id_number'] = 3
 
2817
        attribs['_new_name'] = {
 
2818
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2819
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2820
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2821
        attribs['_new_executability'] = {'new-1': 1}
 
2822
        contents = [
 
2823
            ('new-1', 'file', 'i 1\nbar\n'),
 
2824
            ('new-2', 'directory', ''),
 
2825
            ]
 
2826
        return self.make_records(attribs, contents)
 
2827
 
 
2828
    def test_serialize_creation(self):
 
2829
        tt = self.get_preview()
 
2830
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2831
        tt.new_directory('qux', tt.root, 'quxx')
 
2832
        self.assertSerializesTo(self.creation_records(), tt)
 
2833
 
 
2834
    def test_deserialize_creation(self):
 
2835
        tt = self.get_preview()
 
2836
        tt.deserialize(iter(self.creation_records()))
 
2837
        self.assertEqual(3, tt._id_number)
 
2838
        self.assertEqual({'new-1': u'foo\u1234',
 
2839
                          'new-2': 'qux'}, tt._new_name)
 
2840
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2841
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2842
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2843
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2844
        self.assertEqual({'new-1': 'file',
 
2845
                          'new-2': 'directory'}, tt._new_contents)
 
2846
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2847
        try:
 
2848
            foo_content = foo_limbo.read()
 
2849
        finally:
 
2850
            foo_limbo.close()
 
2851
        self.assertEqual('bar', foo_content)
 
2852
 
 
2853
    def symlink_creation_records(self):
 
2854
        attribs = self.default_attribs()
 
2855
        attribs['_id_number'] = 2
 
2856
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2857
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2858
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2859
        return self.make_records(attribs, contents)
 
2860
 
 
2861
    def test_serialize_symlink_creation(self):
 
2862
        self.requireFeature(tests.SymlinkFeature)
 
2863
        tt = self.get_preview()
 
2864
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2865
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2866
 
 
2867
    def test_deserialize_symlink_creation(self):
 
2868
        self.requireFeature(tests.SymlinkFeature)
 
2869
        tt = self.get_preview()
 
2870
        tt.deserialize(iter(self.symlink_creation_records()))
 
2871
        abspath = tt._limbo_name('new-1')
 
2872
        foo_content = osutils.readlink(abspath)
 
2873
        self.assertEqual(u'bar\u1234', foo_content)
 
2874
 
 
2875
    def make_destruction_preview(self):
 
2876
        tree = self.make_branch_and_tree('.')
 
2877
        self.build_tree([u'foo\u1234', 'bar'])
 
2878
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2879
        return self.get_preview(tree)
 
2880
 
 
2881
    def destruction_records(self):
 
2882
        attribs = self.default_attribs()
 
2883
        attribs['_id_number'] = 3
 
2884
        attribs['_removed_id'] = ['new-1']
 
2885
        attribs['_removed_contents'] = ['new-2']
 
2886
        attribs['_tree_path_ids'] = {
 
2887
            '': 'new-0',
 
2888
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2889
            'bar': 'new-2',
 
2890
            }
 
2891
        return self.make_records(attribs, [])
 
2892
 
 
2893
    def test_serialize_destruction(self):
 
2894
        tt = self.make_destruction_preview()
 
2895
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2896
        tt.unversion_file(foo_trans_id)
 
2897
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2898
        tt.delete_contents(bar_trans_id)
 
2899
        self.assertSerializesTo(self.destruction_records(), tt)
 
2900
 
 
2901
    def test_deserialize_destruction(self):
 
2902
        tt = self.make_destruction_preview()
 
2903
        tt.deserialize(iter(self.destruction_records()))
 
2904
        self.assertEqual({u'foo\u1234': 'new-1',
 
2905
                          'bar': 'new-2',
 
2906
                          '': tt.root}, tt._tree_path_ids)
 
2907
        self.assertEqual({'new-1': u'foo\u1234',
 
2908
                          'new-2': 'bar',
 
2909
                          tt.root: ''}, tt._tree_id_paths)
 
2910
        self.assertEqual(set(['new-1']), tt._removed_id)
 
2911
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
2912
 
 
2913
    def missing_records(self):
 
2914
        attribs = self.default_attribs()
 
2915
        attribs['_id_number'] = 2
 
2916
        attribs['_non_present_ids'] = {
 
2917
            'boo': 'new-1',}
 
2918
        return self.make_records(attribs, [])
 
2919
 
 
2920
    def test_serialize_missing(self):
 
2921
        tt = self.get_preview()
 
2922
        boo_trans_id = tt.trans_id_file_id('boo')
 
2923
        self.assertSerializesTo(self.missing_records(), tt)
 
2924
 
 
2925
    def test_deserialize_missing(self):
 
2926
        tt = self.get_preview()
 
2927
        tt.deserialize(iter(self.missing_records()))
 
2928
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
2929
 
 
2930
    def make_modification_preview(self):
 
2931
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2932
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2933
        tree = self.make_branch_and_tree('tree')
 
2934
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2935
        tree.add('file', 'file-id')
 
2936
        return self.get_preview(tree), LINES_TWO
 
2937
 
 
2938
    def modification_records(self):
 
2939
        attribs = self.default_attribs()
 
2940
        attribs['_id_number'] = 2
 
2941
        attribs['_tree_path_ids'] = {
 
2942
            'file': 'new-1',
 
2943
            '': 'new-0',}
 
2944
        attribs['_removed_contents'] = ['new-1']
 
2945
        contents = [('new-1', 'file',
 
2946
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
2947
        return self.make_records(attribs, contents)
 
2948
 
 
2949
    def test_serialize_modification(self):
 
2950
        tt, LINES = self.make_modification_preview()
 
2951
        trans_id = tt.trans_id_file_id('file-id')
 
2952
        tt.delete_contents(trans_id)
 
2953
        tt.create_file(LINES, trans_id)
 
2954
        self.assertSerializesTo(self.modification_records(), tt)
 
2955
 
 
2956
    def test_deserialize_modification(self):
 
2957
        tt, LINES = self.make_modification_preview()
 
2958
        tt.deserialize(iter(self.modification_records()))
 
2959
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2960
 
 
2961
    def make_kind_change_preview(self):
 
2962
        LINES = 'a\nb\nc\nd\n'
 
2963
        tree = self.make_branch_and_tree('tree')
 
2964
        self.build_tree(['tree/foo/'])
 
2965
        tree.add('foo', 'foo-id')
 
2966
        return self.get_preview(tree), LINES
 
2967
 
 
2968
    def kind_change_records(self):
 
2969
        attribs = self.default_attribs()
 
2970
        attribs['_id_number'] = 2
 
2971
        attribs['_tree_path_ids'] = {
 
2972
            'foo': 'new-1',
 
2973
            '': 'new-0',}
 
2974
        attribs['_removed_contents'] = ['new-1']
 
2975
        contents = [('new-1', 'file',
 
2976
                     'i 4\na\nb\nc\nd\n\n')]
 
2977
        return self.make_records(attribs, contents)
 
2978
 
 
2979
    def test_serialize_kind_change(self):
 
2980
        tt, LINES = self.make_kind_change_preview()
 
2981
        trans_id = tt.trans_id_file_id('foo-id')
 
2982
        tt.delete_contents(trans_id)
 
2983
        tt.create_file(LINES, trans_id)
 
2984
        self.assertSerializesTo(self.kind_change_records(), tt)
 
2985
 
 
2986
    def test_deserialize_kind_change(self):
 
2987
        tt, LINES = self.make_kind_change_preview()
 
2988
        tt.deserialize(iter(self.kind_change_records()))
 
2989
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2990
 
 
2991
    def make_add_contents_preview(self):
 
2992
        LINES = 'a\nb\nc\nd\n'
 
2993
        tree = self.make_branch_and_tree('tree')
 
2994
        self.build_tree(['tree/foo'])
 
2995
        tree.add('foo')
 
2996
        os.unlink('tree/foo')
 
2997
        return self.get_preview(tree), LINES
 
2998
 
 
2999
    def add_contents_records(self):
 
3000
        attribs = self.default_attribs()
 
3001
        attribs['_id_number'] = 2
 
3002
        attribs['_tree_path_ids'] = {
 
3003
            'foo': 'new-1',
 
3004
            '': 'new-0',}
 
3005
        contents = [('new-1', 'file',
 
3006
                     'i 4\na\nb\nc\nd\n\n')]
 
3007
        return self.make_records(attribs, contents)
 
3008
 
 
3009
    def test_serialize_add_contents(self):
 
3010
        tt, LINES = self.make_add_contents_preview()
 
3011
        trans_id = tt.trans_id_tree_path('foo')
 
3012
        tt.create_file(LINES, trans_id)
 
3013
        self.assertSerializesTo(self.add_contents_records(), tt)
 
3014
 
 
3015
    def test_deserialize_add_contents(self):
 
3016
        tt, LINES = self.make_add_contents_preview()
 
3017
        tt.deserialize(iter(self.add_contents_records()))
 
3018
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3019
 
 
3020
    def test_get_parents_lines(self):
 
3021
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3022
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3023
        tree = self.make_branch_and_tree('tree')
 
3024
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3025
        tree.add('file', 'file-id')
 
3026
        tt = self.get_preview(tree)
 
3027
        trans_id = tt.trans_id_tree_path('file')
 
3028
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
3029
            tt._get_parents_lines(trans_id))
 
3030
 
 
3031
    def test_get_parents_texts(self):
 
3032
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3033
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3034
        tree = self.make_branch_and_tree('tree')
 
3035
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3036
        tree.add('file', 'file-id')
 
3037
        tt = self.get_preview(tree)
 
3038
        trans_id = tt.trans_id_tree_path('file')
 
3039
        self.assertEqual((LINES_ONE,),
 
3040
            tt._get_parents_texts(trans_id))