~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2010-03-11 13:47:06 UTC
  • mfrom: (5051.3.16 use-branch-open)
  • Revision ID: pqm@pqm.ubuntu.com-20100311134706-kaerqhx3lf7xn6rh
(Jelmer) Pass colocated branch names further down the call stack.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 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
 
import stat
19
18
from StringIO import StringIO
20
19
import sys
 
20
import time
21
21
 
22
22
from bzrlib import (
 
23
    bencode,
23
24
    errors,
 
25
    filters,
24
26
    generate_ids,
25
27
    osutils,
26
28
    progress,
27
29
    revision as _mod_revision,
28
 
    symbol_versioning,
 
30
    rules,
29
31
    tests,
30
32
    urlutils,
31
33
    )
35
37
                              NonDirectoryParent)
36
38
from bzrlib.diff import show_diff_trees
37
39
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
 
                           ReusingTransform, CantMoveRoot, 
 
40
                           ReusingTransform, CantMoveRoot,
39
41
                           PathsNotVersionedError, ExistingLimbo,
40
42
                           ExistingPendingDeletion, ImmortalLimbo,
41
43
                           ImmortalPendingDeletion, LockError)
42
44
from bzrlib.osutils import file_kind, pathjoin
43
 
from bzrlib.merge import Merge3Merger
 
45
from bzrlib.merge import Merge3Merger, Merger
44
46
from bzrlib.tests import (
45
 
    CaseInsensitiveFilesystemFeature,
46
47
    HardlinkFeature,
47
48
    SymlinkFeature,
48
49
    TestCase,
49
50
    TestCaseInTempDir,
50
51
    TestSkipped,
51
52
    )
52
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
53
 
                              resolve_conflicts, cook_conflicts, 
 
53
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
 
54
                              resolve_conflicts, cook_conflicts,
54
55
                              build_tree, get_backup_name,
55
56
                              _FileMover, resolve_checkout,
56
 
                              TransformPreview)
 
57
                              TransformPreview, create_from_tree)
 
58
 
57
59
 
58
60
class TestTreeTransform(tests.TestCaseWithTransport):
59
61
 
128
130
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
129
131
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
130
132
        self.assertEqual(len(modified_paths), 3)
131
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
133
        tree_mod_paths = [self.wt.id2abspath(f) for f in
132
134
                          ('ozzie', 'my_pretties', 'my_pretties2')]
133
135
        self.assertSubset(tree_mod_paths, modified_paths)
134
136
        # is it safe to finalize repeatedly?
135
137
        transform.finalize()
136
138
        transform.finalize()
137
139
 
 
140
    def test_create_files_same_timestamp(self):
 
141
        transform, root = self.get_transform()
 
142
        self.wt.lock_tree_write()
 
143
        self.addCleanup(self.wt.unlock)
 
144
        # Roll back the clock, so that we know everything is being set to the
 
145
        # exact time
 
146
        transform._creation_mtime = creation_mtime = time.time() - 20.0
 
147
        transform.create_file('content-one',
 
148
                              transform.create_path('one', root))
 
149
        time.sleep(1) # *ugly*
 
150
        transform.create_file('content-two',
 
151
                              transform.create_path('two', root))
 
152
        transform.apply()
 
153
        fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
 
154
        fo.close()
 
155
        fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
 
156
        fo.close()
 
157
        # We only guarantee 2s resolution
 
158
        self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
 
159
            "%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
 
160
        # But if we have more than that, all files should get the same result
 
161
        self.assertEqual(st1.st_mtime, st2.st_mtime)
 
162
 
 
163
    def test_change_root_id(self):
 
164
        transform, root = self.get_transform()
 
165
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
166
        transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
167
        transform.delete_contents(root)
 
168
        transform.unversion_file(root)
 
169
        transform.fixup_new_roots()
 
170
        transform.apply()
 
171
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
172
 
 
173
    def test_change_root_id_add_files(self):
 
174
        transform, root = self.get_transform()
 
175
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
176
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
177
        transform.new_file('file', new_trans_id, ['new-contents\n'],
 
178
                           'new-file-id')
 
179
        transform.delete_contents(root)
 
180
        transform.unversion_file(root)
 
181
        transform.fixup_new_roots()
 
182
        transform.apply()
 
183
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
184
        self.assertEqual('new-file-id', self.wt.path2id('file'))
 
185
        self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
 
186
 
 
187
    def test_add_two_roots(self):
 
188
        transform, root = self.get_transform()
 
189
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
190
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
 
191
        self.assertRaises(ValueError, transform.fixup_new_roots)
 
192
 
138
193
    def test_hardlink(self):
139
194
        self.requireFeature(HardlinkFeature)
140
195
        transform, root = self.get_transform()
154
209
        transform, root = self.get_transform()
155
210
        self.wt.lock_tree_write()
156
211
        self.addCleanup(self.wt.unlock)
157
 
        trans_id = transform.new_file('name', root, 'contents', 
 
212
        trans_id = transform.new_file('name', root, 'contents',
158
213
                                      'my_pretties', True)
159
214
        oz = transform.new_directory('oz', root, 'oz-id')
160
215
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
161
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
216
        toto = transform.new_file('toto', dorothy, 'toto-contents',
162
217
                                  'toto-id', False)
163
218
 
164
219
        self.assertEqual(len(transform.find_conflicts()), 0)
188
243
 
189
244
    def test_conflicts(self):
190
245
        transform, root = self.get_transform()
191
 
        trans_id = transform.new_file('name', root, 'contents', 
 
246
        trans_id = transform.new_file('name', root, 'contents',
192
247
                                      'my_pretties')
193
248
        self.assertEqual(len(transform.find_conflicts()), 0)
194
249
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
195
 
        self.assertEqual(transform.find_conflicts(), 
 
250
        self.assertEqual(transform.find_conflicts(),
196
251
                         [('duplicate', trans_id, trans_id2, 'name')])
197
252
        self.assertRaises(MalformedTransform, transform.apply)
198
253
        transform.adjust_path('name', trans_id, trans_id2)
199
 
        self.assertEqual(transform.find_conflicts(), 
 
254
        self.assertEqual(transform.find_conflicts(),
200
255
                         [('non-directory parent', trans_id)])
201
256
        tinman_id = transform.trans_id_tree_path('tinman')
202
257
        transform.adjust_path('name', tinman_id, trans_id2)
203
 
        self.assertEqual(transform.find_conflicts(), 
204
 
                         [('unversioned parent', tinman_id), 
 
258
        self.assertEqual(transform.find_conflicts(),
 
259
                         [('unversioned parent', tinman_id),
205
260
                          ('missing parent', tinman_id)])
206
261
        lion_id = transform.create_path('lion', root)
207
 
        self.assertEqual(transform.find_conflicts(), 
208
 
                         [('unversioned parent', tinman_id), 
 
262
        self.assertEqual(transform.find_conflicts(),
 
263
                         [('unversioned parent', tinman_id),
209
264
                          ('missing parent', tinman_id)])
210
265
        transform.adjust_path('name', lion_id, trans_id2)
211
 
        self.assertEqual(transform.find_conflicts(), 
 
266
        self.assertEqual(transform.find_conflicts(),
212
267
                         [('unversioned parent', lion_id),
213
268
                          ('missing parent', lion_id)])
214
269
        transform.version_file("Courage", lion_id)
215
 
        self.assertEqual(transform.find_conflicts(), 
216
 
                         [('missing parent', lion_id), 
 
270
        self.assertEqual(transform.find_conflicts(),
 
271
                         [('missing parent', lion_id),
217
272
                          ('versioning no contents', lion_id)])
218
273
        transform.adjust_path('name2', root, trans_id2)
219
 
        self.assertEqual(transform.find_conflicts(), 
 
274
        self.assertEqual(transform.find_conflicts(),
220
275
                         [('versioning no contents', lion_id)])
221
276
        transform.create_file('Contents, okay?', lion_id)
222
277
        transform.adjust_path('name2', trans_id2, trans_id2)
223
 
        self.assertEqual(transform.find_conflicts(), 
224
 
                         [('parent loop', trans_id2), 
 
278
        self.assertEqual(transform.find_conflicts(),
 
279
                         [('parent loop', trans_id2),
225
280
                          ('non-directory parent', trans_id2)])
226
281
        transform.adjust_path('name2', root, trans_id2)
227
282
        oz_id = transform.new_directory('oz', root)
228
283
        transform.set_executability(True, oz_id)
229
 
        self.assertEqual(transform.find_conflicts(), 
 
284
        self.assertEqual(transform.find_conflicts(),
230
285
                         [('unversioned executability', oz_id)])
231
286
        transform.version_file('oz-id', oz_id)
232
 
        self.assertEqual(transform.find_conflicts(), 
 
287
        self.assertEqual(transform.find_conflicts(),
233
288
                         [('non-file executability', oz_id)])
234
289
        transform.set_executability(None, oz_id)
235
290
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
244
299
        self.assert_('oz/tip' in transform2._tree_path_ids)
245
300
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
246
301
        self.assertEqual(len(result), 2)
247
 
        self.assertEqual((result[0][0], result[0][1]), 
 
302
        self.assertEqual((result[0][0], result[0][1]),
248
303
                         ('duplicate', newtip))
249
 
        self.assertEqual((result[1][0], result[1][2]), 
 
304
        self.assertEqual((result[1][0], result[1][2]),
250
305
                         ('duplicate id', newtip))
251
306
        transform2.finalize()
252
307
        transform3 = TreeTransform(self.wt)
253
308
        self.addCleanup(transform3.finalize)
254
309
        oz_id = transform3.trans_id_tree_file_id('oz-id')
255
310
        transform3.delete_contents(oz_id)
256
 
        self.assertEqual(transform3.find_conflicts(), 
 
311
        self.assertEqual(transform3.find_conflicts(),
257
312
                         [('missing parent', oz_id)])
258
313
        root_id = transform3.root
259
314
        tip_id = transform3.trans_id_tree_file_id('tip-id')
368
423
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
369
424
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
370
425
 
 
426
    def test_adjust_path_updates_child_limbo_names(self):
 
427
        tree = self.make_branch_and_tree('tree')
 
428
        transform = TreeTransform(tree)
 
429
        self.addCleanup(transform.finalize)
 
430
        foo_id = transform.new_directory('foo', transform.root)
 
431
        bar_id = transform.new_directory('bar', foo_id)
 
432
        baz_id = transform.new_directory('baz', bar_id)
 
433
        qux_id = transform.new_directory('qux', baz_id)
 
434
        transform.adjust_path('quxx', foo_id, bar_id)
 
435
        self.assertStartsWith(transform._limbo_name(qux_id),
 
436
                              transform._limbo_name(bar_id))
 
437
 
371
438
    def test_add_del(self):
372
439
        start, root = self.get_transform()
373
440
        start.new_directory('a', root, 'a')
386
453
        self.addCleanup(unversion.finalize)
387
454
        parent = unversion.trans_id_tree_path('parent')
388
455
        unversion.unversion_file(parent)
389
 
        self.assertEqual(unversion.find_conflicts(), 
 
456
        self.assertEqual(unversion.find_conflicts(),
390
457
                         [('unversioned parent', parent_id)])
391
458
        file_id = unversion.trans_id_tree_file_id('child-id')
392
459
        unversion.unversion_file(file_id)
412
479
        mangle_tree.adjust_path('name2', root, name1)
413
480
        mangle_tree.adjust_path('name1', root, name2)
414
481
 
415
 
        #tests for deleting parent directories 
 
482
        #tests for deleting parent directories
416
483
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
417
484
        mangle_tree.delete_contents(ddir)
418
485
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
447
514
        create_tree,root = self.get_transform()
448
515
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
449
516
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
450
 
        create_tree.apply()        
 
517
        create_tree.apply()
451
518
        mangle_tree,root = self.get_transform()
452
519
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
453
520
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
461
528
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
462
529
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
463
530
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
464
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
531
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
465
532
                             'test_too_much-id')
466
 
        create_tree.apply()        
 
533
        create_tree.apply()
467
534
        mangle_tree,root = self.get_transform()
468
535
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
469
536
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
470
537
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
471
538
        mangle_tree.adjust_path('selftest', bzrlib, tests)
472
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
539
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
473
540
        mangle_tree.set_executability(True, test_too_much)
474
541
        mangle_tree.apply()
475
542
 
476
543
    def test_both_rename3(self):
477
544
        create_tree,root = self.get_transform()
478
545
        tests = create_tree.new_directory('tests', root, 'tests-id')
479
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
546
        create_tree.new_file('test_too_much.py', tests, 'hello1',
480
547
                             'test_too_much-id')
481
 
        create_tree.apply()        
 
548
        create_tree.apply()
482
549
        mangle_tree,root = self.get_transform()
483
550
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
484
551
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
485
552
        mangle_tree.adjust_path('selftest', root, tests)
486
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
553
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
487
554
        mangle_tree.set_executability(True, test_too_much)
488
555
        mangle_tree.apply()
489
556
 
502
569
        newdir = move_id.new_directory('dir', root, 'newdir')
503
570
        move_id.adjust_path('name2', newdir, name1)
504
571
        move_id.apply()
505
 
        
 
572
 
506
573
    def test_replace_dangling_ie(self):
507
574
        create_tree, root = self.get_transform()
508
575
        # prepare tree
524
591
        resolve_conflicts(replace)
525
592
        replace.apply()
526
593
 
527
 
    def test_symlinks(self):
 
594
    def _test_symlinks(self, link_name1,link_target1,
 
595
                       link_name2, link_target2):
 
596
 
 
597
        def ozpath(p): return 'oz/' + p
 
598
 
528
599
        self.requireFeature(SymlinkFeature)
529
 
        transform,root = self.get_transform()
 
600
        transform, root = self.get_transform()
530
601
        oz_id = transform.new_directory('oz', root, 'oz-id')
531
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
602
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
532
603
                                       '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)            
 
604
        wiz_id = transform.create_path(link_name2, oz_id)
 
605
        transform.create_symlink(link_target2, wiz_id)
 
606
        transform.version_file('wiz-id2', wiz_id)
536
607
        transform.set_executability(True, wiz_id)
537
 
        self.assertEqual(transform.find_conflicts(), 
 
608
        self.assertEqual(transform.find_conflicts(),
538
609
                         [('non-file executability', wiz_id)])
539
610
        transform.set_executability(None, wiz_id)
540
611
        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')
 
612
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
 
613
        self.assertEqual('symlink',
 
614
                         file_kind(self.wt.abspath(ozpath(link_name1))))
 
615
        self.assertEqual(link_target2,
 
616
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
 
617
        self.assertEqual(link_target1,
 
618
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
 
619
 
 
620
    def test_symlinks(self):
 
621
        self._test_symlinks('wizard', 'wizard-target',
 
622
                            'wizard2', 'behind_curtain')
 
623
 
 
624
    def test_symlinks_unicode(self):
 
625
        self.requireFeature(tests.UnicodeFilenameFeature)
 
626
        self._test_symlinks(u'\N{Euro Sign}wizard',
 
627
                            u'wizard-targ\N{Euro Sign}t',
 
628
                            u'\N{Euro Sign}wizard2',
 
629
                            u'b\N{Euro Sign}hind_curtain')
547
630
 
548
631
    def test_unable_create_symlink(self):
549
632
        def tt_helper():
573
656
        create.apply()
574
657
        conflicts,root = self.get_transform()
575
658
        # set up duplicate entry, duplicate id
576
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
659
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
577
660
                                         'dorothy-id')
578
661
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
579
662
        oz = conflicts.trans_id_tree_file_id('oz-id')
603
686
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
604
687
        raw_conflicts = resolve_conflicts(tt)
605
688
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
606
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
689
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
607
690
                                   'dorothy', None, 'dorothy-id')
608
691
        self.assertEqual(cooked_conflicts[0], duplicate)
609
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
692
        duplicate_id = DuplicateID('Unversioned existing file',
610
693
                                   'dorothy.moved', 'dorothy', None,
611
694
                                   'dorothy-id')
612
695
        self.assertEqual(cooked_conflicts[1], duplicate_id)
620
703
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
621
704
                                               'oz-id')
622
705
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
623
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
706
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
624
707
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
625
708
        self.assertEqual(cooked_conflicts[4], deleted_parent)
626
709
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
729
812
        create.apply()
730
813
        transform, root = self.get_transform()
731
814
        transform.adjust_root_path('oldroot', fun)
732
 
        new_root=transform.trans_id_tree_path('')
 
815
        new_root = transform.trans_id_tree_path('')
733
816
        transform.version_file('new-root', new_root)
734
817
        transform.apply()
735
818
 
749
832
 
750
833
    def test_set_executability_order(self):
751
834
        """Ensure that executability behaves the same, no matter what order.
752
 
        
 
835
 
753
836
        - create file and set executability simultaneously
754
837
        - create file and set executability afterward
755
838
        - unsetting the executability of a file whose executability has not been
1162
1245
        transform.cancel_creation(parent)
1163
1246
        transform.finalize()
1164
1247
 
1165
 
    def test_case_insensitive_clash(self):
1166
 
        self.requireFeature(CaseInsensitiveFilesystemFeature)
 
1248
    def test_rollback_on_directory_clash(self):
1167
1249
        def tt_helper():
1168
1250
            wt = self.make_branch_and_tree('.')
1169
1251
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1170
1252
            try:
1171
 
                tt.new_file('foo', tt.root, 'bar')
1172
 
                tt.new_file('Foo', tt.root, 'spam')
 
1253
                foo = tt.new_directory('foo', tt.root)
 
1254
                tt.new_file('bar', foo, 'foobar')
 
1255
                baz = tt.new_directory('baz', tt.root)
 
1256
                tt.new_file('qux', baz, 'quux')
 
1257
                # Ask for a rename 'foo' -> 'baz'
 
1258
                tt.adjust_path('baz', tt.root, foo)
1173
1259
                # Lie to tt that we've already resolved all conflicts.
1174
1260
                tt.apply(no_conflicts=True)
1175
1261
            except:
1176
1262
                wt.unlock()
1177
1263
                raise
 
1264
        # The rename will fail because the target directory is not empty (but
 
1265
        # raises FileExists anyway).
1178
1266
        err = self.assertRaises(errors.FileExists, tt_helper)
1179
1267
        self.assertContainsRe(str(err),
1180
 
            "^File exists: .+/foo")
 
1268
            "^File exists: .+/baz")
1181
1269
 
1182
1270
    def test_two_directories_clash(self):
1183
1271
        def tt_helper():
1186
1274
            try:
1187
1275
                foo_1 = tt.new_directory('foo', tt.root)
1188
1276
                tt.new_directory('bar', foo_1)
 
1277
                # Adding the same directory with a different content
1189
1278
                foo_2 = tt.new_directory('foo', tt.root)
1190
1279
                tt.new_directory('baz', foo_2)
1191
1280
                # Lie to tt that we've already resolved all conflicts.
1204
1293
            try:
1205
1294
                foo_1 = tt.new_directory('foo', tt.root)
1206
1295
                tt.new_directory('bar', foo_1)
 
1296
                # Adding the same directory with a different content
1207
1297
                foo_2 = tt.new_directory('foo', tt.root)
1208
1298
                tt.new_directory('baz', foo_2)
1209
1299
                # Lie to tt that we've already resolved all conflicts.
1306
1396
        transform.cancel_creation(trans_id)
1307
1397
        transform.apply()
1308
1398
 
 
1399
    def test_create_from_tree(self):
 
1400
        tree1 = self.make_branch_and_tree('tree1')
 
1401
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
 
1402
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
 
1403
        tree2 = self.make_branch_and_tree('tree2')
 
1404
        tt = TreeTransform(tree2)
 
1405
        foo_trans_id = tt.create_path('foo', tt.root)
 
1406
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1407
        bar_trans_id = tt.create_path('bar', tt.root)
 
1408
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
 
1409
        tt.apply()
 
1410
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
 
1411
        self.assertFileEqual('baz', 'tree2/bar')
 
1412
 
 
1413
    def test_create_from_tree_bytes(self):
 
1414
        """Provided lines are used instead of tree content."""
 
1415
        tree1 = self.make_branch_and_tree('tree1')
 
1416
        self.build_tree_contents([('tree1/foo', 'bar'),])
 
1417
        tree1.add('foo', 'foo-id')
 
1418
        tree2 = self.make_branch_and_tree('tree2')
 
1419
        tt = TreeTransform(tree2)
 
1420
        foo_trans_id = tt.create_path('foo', tt.root)
 
1421
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
 
1422
        tt.apply()
 
1423
        self.assertFileEqual('qux', 'tree2/foo')
 
1424
 
 
1425
    def test_create_from_tree_symlink(self):
 
1426
        self.requireFeature(SymlinkFeature)
 
1427
        tree1 = self.make_branch_and_tree('tree1')
 
1428
        os.symlink('bar', 'tree1/foo')
 
1429
        tree1.add('foo', 'foo-id')
 
1430
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
 
1431
        foo_trans_id = tt.create_path('foo', tt.root)
 
1432
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1433
        tt.apply()
 
1434
        self.assertEqual('bar', os.readlink('tree2/foo'))
 
1435
 
1309
1436
 
1310
1437
class TransformGroup(object):
1311
1438
 
1365
1492
        # textual merge
1366
1493
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1367
1494
        # three-way text conflict
1368
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1495
        self.assertEqual(this.wt.get_file('b').read(),
1369
1496
                         conflict_text('b', 'b2'))
1370
1497
        # OTHER wins
1371
1498
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1375
1502
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1376
1503
        # No change
1377
1504
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1378
 
        # Correct correct results when THIS == OTHER 
 
1505
        # Correct correct results when THIS == OTHER
1379
1506
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1380
1507
        # Text conflict when THIS & OTHER are text and BASE is dir
1381
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1508
        self.assertEqual(this.wt.get_file('h').read(),
1382
1509
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1383
1510
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1384
1511
                         '1\n2\n3\n4\n')
1385
1512
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1386
1513
                         'h\ni\nj\nk\n')
1387
1514
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1388
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1515
        self.assertEqual(this.wt.get_file('i').read(),
1389
1516
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1390
1517
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1391
1518
                         '1\n2\n3\n4\n')
1415
1542
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1416
1543
            tg.tt.new_file('c', tg.root, 'c', 'c')
1417
1544
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1418
 
        targets = ((base, 'base-e', 'base-f', None, None), 
1419
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1545
        targets = ((base, 'base-e', 'base-f', None, None),
 
1546
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
1420
1547
                   (other, 'other-e', None, 'other-g', 'other-h'))
1421
1548
        for tg, e_target, f_target, g_target, h_target in targets:
1422
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1549
            for link, target in (('e', e_target), ('f', f_target),
1423
1550
                                 ('g', g_target), ('h', h_target)):
1424
1551
                if target is not None:
1425
1552
                    tg.tt.new_symlink(link, tg.root, target, link)
1451
1578
        base = TransformGroup("BASE", root_id)
1452
1579
        this = TransformGroup("THIS", root_id)
1453
1580
        other = TransformGroup("OTHER", root_id)
1454
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1581
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1455
1582
                                   for t in [base, this, other]]
1456
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1583
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1457
1584
                                   for t in [base, this, other]]
1458
1585
        base.tt.new_directory('c', base_a, 'c')
1459
1586
        this.tt.new_directory('c1', this_a, 'c')
1484
1611
        base = TransformGroup("BASE", root_id)
1485
1612
        this = TransformGroup("THIS", root_id)
1486
1613
        other = TransformGroup("OTHER", root_id)
1487
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1614
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1488
1615
                                   for t in [base, this, other]]
1489
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1616
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1490
1617
                                   for t in [base, this, other]]
1491
1618
 
1492
1619
        base.tt.new_file('g', base_a, 'g', 'g')
1583
1710
        os.symlink('foo', 'target2/symlink')
1584
1711
        build_tree(source.basis_tree(), target)
1585
1712
        self.assertEqual([], target.conflicts())
1586
 
        
 
1713
 
1587
1714
    def test_directory_conflict_handling(self):
1588
1715
        """Ensure that when building trees, conflict handling is done"""
1589
1716
        source = self.make_branch_and_tree('source')
1645
1772
        target = self.make_branch_and_tree('target')
1646
1773
        self.build_tree(['target/name'])
1647
1774
        target.add('name')
1648
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1775
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1649
1776
            build_tree, source.basis_tree(), target)
1650
1777
 
1651
1778
    def test_build_tree_rename_count(self):
1795
1922
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1796
1923
        self.assertTrue(source.is_executable('file1-id'))
1797
1924
 
 
1925
    def install_rot13_content_filter(self, pattern):
 
1926
        # We could use
 
1927
        # self.addCleanup(filters._reset_registry, filters._reset_registry())
 
1928
        # below, but that looks a bit... hard to read even if it's exactly
 
1929
        # the same thing.
 
1930
        original_registry = filters._reset_registry()
 
1931
        def restore_registry():
 
1932
            filters._reset_registry(original_registry)
 
1933
        self.addCleanup(restore_registry)
 
1934
        def rot13(chunks, context=None):
 
1935
            return [''.join(chunks).encode('rot13')]
 
1936
        rot13filter = filters.ContentFilter(rot13, rot13)
 
1937
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
1938
        os.mkdir(self.test_home_dir + '/.bazaar')
 
1939
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
1940
        f = open(rules_filename, 'wb')
 
1941
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
1942
        f.close()
 
1943
        def uninstall_rules():
 
1944
            os.remove(rules_filename)
 
1945
            rules.reset_rules()
 
1946
        self.addCleanup(uninstall_rules)
 
1947
        rules.reset_rules()
 
1948
 
 
1949
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
1950
        """build_tree will not hardlink files that have content filtering rules
 
1951
        applied to them (but will still hardlink other files from the same tree
 
1952
        if it can).
 
1953
        """
 
1954
        self.requireFeature(HardlinkFeature)
 
1955
        self.install_rot13_content_filter('file1')
 
1956
        source = self.create_ab_tree()
 
1957
        target = self.make_branch_and_tree('target')
 
1958
        revision_tree = source.basis_tree()
 
1959
        revision_tree.lock_read()
 
1960
        self.addCleanup(revision_tree.unlock)
 
1961
        build_tree(revision_tree, target, source, hardlink=True)
 
1962
        target.lock_read()
 
1963
        self.addCleanup(target.unlock)
 
1964
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1965
        source_stat = os.stat('source/file1')
 
1966
        target_stat = os.stat('target/file1')
 
1967
        self.assertNotEqual(source_stat, target_stat)
 
1968
        source_stat = os.stat('source/file2')
 
1969
        target_stat = os.stat('target/file2')
 
1970
        self.assertEqualStat(source_stat, target_stat)
 
1971
 
1798
1972
    def test_case_insensitive_build_tree_inventory(self):
 
1973
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1974
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1975
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1799
1976
        source = self.make_branch_and_tree('source')
1800
1977
        self.build_tree(['source/file', 'source/FILE'])
1801
1978
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1809
1986
        self.assertEqual('FILE', target.id2path('upper-id'))
1810
1987
 
1811
1988
 
 
1989
class TestCommitTransform(tests.TestCaseWithTransport):
 
1990
 
 
1991
    def get_branch(self):
 
1992
        tree = self.make_branch_and_tree('tree')
 
1993
        tree.lock_write()
 
1994
        self.addCleanup(tree.unlock)
 
1995
        tree.commit('empty commit')
 
1996
        return tree.branch
 
1997
 
 
1998
    def get_branch_and_transform(self):
 
1999
        branch = self.get_branch()
 
2000
        tt = TransformPreview(branch.basis_tree())
 
2001
        self.addCleanup(tt.finalize)
 
2002
        return branch, tt
 
2003
 
 
2004
    def test_commit_wrong_basis(self):
 
2005
        branch = self.get_branch()
 
2006
        basis = branch.repository.revision_tree(
 
2007
            _mod_revision.NULL_REVISION)
 
2008
        tt = TransformPreview(basis)
 
2009
        self.addCleanup(tt.finalize)
 
2010
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
2011
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
2012
                         str(e))
 
2013
 
 
2014
    def test_empy_commit(self):
 
2015
        branch, tt = self.get_branch_and_transform()
 
2016
        rev = tt.commit(branch, 'my message')
 
2017
        self.assertEqual(2, branch.revno())
 
2018
        repo = branch.repository
 
2019
        self.assertEqual('my message', repo.get_revision(rev).message)
 
2020
 
 
2021
    def test_merge_parents(self):
 
2022
        branch, tt = self.get_branch_and_transform()
 
2023
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
2024
        self.assertEqual(['rev1b', 'rev1c'],
 
2025
                         branch.basis_tree().get_parent_ids()[1:])
 
2026
 
 
2027
    def test_first_commit(self):
 
2028
        branch = self.make_branch('branch')
 
2029
        branch.lock_write()
 
2030
        self.addCleanup(branch.unlock)
 
2031
        tt = TransformPreview(branch.basis_tree())
 
2032
        self.addCleanup(tt.finalize)
 
2033
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
2034
        rev = tt.commit(branch, 'my message')
 
2035
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
2036
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
2037
                            branch.last_revision())
 
2038
 
 
2039
    def test_first_commit_with_merge_parents(self):
 
2040
        branch = self.make_branch('branch')
 
2041
        branch.lock_write()
 
2042
        self.addCleanup(branch.unlock)
 
2043
        tt = TransformPreview(branch.basis_tree())
 
2044
        self.addCleanup(tt.finalize)
 
2045
        e = self.assertRaises(ValueError, tt.commit, branch,
 
2046
                          'my message', ['rev1b-id'])
 
2047
        self.assertEqual('Cannot supply merge parents for first commit.',
 
2048
                         str(e))
 
2049
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
2050
 
 
2051
    def test_add_files(self):
 
2052
        branch, tt = self.get_branch_and_transform()
 
2053
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2054
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
2055
        if SymlinkFeature.available():
 
2056
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
2057
        rev = tt.commit(branch, 'message')
 
2058
        tree = branch.basis_tree()
 
2059
        self.assertEqual('file', tree.id2path('file-id'))
 
2060
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
2061
        self.assertEqual('dir', tree.id2path('dir-id'))
 
2062
        if SymlinkFeature.available():
 
2063
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
2064
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
2065
 
 
2066
    def test_add_unversioned(self):
 
2067
        branch, tt = self.get_branch_and_transform()
 
2068
        tt.new_file('file', tt.root, 'contents')
 
2069
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
2070
                          'message', strict=True)
 
2071
 
 
2072
    def test_modify_strict(self):
 
2073
        branch, tt = self.get_branch_and_transform()
 
2074
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2075
        tt.commit(branch, 'message', strict=True)
 
2076
        tt = TransformPreview(branch.basis_tree())
 
2077
        self.addCleanup(tt.finalize)
 
2078
        trans_id = tt.trans_id_file_id('file-id')
 
2079
        tt.delete_contents(trans_id)
 
2080
        tt.create_file('contents', trans_id)
 
2081
        tt.commit(branch, 'message', strict=True)
 
2082
 
 
2083
    def test_commit_malformed(self):
 
2084
        """Committing a malformed transform should raise an exception.
 
2085
 
 
2086
        In this case, we are adding a file without adding its parent.
 
2087
        """
 
2088
        branch, tt = self.get_branch_and_transform()
 
2089
        parent_id = tt.trans_id_file_id('parent-id')
 
2090
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2091
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2092
                          'message')
 
2093
 
 
2094
 
1812
2095
class MockTransform(object):
1813
2096
 
1814
2097
    def has_named_child(self, by_parent, parent_id, name):
1969
2252
        resolve_conflicts(tt)
1970
2253
 
1971
2254
 
 
2255
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2256
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2257
                  (False, False))
 
2258
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2259
              ('', ''), ('directory', 'directory'), (False, None))
 
2260
 
 
2261
 
1972
2262
class TestTransformPreview(tests.TestCaseWithTransport):
1973
2263
 
1974
2264
    def create_tree(self):
1975
2265
        tree = self.make_branch_and_tree('.')
1976
2266
        self.build_tree_contents([('a', 'content 1')])
 
2267
        tree.set_root_id('TREE_ROOT')
1977
2268
        tree.add('a', 'a-id')
1978
2269
        tree.commit('rev1', rev_id='rev1')
1979
2270
        return tree.branch.repository.revision_tree('rev1')
2046
2337
                          (False, False))],
2047
2338
                          list(preview_tree.iter_changes(revision_tree)))
2048
2339
 
2049
 
    def test_wrong_tree_value_error(self):
 
2340
    def test_include_unchanged_succeeds(self):
2050
2341
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2051
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
2052
 
                              preview_tree)
2053
 
        self.assertEqual('from_tree must be transform source tree.', str(e))
 
2342
        changes = preview_tree.iter_changes(revision_tree,
 
2343
                                            include_unchanged=True)
 
2344
        root = revision_tree.inventory.root.file_id
2054
2345
 
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))
 
2346
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2060
2347
 
2061
2348
    def test_specific_files(self):
2062
2349
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
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))
 
2350
        changes = preview_tree.iter_changes(revision_tree,
 
2351
                                            specific_files=[''])
 
2352
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2066
2353
 
2067
 
    def test_want_unversioned_value_error(self):
 
2354
    def test_want_unversioned(self):
2068
2355
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
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))
 
2356
        changes = preview_tree.iter_changes(revision_tree,
 
2357
                                            want_unversioned=True)
 
2358
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2072
2359
 
2073
2360
    def test_ignore_extra_trees_no_specific_files(self):
2074
2361
        # extra_trees is harmless without specific_files, so we'll silently
2084
2371
    def test_ignore_pb(self):
2085
2372
        # pb could be supported, but TT.iter_changes doesn't support it.
2086
2373
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2087
 
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2374
        preview_tree.iter_changes(revision_tree)
2088
2375
 
2089
2376
    def test_kind(self):
2090
2377
        revision_tree = self.create_tree()
2105
2392
        self.assertEqual(os.stat(limbo_path).st_mtime,
2106
2393
                         preview_tree.get_file_mtime('file-id'))
2107
2394
 
 
2395
    def test_get_file_mtime_renamed(self):
 
2396
        work_tree = self.make_branch_and_tree('tree')
 
2397
        self.build_tree(['tree/file'])
 
2398
        work_tree.add('file', 'file-id')
 
2399
        preview = TransformPreview(work_tree)
 
2400
        self.addCleanup(preview.finalize)
 
2401
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2402
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2403
        preview_tree = preview.get_preview_tree()
 
2404
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2405
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2406
 
2108
2407
    def test_get_file(self):
2109
2408
        preview = self.get_empty_preview()
2110
2409
        preview.new_file('file', preview.root, 'contents', 'file-id')
2258
2557
        self.assertEqual(('missing', None, None, None), summary)
2259
2558
 
2260
2559
    def test_file_content_summary_executable(self):
2261
 
        if not osutils.supports_executable():
2262
 
            raise TestNotApplicable()
2263
2560
        preview = self.get_empty_preview()
2264
2561
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2265
2562
        preview.set_executability(True, path_id)
2274
2571
        self.assertIs(None, summary[3])
2275
2572
 
2276
2573
    def test_change_executability(self):
2277
 
        if not osutils.supports_executable():
2278
 
            raise TestNotApplicable()
2279
2574
        tree = self.make_branch_and_tree('tree')
2280
2575
        self.build_tree(['tree/path'])
2281
2576
        tree.add('path')
2295
2590
        # size must be known
2296
2591
        self.assertEqual(len('contents'), summary[1])
2297
2592
        # not executable
2298
 
        if osutils.supports_executable():
2299
 
            self.assertEqual(False, summary[2])
2300
 
        else:
2301
 
            self.assertEqual(None, summary[2])
 
2593
        self.assertEqual(False, summary[2])
2302
2594
        # will not have hash (not cheap to determine)
2303
2595
        self.assertIs(None, summary[3])
2304
2596
 
2445
2737
 
2446
2738
    def test_walkdirs(self):
2447
2739
        preview = self.get_empty_preview()
2448
 
        preview.version_file('tree-root', preview.root)
 
2740
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2741
        # FIXME: new_directory should mark root.
 
2742
        preview.fixup_new_roots()
2449
2743
        preview_tree = preview.get_preview_tree()
2450
2744
        file_trans_id = preview.new_file('a', preview.root, 'contents',
2451
2745
                                         'a-id')
2452
2746
        expected = [(('', 'tree-root'),
2453
2747
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
2454
2748
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2749
 
 
2750
    def test_extras(self):
 
2751
        work_tree = self.make_branch_and_tree('tree')
 
2752
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2753
                         'tree/not-removed-file'])
 
2754
        work_tree.add(['removed-file', 'not-removed-file'])
 
2755
        preview = TransformPreview(work_tree)
 
2756
        self.addCleanup(preview.finalize)
 
2757
        preview.new_file('new-file', preview.root, 'contents')
 
2758
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2759
                         'new-versioned-id')
 
2760
        tree = preview.get_preview_tree()
 
2761
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2762
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2763
                         set(tree.extras()))
 
2764
 
 
2765
    def test_merge_into_preview(self):
 
2766
        work_tree = self.make_branch_and_tree('tree')
 
2767
        self.build_tree_contents([('tree/file','b\n')])
 
2768
        work_tree.add('file', 'file-id')
 
2769
        work_tree.commit('first commit')
 
2770
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2771
        self.build_tree_contents([('child/file','b\nc\n')])
 
2772
        child_tree.commit('child commit')
 
2773
        child_tree.lock_write()
 
2774
        self.addCleanup(child_tree.unlock)
 
2775
        work_tree.lock_write()
 
2776
        self.addCleanup(work_tree.unlock)
 
2777
        preview = TransformPreview(work_tree)
 
2778
        self.addCleanup(preview.finalize)
 
2779
        file_trans_id = preview.trans_id_file_id('file-id')
 
2780
        preview.delete_contents(file_trans_id)
 
2781
        preview.create_file('a\nb\n', file_trans_id)
 
2782
        preview_tree = preview.get_preview_tree()
 
2783
        merger = Merger.from_revision_ids(None, preview_tree,
 
2784
                                          child_tree.branch.last_revision(),
 
2785
                                          other_branch=child_tree.branch,
 
2786
                                          tree_branch=work_tree.branch)
 
2787
        merger.merge_type = Merge3Merger
 
2788
        tt = merger.make_merger().make_preview_transform()
 
2789
        self.addCleanup(tt.finalize)
 
2790
        final_tree = tt.get_preview_tree()
 
2791
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2792
 
 
2793
    def test_merge_preview_into_workingtree(self):
 
2794
        tree = self.make_branch_and_tree('tree')
 
2795
        tree.set_root_id('TREE_ROOT')
 
2796
        tt = TransformPreview(tree)
 
2797
        self.addCleanup(tt.finalize)
 
2798
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2799
        tree2 = self.make_branch_and_tree('tree2')
 
2800
        tree2.set_root_id('TREE_ROOT')
 
2801
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2802
                                         None, tree.basis_tree())
 
2803
        merger.merge_type = Merge3Merger
 
2804
        merger.do_merge()
 
2805
 
 
2806
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2807
        tree = self.make_branch_and_tree('tree')
 
2808
        self.build_tree_contents([('tree/foo', 'bar')])
 
2809
        tree.add('foo', 'foo-id')
 
2810
        tree.commit('foo')
 
2811
        tt = TransformPreview(tree)
 
2812
        self.addCleanup(tt.finalize)
 
2813
        trans_id = tt.trans_id_file_id('foo-id')
 
2814
        tt.delete_contents(trans_id)
 
2815
        tt.create_file('baz', trans_id)
 
2816
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2817
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2818
        pb = None
 
2819
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2820
                                         pb, tree.basis_tree())
 
2821
        merger.merge_type = Merge3Merger
 
2822
        merger.do_merge()
 
2823
 
 
2824
    def test_is_executable(self):
 
2825
        tree = self.make_branch_and_tree('tree')
 
2826
        preview = TransformPreview(tree)
 
2827
        self.addCleanup(preview.finalize)
 
2828
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2829
        preview_tree = preview.get_preview_tree()
 
2830
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2831
                                                           'tree/foo'))
 
2832
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2833
 
 
2834
    def test_commit_preview_tree(self):
 
2835
        tree = self.make_branch_and_tree('tree')
 
2836
        rev_id = tree.commit('rev1')
 
2837
        tree.branch.lock_write()
 
2838
        self.addCleanup(tree.branch.unlock)
 
2839
        tt = TransformPreview(tree)
 
2840
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2841
        self.addCleanup(tt.finalize)
 
2842
        preview = tt.get_preview_tree()
 
2843
        preview.set_parent_ids([rev_id])
 
2844
        builder = tree.branch.get_commit_builder([rev_id])
 
2845
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2846
        builder.finish_inventory()
 
2847
        rev2_id = builder.commit('rev2')
 
2848
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2849
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2850
 
 
2851
    def test_ascii_limbo_paths(self):
 
2852
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2853
        branch = self.make_branch('any')
 
2854
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2855
        tt = TransformPreview(tree)
 
2856
        self.addCleanup(tt.finalize)
 
2857
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2858
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2859
        limbo_path = tt._limbo_name(bar_id)
 
2860
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2861
 
 
2862
 
 
2863
class FakeSerializer(object):
 
2864
    """Serializer implementation that simply returns the input.
 
2865
 
 
2866
    The input is returned in the order used by pack.ContainerPushParser.
 
2867
    """
 
2868
    @staticmethod
 
2869
    def bytes_record(bytes, names):
 
2870
        return names, bytes
 
2871
 
 
2872
 
 
2873
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2874
 
 
2875
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2876
 
 
2877
    def get_preview(self, tree=None):
 
2878
        if tree is None:
 
2879
            tree = self.make_branch_and_tree('tree')
 
2880
        tt = TransformPreview(tree)
 
2881
        self.addCleanup(tt.finalize)
 
2882
        return tt
 
2883
 
 
2884
    def assertSerializesTo(self, expected, tt):
 
2885
        records = list(tt.serialize(FakeSerializer()))
 
2886
        self.assertEqual(expected, records)
 
2887
 
 
2888
    @staticmethod
 
2889
    def default_attribs():
 
2890
        return {
 
2891
            '_id_number': 1,
 
2892
            '_new_name': {},
 
2893
            '_new_parent': {},
 
2894
            '_new_executability': {},
 
2895
            '_new_id': {},
 
2896
            '_tree_path_ids': {'': 'new-0'},
 
2897
            '_removed_id': [],
 
2898
            '_removed_contents': [],
 
2899
            '_non_present_ids': {},
 
2900
            }
 
2901
 
 
2902
    def make_records(self, attribs, contents):
 
2903
        records = [
 
2904
            (((('attribs'),),), bencode.bencode(attribs))]
 
2905
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2906
        return records
 
2907
 
 
2908
    def creation_records(self):
 
2909
        attribs = self.default_attribs()
 
2910
        attribs['_id_number'] = 3
 
2911
        attribs['_new_name'] = {
 
2912
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2913
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2914
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2915
        attribs['_new_executability'] = {'new-1': 1}
 
2916
        contents = [
 
2917
            ('new-1', 'file', 'i 1\nbar\n'),
 
2918
            ('new-2', 'directory', ''),
 
2919
            ]
 
2920
        return self.make_records(attribs, contents)
 
2921
 
 
2922
    def test_serialize_creation(self):
 
2923
        tt = self.get_preview()
 
2924
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2925
        tt.new_directory('qux', tt.root, 'quxx')
 
2926
        self.assertSerializesTo(self.creation_records(), tt)
 
2927
 
 
2928
    def test_deserialize_creation(self):
 
2929
        tt = self.get_preview()
 
2930
        tt.deserialize(iter(self.creation_records()))
 
2931
        self.assertEqual(3, tt._id_number)
 
2932
        self.assertEqual({'new-1': u'foo\u1234',
 
2933
                          'new-2': 'qux'}, tt._new_name)
 
2934
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2935
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2936
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2937
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2938
        self.assertEqual({'new-1': 'file',
 
2939
                          'new-2': 'directory'}, tt._new_contents)
 
2940
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2941
        try:
 
2942
            foo_content = foo_limbo.read()
 
2943
        finally:
 
2944
            foo_limbo.close()
 
2945
        self.assertEqual('bar', foo_content)
 
2946
 
 
2947
    def symlink_creation_records(self):
 
2948
        attribs = self.default_attribs()
 
2949
        attribs['_id_number'] = 2
 
2950
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2951
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2952
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2953
        return self.make_records(attribs, contents)
 
2954
 
 
2955
    def test_serialize_symlink_creation(self):
 
2956
        self.requireFeature(tests.SymlinkFeature)
 
2957
        tt = self.get_preview()
 
2958
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2959
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2960
 
 
2961
    def test_deserialize_symlink_creation(self):
 
2962
        self.requireFeature(tests.SymlinkFeature)
 
2963
        tt = self.get_preview()
 
2964
        tt.deserialize(iter(self.symlink_creation_records()))
 
2965
        abspath = tt._limbo_name('new-1')
 
2966
        foo_content = osutils.readlink(abspath)
 
2967
        self.assertEqual(u'bar\u1234', foo_content)
 
2968
 
 
2969
    def make_destruction_preview(self):
 
2970
        tree = self.make_branch_and_tree('.')
 
2971
        self.build_tree([u'foo\u1234', 'bar'])
 
2972
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2973
        return self.get_preview(tree)
 
2974
 
 
2975
    def destruction_records(self):
 
2976
        attribs = self.default_attribs()
 
2977
        attribs['_id_number'] = 3
 
2978
        attribs['_removed_id'] = ['new-1']
 
2979
        attribs['_removed_contents'] = ['new-2']
 
2980
        attribs['_tree_path_ids'] = {
 
2981
            '': 'new-0',
 
2982
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2983
            'bar': 'new-2',
 
2984
            }
 
2985
        return self.make_records(attribs, [])
 
2986
 
 
2987
    def test_serialize_destruction(self):
 
2988
        tt = self.make_destruction_preview()
 
2989
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2990
        tt.unversion_file(foo_trans_id)
 
2991
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2992
        tt.delete_contents(bar_trans_id)
 
2993
        self.assertSerializesTo(self.destruction_records(), tt)
 
2994
 
 
2995
    def test_deserialize_destruction(self):
 
2996
        tt = self.make_destruction_preview()
 
2997
        tt.deserialize(iter(self.destruction_records()))
 
2998
        self.assertEqual({u'foo\u1234': 'new-1',
 
2999
                          'bar': 'new-2',
 
3000
                          '': tt.root}, tt._tree_path_ids)
 
3001
        self.assertEqual({'new-1': u'foo\u1234',
 
3002
                          'new-2': 'bar',
 
3003
                          tt.root: ''}, tt._tree_id_paths)
 
3004
        self.assertEqual(set(['new-1']), tt._removed_id)
 
3005
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
3006
 
 
3007
    def missing_records(self):
 
3008
        attribs = self.default_attribs()
 
3009
        attribs['_id_number'] = 2
 
3010
        attribs['_non_present_ids'] = {
 
3011
            'boo': 'new-1',}
 
3012
        return self.make_records(attribs, [])
 
3013
 
 
3014
    def test_serialize_missing(self):
 
3015
        tt = self.get_preview()
 
3016
        boo_trans_id = tt.trans_id_file_id('boo')
 
3017
        self.assertSerializesTo(self.missing_records(), tt)
 
3018
 
 
3019
    def test_deserialize_missing(self):
 
3020
        tt = self.get_preview()
 
3021
        tt.deserialize(iter(self.missing_records()))
 
3022
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
3023
 
 
3024
    def make_modification_preview(self):
 
3025
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3026
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3027
        tree = self.make_branch_and_tree('tree')
 
3028
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3029
        tree.add('file', 'file-id')
 
3030
        return self.get_preview(tree), LINES_TWO
 
3031
 
 
3032
    def modification_records(self):
 
3033
        attribs = self.default_attribs()
 
3034
        attribs['_id_number'] = 2
 
3035
        attribs['_tree_path_ids'] = {
 
3036
            'file': 'new-1',
 
3037
            '': 'new-0',}
 
3038
        attribs['_removed_contents'] = ['new-1']
 
3039
        contents = [('new-1', 'file',
 
3040
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
3041
        return self.make_records(attribs, contents)
 
3042
 
 
3043
    def test_serialize_modification(self):
 
3044
        tt, LINES = self.make_modification_preview()
 
3045
        trans_id = tt.trans_id_file_id('file-id')
 
3046
        tt.delete_contents(trans_id)
 
3047
        tt.create_file(LINES, trans_id)
 
3048
        self.assertSerializesTo(self.modification_records(), tt)
 
3049
 
 
3050
    def test_deserialize_modification(self):
 
3051
        tt, LINES = self.make_modification_preview()
 
3052
        tt.deserialize(iter(self.modification_records()))
 
3053
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3054
 
 
3055
    def make_kind_change_preview(self):
 
3056
        LINES = 'a\nb\nc\nd\n'
 
3057
        tree = self.make_branch_and_tree('tree')
 
3058
        self.build_tree(['tree/foo/'])
 
3059
        tree.add('foo', 'foo-id')
 
3060
        return self.get_preview(tree), LINES
 
3061
 
 
3062
    def kind_change_records(self):
 
3063
        attribs = self.default_attribs()
 
3064
        attribs['_id_number'] = 2
 
3065
        attribs['_tree_path_ids'] = {
 
3066
            'foo': 'new-1',
 
3067
            '': 'new-0',}
 
3068
        attribs['_removed_contents'] = ['new-1']
 
3069
        contents = [('new-1', 'file',
 
3070
                     'i 4\na\nb\nc\nd\n\n')]
 
3071
        return self.make_records(attribs, contents)
 
3072
 
 
3073
    def test_serialize_kind_change(self):
 
3074
        tt, LINES = self.make_kind_change_preview()
 
3075
        trans_id = tt.trans_id_file_id('foo-id')
 
3076
        tt.delete_contents(trans_id)
 
3077
        tt.create_file(LINES, trans_id)
 
3078
        self.assertSerializesTo(self.kind_change_records(), tt)
 
3079
 
 
3080
    def test_deserialize_kind_change(self):
 
3081
        tt, LINES = self.make_kind_change_preview()
 
3082
        tt.deserialize(iter(self.kind_change_records()))
 
3083
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3084
 
 
3085
    def make_add_contents_preview(self):
 
3086
        LINES = 'a\nb\nc\nd\n'
 
3087
        tree = self.make_branch_and_tree('tree')
 
3088
        self.build_tree(['tree/foo'])
 
3089
        tree.add('foo')
 
3090
        os.unlink('tree/foo')
 
3091
        return self.get_preview(tree), LINES
 
3092
 
 
3093
    def add_contents_records(self):
 
3094
        attribs = self.default_attribs()
 
3095
        attribs['_id_number'] = 2
 
3096
        attribs['_tree_path_ids'] = {
 
3097
            'foo': 'new-1',
 
3098
            '': 'new-0',}
 
3099
        contents = [('new-1', 'file',
 
3100
                     'i 4\na\nb\nc\nd\n\n')]
 
3101
        return self.make_records(attribs, contents)
 
3102
 
 
3103
    def test_serialize_add_contents(self):
 
3104
        tt, LINES = self.make_add_contents_preview()
 
3105
        trans_id = tt.trans_id_tree_path('foo')
 
3106
        tt.create_file(LINES, trans_id)
 
3107
        self.assertSerializesTo(self.add_contents_records(), tt)
 
3108
 
 
3109
    def test_deserialize_add_contents(self):
 
3110
        tt, LINES = self.make_add_contents_preview()
 
3111
        tt.deserialize(iter(self.add_contents_records()))
 
3112
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
3113
 
 
3114
    def test_get_parents_lines(self):
 
3115
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3116
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3117
        tree = self.make_branch_and_tree('tree')
 
3118
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3119
        tree.add('file', 'file-id')
 
3120
        tt = self.get_preview(tree)
 
3121
        trans_id = tt.trans_id_tree_path('file')
 
3122
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
3123
            tt._get_parents_lines(trans_id))
 
3124
 
 
3125
    def test_get_parents_texts(self):
 
3126
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
3127
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
3128
        tree = self.make_branch_and_tree('tree')
 
3129
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
3130
        tree.add('file', 'file-id')
 
3131
        tt = self.get_preview(tree)
 
3132
        trans_id = tt.trans_id_tree_path('file')
 
3133
        self.assertEqual((LINES_ONE,),
 
3134
            tt._get_parents_texts(trans_id))