~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Andrew Bennetts
  • Date: 2010-01-12 03:53:21 UTC
  • mfrom: (4948 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4964.
  • Revision ID: andrew.bennetts@canonical.com-20100112035321-hofpz5p10224ryj3
Merge lp:bzr, resolving conflicts.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008, 2009, 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)
48
50
    TestCaseInTempDir,
49
51
    TestSkipped,
50
52
    )
51
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
52
 
                              resolve_conflicts, cook_conflicts, 
 
53
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
 
54
                              resolve_conflicts, cook_conflicts,
53
55
                              build_tree, get_backup_name,
54
56
                              _FileMover, resolve_checkout,
55
57
                              TransformPreview, create_from_tree)
56
 
from bzrlib.util import bencode
57
58
 
58
59
 
59
60
class TestTreeTransform(tests.TestCaseWithTransport):
129
130
        self.assertIs(self.wt.is_executable('my_pretties2'), False)
130
131
        self.assertEqual('directory', file_kind(self.wt.abspath('oz')))
131
132
        self.assertEqual(len(modified_paths), 3)
132
 
        tree_mod_paths = [self.wt.id2abspath(f) for f in 
 
133
        tree_mod_paths = [self.wt.id2abspath(f) for f in
133
134
                          ('ozzie', 'my_pretties', 'my_pretties2')]
134
135
        self.assertSubset(tree_mod_paths, modified_paths)
135
136
        # is it safe to finalize repeatedly?
136
137
        transform.finalize()
137
138
        transform.finalize()
138
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
 
139
163
    def test_hardlink(self):
140
164
        self.requireFeature(HardlinkFeature)
141
165
        transform, root = self.get_transform()
155
179
        transform, root = self.get_transform()
156
180
        self.wt.lock_tree_write()
157
181
        self.addCleanup(self.wt.unlock)
158
 
        trans_id = transform.new_file('name', root, 'contents', 
 
182
        trans_id = transform.new_file('name', root, 'contents',
159
183
                                      'my_pretties', True)
160
184
        oz = transform.new_directory('oz', root, 'oz-id')
161
185
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
162
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
186
        toto = transform.new_file('toto', dorothy, 'toto-contents',
163
187
                                  'toto-id', False)
164
188
 
165
189
        self.assertEqual(len(transform.find_conflicts()), 0)
189
213
 
190
214
    def test_conflicts(self):
191
215
        transform, root = self.get_transform()
192
 
        trans_id = transform.new_file('name', root, 'contents', 
 
216
        trans_id = transform.new_file('name', root, 'contents',
193
217
                                      'my_pretties')
194
218
        self.assertEqual(len(transform.find_conflicts()), 0)
195
219
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
196
 
        self.assertEqual(transform.find_conflicts(), 
 
220
        self.assertEqual(transform.find_conflicts(),
197
221
                         [('duplicate', trans_id, trans_id2, 'name')])
198
222
        self.assertRaises(MalformedTransform, transform.apply)
199
223
        transform.adjust_path('name', trans_id, trans_id2)
200
 
        self.assertEqual(transform.find_conflicts(), 
 
224
        self.assertEqual(transform.find_conflicts(),
201
225
                         [('non-directory parent', trans_id)])
202
226
        tinman_id = transform.trans_id_tree_path('tinman')
203
227
        transform.adjust_path('name', tinman_id, trans_id2)
204
 
        self.assertEqual(transform.find_conflicts(), 
205
 
                         [('unversioned parent', tinman_id), 
 
228
        self.assertEqual(transform.find_conflicts(),
 
229
                         [('unversioned parent', tinman_id),
206
230
                          ('missing parent', tinman_id)])
207
231
        lion_id = transform.create_path('lion', root)
208
 
        self.assertEqual(transform.find_conflicts(), 
209
 
                         [('unversioned parent', tinman_id), 
 
232
        self.assertEqual(transform.find_conflicts(),
 
233
                         [('unversioned parent', tinman_id),
210
234
                          ('missing parent', tinman_id)])
211
235
        transform.adjust_path('name', lion_id, trans_id2)
212
 
        self.assertEqual(transform.find_conflicts(), 
 
236
        self.assertEqual(transform.find_conflicts(),
213
237
                         [('unversioned parent', lion_id),
214
238
                          ('missing parent', lion_id)])
215
239
        transform.version_file("Courage", lion_id)
216
 
        self.assertEqual(transform.find_conflicts(), 
217
 
                         [('missing parent', lion_id), 
 
240
        self.assertEqual(transform.find_conflicts(),
 
241
                         [('missing parent', lion_id),
218
242
                          ('versioning no contents', lion_id)])
219
243
        transform.adjust_path('name2', root, trans_id2)
220
 
        self.assertEqual(transform.find_conflicts(), 
 
244
        self.assertEqual(transform.find_conflicts(),
221
245
                         [('versioning no contents', lion_id)])
222
246
        transform.create_file('Contents, okay?', lion_id)
223
247
        transform.adjust_path('name2', trans_id2, trans_id2)
224
 
        self.assertEqual(transform.find_conflicts(), 
225
 
                         [('parent loop', trans_id2), 
 
248
        self.assertEqual(transform.find_conflicts(),
 
249
                         [('parent loop', trans_id2),
226
250
                          ('non-directory parent', trans_id2)])
227
251
        transform.adjust_path('name2', root, trans_id2)
228
252
        oz_id = transform.new_directory('oz', root)
229
253
        transform.set_executability(True, oz_id)
230
 
        self.assertEqual(transform.find_conflicts(), 
 
254
        self.assertEqual(transform.find_conflicts(),
231
255
                         [('unversioned executability', oz_id)])
232
256
        transform.version_file('oz-id', oz_id)
233
 
        self.assertEqual(transform.find_conflicts(), 
 
257
        self.assertEqual(transform.find_conflicts(),
234
258
                         [('non-file executability', oz_id)])
235
259
        transform.set_executability(None, oz_id)
236
260
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
245
269
        self.assert_('oz/tip' in transform2._tree_path_ids)
246
270
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
247
271
        self.assertEqual(len(result), 2)
248
 
        self.assertEqual((result[0][0], result[0][1]), 
 
272
        self.assertEqual((result[0][0], result[0][1]),
249
273
                         ('duplicate', newtip))
250
 
        self.assertEqual((result[1][0], result[1][2]), 
 
274
        self.assertEqual((result[1][0], result[1][2]),
251
275
                         ('duplicate id', newtip))
252
276
        transform2.finalize()
253
277
        transform3 = TreeTransform(self.wt)
254
278
        self.addCleanup(transform3.finalize)
255
279
        oz_id = transform3.trans_id_tree_file_id('oz-id')
256
280
        transform3.delete_contents(oz_id)
257
 
        self.assertEqual(transform3.find_conflicts(), 
 
281
        self.assertEqual(transform3.find_conflicts(),
258
282
                         [('missing parent', oz_id)])
259
283
        root_id = transform3.root
260
284
        tip_id = transform3.trans_id_tree_file_id('tip-id')
369
393
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
370
394
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
371
395
 
 
396
    def test_adjust_path_updates_child_limbo_names(self):
 
397
        tree = self.make_branch_and_tree('tree')
 
398
        transform = TreeTransform(tree)
 
399
        self.addCleanup(transform.finalize)
 
400
        foo_id = transform.new_directory('foo', transform.root)
 
401
        bar_id = transform.new_directory('bar', foo_id)
 
402
        baz_id = transform.new_directory('baz', bar_id)
 
403
        qux_id = transform.new_directory('qux', baz_id)
 
404
        transform.adjust_path('quxx', foo_id, bar_id)
 
405
        self.assertStartsWith(transform._limbo_name(qux_id),
 
406
                              transform._limbo_name(bar_id))
 
407
 
372
408
    def test_add_del(self):
373
409
        start, root = self.get_transform()
374
410
        start.new_directory('a', root, 'a')
387
423
        self.addCleanup(unversion.finalize)
388
424
        parent = unversion.trans_id_tree_path('parent')
389
425
        unversion.unversion_file(parent)
390
 
        self.assertEqual(unversion.find_conflicts(), 
 
426
        self.assertEqual(unversion.find_conflicts(),
391
427
                         [('unversioned parent', parent_id)])
392
428
        file_id = unversion.trans_id_tree_file_id('child-id')
393
429
        unversion.unversion_file(file_id)
413
449
        mangle_tree.adjust_path('name2', root, name1)
414
450
        mangle_tree.adjust_path('name1', root, name2)
415
451
 
416
 
        #tests for deleting parent directories 
 
452
        #tests for deleting parent directories
417
453
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
418
454
        mangle_tree.delete_contents(ddir)
419
455
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
448
484
        create_tree,root = self.get_transform()
449
485
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
450
486
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
451
 
        create_tree.apply()        
 
487
        create_tree.apply()
452
488
        mangle_tree,root = self.get_transform()
453
489
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
454
490
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
462
498
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
463
499
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
464
500
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
465
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
501
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
466
502
                             'test_too_much-id')
467
 
        create_tree.apply()        
 
503
        create_tree.apply()
468
504
        mangle_tree,root = self.get_transform()
469
505
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
470
506
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
471
507
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
472
508
        mangle_tree.adjust_path('selftest', bzrlib, tests)
473
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
509
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
474
510
        mangle_tree.set_executability(True, test_too_much)
475
511
        mangle_tree.apply()
476
512
 
477
513
    def test_both_rename3(self):
478
514
        create_tree,root = self.get_transform()
479
515
        tests = create_tree.new_directory('tests', root, 'tests-id')
480
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
516
        create_tree.new_file('test_too_much.py', tests, 'hello1',
481
517
                             'test_too_much-id')
482
 
        create_tree.apply()        
 
518
        create_tree.apply()
483
519
        mangle_tree,root = self.get_transform()
484
520
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
485
521
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
486
522
        mangle_tree.adjust_path('selftest', root, tests)
487
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
523
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
488
524
        mangle_tree.set_executability(True, test_too_much)
489
525
        mangle_tree.apply()
490
526
 
503
539
        newdir = move_id.new_directory('dir', root, 'newdir')
504
540
        move_id.adjust_path('name2', newdir, name1)
505
541
        move_id.apply()
506
 
        
 
542
 
507
543
    def test_replace_dangling_ie(self):
508
544
        create_tree, root = self.get_transform()
509
545
        # prepare tree
525
561
        resolve_conflicts(replace)
526
562
        replace.apply()
527
563
 
528
 
    def test_symlinks(self):
 
564
    def _test_symlinks(self, link_name1,link_target1,
 
565
                       link_name2, link_target2):
 
566
 
 
567
        def ozpath(p): return 'oz/' + p
 
568
 
529
569
        self.requireFeature(SymlinkFeature)
530
 
        transform,root = self.get_transform()
 
570
        transform, root = self.get_transform()
531
571
        oz_id = transform.new_directory('oz', root, 'oz-id')
532
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
572
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
533
573
                                       'wizard-id')
534
 
        wiz_id = transform.create_path('wizard2', oz_id)
535
 
        transform.create_symlink('behind_curtain', wiz_id)
536
 
        transform.version_file('wiz-id2', wiz_id)            
 
574
        wiz_id = transform.create_path(link_name2, oz_id)
 
575
        transform.create_symlink(link_target2, wiz_id)
 
576
        transform.version_file('wiz-id2', wiz_id)
537
577
        transform.set_executability(True, wiz_id)
538
 
        self.assertEqual(transform.find_conflicts(), 
 
578
        self.assertEqual(transform.find_conflicts(),
539
579
                         [('non-file executability', wiz_id)])
540
580
        transform.set_executability(None, wiz_id)
541
581
        transform.apply()
542
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
543
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
544
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')), 
545
 
                         'behind_curtain')
546
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
547
 
                         'wizard-target')
 
582
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
 
583
        self.assertEqual('symlink',
 
584
                         file_kind(self.wt.abspath(ozpath(link_name1))))
 
585
        self.assertEqual(link_target2,
 
586
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
 
587
        self.assertEqual(link_target1,
 
588
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
 
589
 
 
590
    def test_symlinks(self):
 
591
        self._test_symlinks('wizard', 'wizard-target',
 
592
                            'wizard2', 'behind_curtain')
 
593
 
 
594
    def test_symlinks_unicode(self):
 
595
        self.requireFeature(tests.UnicodeFilenameFeature)
 
596
        self._test_symlinks(u'\N{Euro Sign}wizard',
 
597
                            u'wizard-targ\N{Euro Sign}t',
 
598
                            u'\N{Euro Sign}wizard2',
 
599
                            u'b\N{Euro Sign}hind_curtain')
548
600
 
549
601
    def test_unable_create_symlink(self):
550
602
        def tt_helper():
574
626
        create.apply()
575
627
        conflicts,root = self.get_transform()
576
628
        # set up duplicate entry, duplicate id
577
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
629
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
578
630
                                         'dorothy-id')
579
631
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
580
632
        oz = conflicts.trans_id_tree_file_id('oz-id')
604
656
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
605
657
        raw_conflicts = resolve_conflicts(tt)
606
658
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
607
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
659
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
608
660
                                   'dorothy', None, 'dorothy-id')
609
661
        self.assertEqual(cooked_conflicts[0], duplicate)
610
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
662
        duplicate_id = DuplicateID('Unversioned existing file',
611
663
                                   'dorothy.moved', 'dorothy', None,
612
664
                                   'dorothy-id')
613
665
        self.assertEqual(cooked_conflicts[1], duplicate_id)
621
673
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
622
674
                                               'oz-id')
623
675
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
624
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
676
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
625
677
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
626
678
        self.assertEqual(cooked_conflicts[4], deleted_parent)
627
679
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
750
802
 
751
803
    def test_set_executability_order(self):
752
804
        """Ensure that executability behaves the same, no matter what order.
753
 
        
 
805
 
754
806
        - create file and set executability simultaneously
755
807
        - create file and set executability afterward
756
808
        - unsetting the executability of a file whose executability has not been
1410
1462
        # textual merge
1411
1463
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1412
1464
        # three-way text conflict
1413
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1465
        self.assertEqual(this.wt.get_file('b').read(),
1414
1466
                         conflict_text('b', 'b2'))
1415
1467
        # OTHER wins
1416
1468
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1420
1472
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1421
1473
        # No change
1422
1474
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1423
 
        # Correct correct results when THIS == OTHER 
 
1475
        # Correct correct results when THIS == OTHER
1424
1476
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1425
1477
        # Text conflict when THIS & OTHER are text and BASE is dir
1426
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1478
        self.assertEqual(this.wt.get_file('h').read(),
1427
1479
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1428
1480
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1429
1481
                         '1\n2\n3\n4\n')
1430
1482
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1431
1483
                         'h\ni\nj\nk\n')
1432
1484
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1433
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1485
        self.assertEqual(this.wt.get_file('i').read(),
1434
1486
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1435
1487
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1436
1488
                         '1\n2\n3\n4\n')
1460
1512
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1461
1513
            tg.tt.new_file('c', tg.root, 'c', 'c')
1462
1514
            tg.tt.new_symlink('d', tg.root, tg.name, 'd')
1463
 
        targets = ((base, 'base-e', 'base-f', None, None), 
1464
 
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'), 
 
1515
        targets = ((base, 'base-e', 'base-f', None, None),
 
1516
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
1465
1517
                   (other, 'other-e', None, 'other-g', 'other-h'))
1466
1518
        for tg, e_target, f_target, g_target, h_target in targets:
1467
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1519
            for link, target in (('e', e_target), ('f', f_target),
1468
1520
                                 ('g', g_target), ('h', h_target)):
1469
1521
                if target is not None:
1470
1522
                    tg.tt.new_symlink(link, tg.root, target, link)
1496
1548
        base = TransformGroup("BASE", root_id)
1497
1549
        this = TransformGroup("THIS", root_id)
1498
1550
        other = TransformGroup("OTHER", root_id)
1499
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1551
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1500
1552
                                   for t in [base, this, other]]
1501
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1553
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1502
1554
                                   for t in [base, this, other]]
1503
1555
        base.tt.new_directory('c', base_a, 'c')
1504
1556
        this.tt.new_directory('c1', this_a, 'c')
1529
1581
        base = TransformGroup("BASE", root_id)
1530
1582
        this = TransformGroup("THIS", root_id)
1531
1583
        other = TransformGroup("OTHER", root_id)
1532
 
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
 
1584
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a')
1533
1585
                                   for t in [base, this, other]]
1534
 
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
 
1586
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b')
1535
1587
                                   for t in [base, this, other]]
1536
1588
 
1537
1589
        base.tt.new_file('g', base_a, 'g', 'g')
1628
1680
        os.symlink('foo', 'target2/symlink')
1629
1681
        build_tree(source.basis_tree(), target)
1630
1682
        self.assertEqual([], target.conflicts())
1631
 
        
 
1683
 
1632
1684
    def test_directory_conflict_handling(self):
1633
1685
        """Ensure that when building trees, conflict handling is done"""
1634
1686
        source = self.make_branch_and_tree('source')
1690
1742
        target = self.make_branch_and_tree('target')
1691
1743
        self.build_tree(['target/name'])
1692
1744
        target.add('name')
1693
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1745
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1694
1746
            build_tree, source.basis_tree(), target)
1695
1747
 
1696
1748
    def test_build_tree_rename_count(self):
1840
1892
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1841
1893
        self.assertTrue(source.is_executable('file1-id'))
1842
1894
 
 
1895
    def install_rot13_content_filter(self, pattern):
 
1896
        original_registry = filters._reset_registry()
 
1897
        def restore_registry():
 
1898
            filters._reset_registry(original_registry)
 
1899
        self.addCleanup(restore_registry)
 
1900
        def rot13(chunks, context=None):
 
1901
            return [''.join(chunks).encode('rot13')]
 
1902
        rot13filter = filters.ContentFilter(rot13, rot13)
 
1903
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
1904
        os.mkdir(self.test_home_dir + '/.bazaar')
 
1905
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
1906
        f = open(rules_filename, 'wb')
 
1907
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
1908
        f.close()
 
1909
        def uninstall_rules():
 
1910
            os.remove(rules_filename)
 
1911
            rules.reset_rules()
 
1912
        self.addCleanup(uninstall_rules)
 
1913
        rules.reset_rules()
 
1914
 
 
1915
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
1916
        """build_tree will not hardlink files that have content filtering rules
 
1917
        applied to them (but will still hardlink other files from the same tree
 
1918
        if it can).
 
1919
        """
 
1920
        self.requireFeature(HardlinkFeature)
 
1921
        self.install_rot13_content_filter('file1')
 
1922
        source = self.create_ab_tree()
 
1923
        target = self.make_branch_and_tree('target')
 
1924
        revision_tree = source.basis_tree()
 
1925
        revision_tree.lock_read()
 
1926
        self.addCleanup(revision_tree.unlock)
 
1927
        build_tree(revision_tree, target, source, hardlink=True)
 
1928
        target.lock_read()
 
1929
        self.addCleanup(target.unlock)
 
1930
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1931
        source_stat = os.stat('source/file1')
 
1932
        target_stat = os.stat('target/file1')
 
1933
        self.assertNotEqual(source_stat, target_stat)
 
1934
        source_stat = os.stat('source/file2')
 
1935
        target_stat = os.stat('target/file2')
 
1936
        self.assertEqualStat(source_stat, target_stat)
 
1937
 
1843
1938
    def test_case_insensitive_build_tree_inventory(self):
 
1939
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1940
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1941
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1844
1942
        source = self.make_branch_and_tree('source')
1845
1943
        self.build_tree(['source/file', 'source/FILE'])
1846
1944
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1854
1952
        self.assertEqual('FILE', target.id2path('upper-id'))
1855
1953
 
1856
1954
 
 
1955
class TestCommitTransform(tests.TestCaseWithTransport):
 
1956
 
 
1957
    def get_branch(self):
 
1958
        tree = self.make_branch_and_tree('tree')
 
1959
        tree.lock_write()
 
1960
        self.addCleanup(tree.unlock)
 
1961
        tree.commit('empty commit')
 
1962
        return tree.branch
 
1963
 
 
1964
    def get_branch_and_transform(self):
 
1965
        branch = self.get_branch()
 
1966
        tt = TransformPreview(branch.basis_tree())
 
1967
        self.addCleanup(tt.finalize)
 
1968
        return branch, tt
 
1969
 
 
1970
    def test_commit_wrong_basis(self):
 
1971
        branch = self.get_branch()
 
1972
        basis = branch.repository.revision_tree(
 
1973
            _mod_revision.NULL_REVISION)
 
1974
        tt = TransformPreview(basis)
 
1975
        self.addCleanup(tt.finalize)
 
1976
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
1977
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
1978
                         str(e))
 
1979
 
 
1980
    def test_empy_commit(self):
 
1981
        branch, tt = self.get_branch_and_transform()
 
1982
        rev = tt.commit(branch, 'my message')
 
1983
        self.assertEqual(2, branch.revno())
 
1984
        repo = branch.repository
 
1985
        self.assertEqual('my message', repo.get_revision(rev).message)
 
1986
 
 
1987
    def test_merge_parents(self):
 
1988
        branch, tt = self.get_branch_and_transform()
 
1989
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
1990
        self.assertEqual(['rev1b', 'rev1c'],
 
1991
                         branch.basis_tree().get_parent_ids()[1:])
 
1992
 
 
1993
    def test_first_commit(self):
 
1994
        branch = self.make_branch('branch')
 
1995
        branch.lock_write()
 
1996
        self.addCleanup(branch.unlock)
 
1997
        tt = TransformPreview(branch.basis_tree())
 
1998
        self.addCleanup(tt.finalize)
 
1999
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
2000
        rev = tt.commit(branch, 'my message')
 
2001
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
2002
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
2003
                            branch.last_revision())
 
2004
 
 
2005
    def test_first_commit_with_merge_parents(self):
 
2006
        branch = self.make_branch('branch')
 
2007
        branch.lock_write()
 
2008
        self.addCleanup(branch.unlock)
 
2009
        tt = TransformPreview(branch.basis_tree())
 
2010
        self.addCleanup(tt.finalize)
 
2011
        e = self.assertRaises(ValueError, tt.commit, branch,
 
2012
                          'my message', ['rev1b-id'])
 
2013
        self.assertEqual('Cannot supply merge parents for first commit.',
 
2014
                         str(e))
 
2015
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
2016
 
 
2017
    def test_add_files(self):
 
2018
        branch, tt = self.get_branch_and_transform()
 
2019
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2020
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
2021
        if SymlinkFeature.available():
 
2022
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
2023
        rev = tt.commit(branch, 'message')
 
2024
        tree = branch.basis_tree()
 
2025
        self.assertEqual('file', tree.id2path('file-id'))
 
2026
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
2027
        self.assertEqual('dir', tree.id2path('dir-id'))
 
2028
        if SymlinkFeature.available():
 
2029
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
2030
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
2031
 
 
2032
    def test_add_unversioned(self):
 
2033
        branch, tt = self.get_branch_and_transform()
 
2034
        tt.new_file('file', tt.root, 'contents')
 
2035
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
2036
                          'message', strict=True)
 
2037
 
 
2038
    def test_modify_strict(self):
 
2039
        branch, tt = self.get_branch_and_transform()
 
2040
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2041
        tt.commit(branch, 'message', strict=True)
 
2042
        tt = TransformPreview(branch.basis_tree())
 
2043
        self.addCleanup(tt.finalize)
 
2044
        trans_id = tt.trans_id_file_id('file-id')
 
2045
        tt.delete_contents(trans_id)
 
2046
        tt.create_file('contents', trans_id)
 
2047
        tt.commit(branch, 'message', strict=True)
 
2048
 
 
2049
    def test_commit_malformed(self):
 
2050
        """Committing a malformed transform should raise an exception.
 
2051
 
 
2052
        In this case, we are adding a file without adding its parent.
 
2053
        """
 
2054
        branch, tt = self.get_branch_and_transform()
 
2055
        parent_id = tt.trans_id_file_id('parent-id')
 
2056
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2057
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2058
                          'message')
 
2059
 
 
2060
 
1857
2061
class MockTransform(object):
1858
2062
 
1859
2063
    def has_named_child(self, by_parent, parent_id, name):
2026
2230
    def create_tree(self):
2027
2231
        tree = self.make_branch_and_tree('.')
2028
2232
        self.build_tree_contents([('a', 'content 1')])
 
2233
        tree.set_root_id('TREE_ROOT')
2029
2234
        tree.add('a', 'a-id')
2030
2235
        tree.commit('rev1', rev_id='rev1')
2031
2236
        return tree.branch.repository.revision_tree('rev1')
2153
2358
        self.assertEqual(os.stat(limbo_path).st_mtime,
2154
2359
                         preview_tree.get_file_mtime('file-id'))
2155
2360
 
 
2361
    def test_get_file_mtime_renamed(self):
 
2362
        work_tree = self.make_branch_and_tree('tree')
 
2363
        self.build_tree(['tree/file'])
 
2364
        work_tree.add('file', 'file-id')
 
2365
        preview = TransformPreview(work_tree)
 
2366
        self.addCleanup(preview.finalize)
 
2367
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2368
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2369
        preview_tree = preview.get_preview_tree()
 
2370
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2371
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2372
 
2156
2373
    def test_get_file(self):
2157
2374
        preview = self.get_empty_preview()
2158
2375
        preview.new_file('file', preview.root, 'contents', 'file-id')
2306
2523
        self.assertEqual(('missing', None, None, None), summary)
2307
2524
 
2308
2525
    def test_file_content_summary_executable(self):
2309
 
        if not osutils.supports_executable():
2310
 
            raise TestNotApplicable()
2311
2526
        preview = self.get_empty_preview()
2312
2527
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2313
2528
        preview.set_executability(True, path_id)
2322
2537
        self.assertIs(None, summary[3])
2323
2538
 
2324
2539
    def test_change_executability(self):
2325
 
        if not osutils.supports_executable():
2326
 
            raise TestNotApplicable()
2327
2540
        tree = self.make_branch_and_tree('tree')
2328
2541
        self.build_tree(['tree/path'])
2329
2542
        tree.add('path')
2343
2556
        # size must be known
2344
2557
        self.assertEqual(len('contents'), summary[1])
2345
2558
        # not executable
2346
 
        if osutils.supports_executable():
2347
 
            self.assertEqual(False, summary[2])
2348
 
        else:
2349
 
            self.assertEqual(None, summary[2])
 
2559
        self.assertEqual(False, summary[2])
2350
2560
        # will not have hash (not cheap to determine)
2351
2561
        self.assertIs(None, summary[3])
2352
2562
 
2493
2703
 
2494
2704
    def test_walkdirs(self):
2495
2705
        preview = self.get_empty_preview()
2496
 
        preview.version_file('tree-root', preview.root)
 
2706
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2707
        # FIXME: new_directory should mark root.
 
2708
        preview.adjust_path('', ROOT_PARENT, root)
2497
2709
        preview_tree = preview.get_preview_tree()
2498
2710
        file_trans_id = preview.new_file('a', preview.root, 'contents',
2499
2711
                                         'a-id')
2530
2742
        self.addCleanup(work_tree.unlock)
2531
2743
        preview = TransformPreview(work_tree)
2532
2744
        self.addCleanup(preview.finalize)
2533
 
        preview_tree = preview.get_preview_tree()
2534
2745
        file_trans_id = preview.trans_id_file_id('file-id')
2535
2746
        preview.delete_contents(file_trans_id)
2536
2747
        preview.create_file('a\nb\n', file_trans_id)
2537
2748
        pb = progress.DummyProgress()
 
2749
        preview_tree = preview.get_preview_tree()
2538
2750
        merger = Merger.from_revision_ids(pb, preview_tree,
2539
2751
                                          child_tree.branch.last_revision(),
2540
2752
                                          other_branch=child_tree.branch,
2547
2759
 
2548
2760
    def test_merge_preview_into_workingtree(self):
2549
2761
        tree = self.make_branch_and_tree('tree')
 
2762
        tree.set_root_id('TREE_ROOT')
2550
2763
        tt = TransformPreview(tree)
2551
2764
        self.addCleanup(tt.finalize)
2552
2765
        tt.new_file('name', tt.root, 'content', 'file-id')
2553
2766
        tree2 = self.make_branch_and_tree('tree2')
 
2767
        tree2.set_root_id('TREE_ROOT')
2554
2768
        pb = progress.DummyProgress()
2555
2769
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2556
2770
                                         pb, tree.basis_tree())
2585
2799
                                                           'tree/foo'))
2586
2800
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
2587
2801
 
 
2802
    def test_commit_preview_tree(self):
 
2803
        tree = self.make_branch_and_tree('tree')
 
2804
        rev_id = tree.commit('rev1')
 
2805
        tree.branch.lock_write()
 
2806
        self.addCleanup(tree.branch.unlock)
 
2807
        tt = TransformPreview(tree)
 
2808
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2809
        self.addCleanup(tt.finalize)
 
2810
        preview = tt.get_preview_tree()
 
2811
        preview.set_parent_ids([rev_id])
 
2812
        builder = tree.branch.get_commit_builder([rev_id])
 
2813
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2814
        builder.finish_inventory()
 
2815
        rev2_id = builder.commit('rev2')
 
2816
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2817
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2818
 
 
2819
    def test_ascii_limbo_paths(self):
 
2820
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2821
        branch = self.make_branch('any')
 
2822
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2823
        tt = TransformPreview(tree)
 
2824
        self.addCleanup(tt.finalize)
 
2825
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2826
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2827
        limbo_path = tt._limbo_name(bar_id)
 
2828
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2829
 
2588
2830
 
2589
2831
class FakeSerializer(object):
2590
2832
    """Serializer implementation that simply returns the input.
2685
2927
        self.assertSerializesTo(self.symlink_creation_records(), tt)
2686
2928
 
2687
2929
    def test_deserialize_symlink_creation(self):
 
2930
        self.requireFeature(tests.SymlinkFeature)
2688
2931
        tt = self.get_preview()
2689
2932
        tt.deserialize(iter(self.symlink_creation_records()))
2690
 
        # XXX readlink should be returning unicode, not utf-8
2691
 
        foo_content = os.readlink(tt._limbo_name('new-1')).decode('utf-8')
 
2933
        abspath = tt._limbo_name('new-1')
 
2934
        foo_content = osutils.readlink(abspath)
2692
2935
        self.assertEqual(u'bar\u1234', foo_content)
2693
2936
 
2694
2937
    def make_destruction_preview(self):