~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Robert Collins
  • Date: 2010-01-28 18:05:44 UTC
  • mto: (4797.2.5 2.1)
  • mto: This revision was merged to the branch mainline in revision 4989.
  • Revision ID: robertc@robertcollins.net-20100128180544-6l8x7o7obaq7b51x
Tweak ConfigurableFileMerger to use class variables rather than requiring __init__ wrapping as future proofing for helper functions.

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)
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
 
 
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
 
139
193
    def test_hardlink(self):
140
194
        self.requireFeature(HardlinkFeature)
141
195
        transform, root = self.get_transform()
155
209
        transform, root = self.get_transform()
156
210
        self.wt.lock_tree_write()
157
211
        self.addCleanup(self.wt.unlock)
158
 
        trans_id = transform.new_file('name', root, 'contents', 
 
212
        trans_id = transform.new_file('name', root, 'contents',
159
213
                                      'my_pretties', True)
160
214
        oz = transform.new_directory('oz', root, 'oz-id')
161
215
        dorothy = transform.new_directory('dorothy', oz, 'dorothy-id')
162
 
        toto = transform.new_file('toto', dorothy, 'toto-contents', 
 
216
        toto = transform.new_file('toto', dorothy, 'toto-contents',
163
217
                                  'toto-id', False)
164
218
 
165
219
        self.assertEqual(len(transform.find_conflicts()), 0)
189
243
 
190
244
    def test_conflicts(self):
191
245
        transform, root = self.get_transform()
192
 
        trans_id = transform.new_file('name', root, 'contents', 
 
246
        trans_id = transform.new_file('name', root, 'contents',
193
247
                                      'my_pretties')
194
248
        self.assertEqual(len(transform.find_conflicts()), 0)
195
249
        trans_id2 = transform.new_file('name', root, 'Crontents', 'toto')
196
 
        self.assertEqual(transform.find_conflicts(), 
 
250
        self.assertEqual(transform.find_conflicts(),
197
251
                         [('duplicate', trans_id, trans_id2, 'name')])
198
252
        self.assertRaises(MalformedTransform, transform.apply)
199
253
        transform.adjust_path('name', trans_id, trans_id2)
200
 
        self.assertEqual(transform.find_conflicts(), 
 
254
        self.assertEqual(transform.find_conflicts(),
201
255
                         [('non-directory parent', trans_id)])
202
256
        tinman_id = transform.trans_id_tree_path('tinman')
203
257
        transform.adjust_path('name', tinman_id, trans_id2)
204
 
        self.assertEqual(transform.find_conflicts(), 
205
 
                         [('unversioned parent', tinman_id), 
 
258
        self.assertEqual(transform.find_conflicts(),
 
259
                         [('unversioned parent', tinman_id),
206
260
                          ('missing parent', tinman_id)])
207
261
        lion_id = transform.create_path('lion', root)
208
 
        self.assertEqual(transform.find_conflicts(), 
209
 
                         [('unversioned parent', tinman_id), 
 
262
        self.assertEqual(transform.find_conflicts(),
 
263
                         [('unversioned parent', tinman_id),
210
264
                          ('missing parent', tinman_id)])
211
265
        transform.adjust_path('name', lion_id, trans_id2)
212
 
        self.assertEqual(transform.find_conflicts(), 
 
266
        self.assertEqual(transform.find_conflicts(),
213
267
                         [('unversioned parent', lion_id),
214
268
                          ('missing parent', lion_id)])
215
269
        transform.version_file("Courage", lion_id)
216
 
        self.assertEqual(transform.find_conflicts(), 
217
 
                         [('missing parent', lion_id), 
 
270
        self.assertEqual(transform.find_conflicts(),
 
271
                         [('missing parent', lion_id),
218
272
                          ('versioning no contents', lion_id)])
219
273
        transform.adjust_path('name2', root, trans_id2)
220
 
        self.assertEqual(transform.find_conflicts(), 
 
274
        self.assertEqual(transform.find_conflicts(),
221
275
                         [('versioning no contents', lion_id)])
222
276
        transform.create_file('Contents, okay?', lion_id)
223
277
        transform.adjust_path('name2', trans_id2, trans_id2)
224
 
        self.assertEqual(transform.find_conflicts(), 
225
 
                         [('parent loop', trans_id2), 
 
278
        self.assertEqual(transform.find_conflicts(),
 
279
                         [('parent loop', trans_id2),
226
280
                          ('non-directory parent', trans_id2)])
227
281
        transform.adjust_path('name2', root, trans_id2)
228
282
        oz_id = transform.new_directory('oz', root)
229
283
        transform.set_executability(True, oz_id)
230
 
        self.assertEqual(transform.find_conflicts(), 
 
284
        self.assertEqual(transform.find_conflicts(),
231
285
                         [('unversioned executability', oz_id)])
232
286
        transform.version_file('oz-id', oz_id)
233
 
        self.assertEqual(transform.find_conflicts(), 
 
287
        self.assertEqual(transform.find_conflicts(),
234
288
                         [('non-file executability', oz_id)])
235
289
        transform.set_executability(None, oz_id)
236
290
        tip_id = transform.new_file('tip', oz_id, 'ozma', 'tip-id')
245
299
        self.assert_('oz/tip' in transform2._tree_path_ids)
246
300
        self.assertEqual(fp.get_path(newtip), pathjoin('oz', 'tip'))
247
301
        self.assertEqual(len(result), 2)
248
 
        self.assertEqual((result[0][0], result[0][1]), 
 
302
        self.assertEqual((result[0][0], result[0][1]),
249
303
                         ('duplicate', newtip))
250
 
        self.assertEqual((result[1][0], result[1][2]), 
 
304
        self.assertEqual((result[1][0], result[1][2]),
251
305
                         ('duplicate id', newtip))
252
306
        transform2.finalize()
253
307
        transform3 = TreeTransform(self.wt)
254
308
        self.addCleanup(transform3.finalize)
255
309
        oz_id = transform3.trans_id_tree_file_id('oz-id')
256
310
        transform3.delete_contents(oz_id)
257
 
        self.assertEqual(transform3.find_conflicts(), 
 
311
        self.assertEqual(transform3.find_conflicts(),
258
312
                         [('missing parent', oz_id)])
259
313
        root_id = transform3.root
260
314
        tip_id = transform3.trans_id_tree_file_id('tip-id')
369
423
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
370
424
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
371
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
 
372
438
    def test_add_del(self):
373
439
        start, root = self.get_transform()
374
440
        start.new_directory('a', root, 'a')
387
453
        self.addCleanup(unversion.finalize)
388
454
        parent = unversion.trans_id_tree_path('parent')
389
455
        unversion.unversion_file(parent)
390
 
        self.assertEqual(unversion.find_conflicts(), 
 
456
        self.assertEqual(unversion.find_conflicts(),
391
457
                         [('unversioned parent', parent_id)])
392
458
        file_id = unversion.trans_id_tree_file_id('child-id')
393
459
        unversion.unversion_file(file_id)
413
479
        mangle_tree.adjust_path('name2', root, name1)
414
480
        mangle_tree.adjust_path('name1', root, name2)
415
481
 
416
 
        #tests for deleting parent directories 
 
482
        #tests for deleting parent directories
417
483
        ddir = mangle_tree.trans_id_tree_file_id('ddir')
418
484
        mangle_tree.delete_contents(ddir)
419
485
        dfile = mangle_tree.trans_id_tree_file_id('dfile')
448
514
        create_tree,root = self.get_transform()
449
515
        newdir = create_tree.new_directory('selftest', root, 'selftest-id')
450
516
        create_tree.new_file('blackbox.py', newdir, 'hello1', 'blackbox-id')
451
 
        create_tree.apply()        
 
517
        create_tree.apply()
452
518
        mangle_tree,root = self.get_transform()
453
519
        selftest = mangle_tree.trans_id_tree_file_id('selftest-id')
454
520
        blackbox = mangle_tree.trans_id_tree_file_id('blackbox-id')
462
528
        bzrlib = create_tree.new_directory('bzrlib', root, 'bzrlib-id')
463
529
        tests = create_tree.new_directory('tests', bzrlib, 'tests-id')
464
530
        blackbox = create_tree.new_directory('blackbox', tests, 'blackbox-id')
465
 
        create_tree.new_file('test_too_much.py', blackbox, 'hello1', 
 
531
        create_tree.new_file('test_too_much.py', blackbox, 'hello1',
466
532
                             'test_too_much-id')
467
 
        create_tree.apply()        
 
533
        create_tree.apply()
468
534
        mangle_tree,root = self.get_transform()
469
535
        bzrlib = mangle_tree.trans_id_tree_file_id('bzrlib-id')
470
536
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
471
537
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
472
538
        mangle_tree.adjust_path('selftest', bzrlib, tests)
473
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
539
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
474
540
        mangle_tree.set_executability(True, test_too_much)
475
541
        mangle_tree.apply()
476
542
 
477
543
    def test_both_rename3(self):
478
544
        create_tree,root = self.get_transform()
479
545
        tests = create_tree.new_directory('tests', root, 'tests-id')
480
 
        create_tree.new_file('test_too_much.py', tests, 'hello1', 
 
546
        create_tree.new_file('test_too_much.py', tests, 'hello1',
481
547
                             'test_too_much-id')
482
 
        create_tree.apply()        
 
548
        create_tree.apply()
483
549
        mangle_tree,root = self.get_transform()
484
550
        tests = mangle_tree.trans_id_tree_file_id('tests-id')
485
551
        test_too_much = mangle_tree.trans_id_tree_file_id('test_too_much-id')
486
552
        mangle_tree.adjust_path('selftest', root, tests)
487
 
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much) 
 
553
        mangle_tree.adjust_path('blackbox.py', tests, test_too_much)
488
554
        mangle_tree.set_executability(True, test_too_much)
489
555
        mangle_tree.apply()
490
556
 
503
569
        newdir = move_id.new_directory('dir', root, 'newdir')
504
570
        move_id.adjust_path('name2', newdir, name1)
505
571
        move_id.apply()
506
 
        
 
572
 
507
573
    def test_replace_dangling_ie(self):
508
574
        create_tree, root = self.get_transform()
509
575
        # prepare tree
525
591
        resolve_conflicts(replace)
526
592
        replace.apply()
527
593
 
528
 
    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
 
529
599
        self.requireFeature(SymlinkFeature)
530
 
        transform,root = self.get_transform()
 
600
        transform, root = self.get_transform()
531
601
        oz_id = transform.new_directory('oz', root, 'oz-id')
532
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
 
602
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
533
603
                                       '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)            
 
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)
537
607
        transform.set_executability(True, wiz_id)
538
 
        self.assertEqual(transform.find_conflicts(), 
 
608
        self.assertEqual(transform.find_conflicts(),
539
609
                         [('non-file executability', wiz_id)])
540
610
        transform.set_executability(None, wiz_id)
541
611
        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')
 
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')
548
630
 
549
631
    def test_unable_create_symlink(self):
550
632
        def tt_helper():
574
656
        create.apply()
575
657
        conflicts,root = self.get_transform()
576
658
        # set up duplicate entry, duplicate id
577
 
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy', 
 
659
        new_dorothy = conflicts.new_file('dorothy', root, 'dorothy',
578
660
                                         'dorothy-id')
579
661
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
580
662
        oz = conflicts.trans_id_tree_file_id('oz-id')
604
686
        tt, emerald, oz, old_dorothy, new_dorothy = self.get_conflicted()
605
687
        raw_conflicts = resolve_conflicts(tt)
606
688
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
607
 
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved', 
 
689
        duplicate = DuplicateEntry('Moved existing file to', 'dorothy.moved',
608
690
                                   'dorothy', None, 'dorothy-id')
609
691
        self.assertEqual(cooked_conflicts[0], duplicate)
610
 
        duplicate_id = DuplicateID('Unversioned existing file', 
 
692
        duplicate_id = DuplicateID('Unversioned existing file',
611
693
                                   'dorothy.moved', 'dorothy', None,
612
694
                                   'dorothy-id')
613
695
        self.assertEqual(cooked_conflicts[1], duplicate_id)
621
703
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
622
704
                                               'oz-id')
623
705
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
624
 
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
 
706
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity',
625
707
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
626
708
        self.assertEqual(cooked_conflicts[4], deleted_parent)
627
709
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
730
812
        create.apply()
731
813
        transform, root = self.get_transform()
732
814
        transform.adjust_root_path('oldroot', fun)
733
 
        new_root=transform.trans_id_tree_path('')
 
815
        new_root = transform.trans_id_tree_path('')
734
816
        transform.version_file('new-root', new_root)
735
817
        transform.apply()
736
818
 
750
832
 
751
833
    def test_set_executability_order(self):
752
834
        """Ensure that executability behaves the same, no matter what order.
753
 
        
 
835
 
754
836
        - create file and set executability simultaneously
755
837
        - create file and set executability afterward
756
838
        - unsetting the executability of a file whose executability has not been
1410
1492
        # textual merge
1411
1493
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1412
1494
        # three-way text conflict
1413
 
        self.assertEqual(this.wt.get_file('b').read(), 
 
1495
        self.assertEqual(this.wt.get_file('b').read(),
1414
1496
                         conflict_text('b', 'b2'))
1415
1497
        # OTHER wins
1416
1498
        self.assertEqual(this.wt.get_file('c').read(), 'c2')
1420
1502
        self.assertEqual(this.wt.get_file('e').read(), 'e2')
1421
1503
        # No change
1422
1504
        self.assertEqual(this.wt.get_file('f').read(), 'f')
1423
 
        # Correct correct results when THIS == OTHER 
 
1505
        # Correct correct results when THIS == OTHER
1424
1506
        self.assertEqual(this.wt.get_file('g').read(), 'g')
1425
1507
        # Text conflict when THIS & OTHER are text and BASE is dir
1426
 
        self.assertEqual(this.wt.get_file('h').read(), 
 
1508
        self.assertEqual(this.wt.get_file('h').read(),
1427
1509
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1428
1510
        self.assertEqual(this.wt.get_file_byname('h.THIS').read(),
1429
1511
                         '1\n2\n3\n4\n')
1430
1512
        self.assertEqual(this.wt.get_file_byname('h.OTHER').read(),
1431
1513
                         'h\ni\nj\nk\n')
1432
1514
        self.assertEqual(file_kind(this.wt.abspath('h.BASE')), 'directory')
1433
 
        self.assertEqual(this.wt.get_file('i').read(), 
 
1515
        self.assertEqual(this.wt.get_file('i').read(),
1434
1516
                         conflict_text('1\n2\n3\n4\n', 'h\ni\nj\nk\n'))
1435
1517
        self.assertEqual(this.wt.get_file_byname('i.THIS').read(),
1436
1518
                         '1\n2\n3\n4\n')
1460
1542
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1461
1543
            tg.tt.new_file('c', tg.root, 'c', 'c')
1462
1544
            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'), 
 
1545
        targets = ((base, 'base-e', 'base-f', None, None),
 
1546
                   (this, 'other-e', 'this-f', 'other-g', 'this-h'),
1465
1547
                   (other, 'other-e', None, 'other-g', 'other-h'))
1466
1548
        for tg, e_target, f_target, g_target, h_target in targets:
1467
 
            for link, target in (('e', e_target), ('f', f_target), 
 
1549
            for link, target in (('e', e_target), ('f', f_target),
1468
1550
                                 ('g', g_target), ('h', h_target)):
1469
1551
                if target is not None:
1470
1552
                    tg.tt.new_symlink(link, tg.root, target, link)
1496
1578
        base = TransformGroup("BASE", root_id)
1497
1579
        this = TransformGroup("THIS", root_id)
1498
1580
        other = TransformGroup("OTHER", root_id)
1499
 
        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')
1500
1582
                                   for t in [base, this, other]]
1501
 
        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')
1502
1584
                                   for t in [base, this, other]]
1503
1585
        base.tt.new_directory('c', base_a, 'c')
1504
1586
        this.tt.new_directory('c1', this_a, 'c')
1529
1611
        base = TransformGroup("BASE", root_id)
1530
1612
        this = TransformGroup("THIS", root_id)
1531
1613
        other = TransformGroup("OTHER", root_id)
1532
 
        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')
1533
1615
                                   for t in [base, this, other]]
1534
 
        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')
1535
1617
                                   for t in [base, this, other]]
1536
1618
 
1537
1619
        base.tt.new_file('g', base_a, 'g', 'g')
1628
1710
        os.symlink('foo', 'target2/symlink')
1629
1711
        build_tree(source.basis_tree(), target)
1630
1712
        self.assertEqual([], target.conflicts())
1631
 
        
 
1713
 
1632
1714
    def test_directory_conflict_handling(self):
1633
1715
        """Ensure that when building trees, conflict handling is done"""
1634
1716
        source = self.make_branch_and_tree('source')
1690
1772
        target = self.make_branch_and_tree('target')
1691
1773
        self.build_tree(['target/name'])
1692
1774
        target.add('name')
1693
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1775
        self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1694
1776
            build_tree, source.basis_tree(), target)
1695
1777
 
1696
1778
    def test_build_tree_rename_count(self):
1840
1922
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1841
1923
        self.assertTrue(source.is_executable('file1-id'))
1842
1924
 
 
1925
    def install_rot13_content_filter(self, pattern):
 
1926
        original_registry = filters._reset_registry()
 
1927
        def restore_registry():
 
1928
            filters._reset_registry(original_registry)
 
1929
        self.addCleanup(restore_registry)
 
1930
        def rot13(chunks, context=None):
 
1931
            return [''.join(chunks).encode('rot13')]
 
1932
        rot13filter = filters.ContentFilter(rot13, rot13)
 
1933
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
1934
        os.mkdir(self.test_home_dir + '/.bazaar')
 
1935
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
1936
        f = open(rules_filename, 'wb')
 
1937
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
1938
        f.close()
 
1939
        def uninstall_rules():
 
1940
            os.remove(rules_filename)
 
1941
            rules.reset_rules()
 
1942
        self.addCleanup(uninstall_rules)
 
1943
        rules.reset_rules()
 
1944
 
 
1945
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
1946
        """build_tree will not hardlink files that have content filtering rules
 
1947
        applied to them (but will still hardlink other files from the same tree
 
1948
        if it can).
 
1949
        """
 
1950
        self.requireFeature(HardlinkFeature)
 
1951
        self.install_rot13_content_filter('file1')
 
1952
        source = self.create_ab_tree()
 
1953
        target = self.make_branch_and_tree('target')
 
1954
        revision_tree = source.basis_tree()
 
1955
        revision_tree.lock_read()
 
1956
        self.addCleanup(revision_tree.unlock)
 
1957
        build_tree(revision_tree, target, source, hardlink=True)
 
1958
        target.lock_read()
 
1959
        self.addCleanup(target.unlock)
 
1960
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1961
        source_stat = os.stat('source/file1')
 
1962
        target_stat = os.stat('target/file1')
 
1963
        self.assertNotEqual(source_stat, target_stat)
 
1964
        source_stat = os.stat('source/file2')
 
1965
        target_stat = os.stat('target/file2')
 
1966
        self.assertEqualStat(source_stat, target_stat)
 
1967
 
1843
1968
    def test_case_insensitive_build_tree_inventory(self):
 
1969
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
1970
            or tests.CaseInsCasePresFilenameFeature.available()):
 
1971
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1844
1972
        source = self.make_branch_and_tree('source')
1845
1973
        self.build_tree(['source/file', 'source/FILE'])
1846
1974
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1854
1982
        self.assertEqual('FILE', target.id2path('upper-id'))
1855
1983
 
1856
1984
 
 
1985
class TestCommitTransform(tests.TestCaseWithTransport):
 
1986
 
 
1987
    def get_branch(self):
 
1988
        tree = self.make_branch_and_tree('tree')
 
1989
        tree.lock_write()
 
1990
        self.addCleanup(tree.unlock)
 
1991
        tree.commit('empty commit')
 
1992
        return tree.branch
 
1993
 
 
1994
    def get_branch_and_transform(self):
 
1995
        branch = self.get_branch()
 
1996
        tt = TransformPreview(branch.basis_tree())
 
1997
        self.addCleanup(tt.finalize)
 
1998
        return branch, tt
 
1999
 
 
2000
    def test_commit_wrong_basis(self):
 
2001
        branch = self.get_branch()
 
2002
        basis = branch.repository.revision_tree(
 
2003
            _mod_revision.NULL_REVISION)
 
2004
        tt = TransformPreview(basis)
 
2005
        self.addCleanup(tt.finalize)
 
2006
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
2007
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
2008
                         str(e))
 
2009
 
 
2010
    def test_empy_commit(self):
 
2011
        branch, tt = self.get_branch_and_transform()
 
2012
        rev = tt.commit(branch, 'my message')
 
2013
        self.assertEqual(2, branch.revno())
 
2014
        repo = branch.repository
 
2015
        self.assertEqual('my message', repo.get_revision(rev).message)
 
2016
 
 
2017
    def test_merge_parents(self):
 
2018
        branch, tt = self.get_branch_and_transform()
 
2019
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
2020
        self.assertEqual(['rev1b', 'rev1c'],
 
2021
                         branch.basis_tree().get_parent_ids()[1:])
 
2022
 
 
2023
    def test_first_commit(self):
 
2024
        branch = self.make_branch('branch')
 
2025
        branch.lock_write()
 
2026
        self.addCleanup(branch.unlock)
 
2027
        tt = TransformPreview(branch.basis_tree())
 
2028
        self.addCleanup(tt.finalize)
 
2029
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
2030
        rev = tt.commit(branch, 'my message')
 
2031
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
2032
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
2033
                            branch.last_revision())
 
2034
 
 
2035
    def test_first_commit_with_merge_parents(self):
 
2036
        branch = self.make_branch('branch')
 
2037
        branch.lock_write()
 
2038
        self.addCleanup(branch.unlock)
 
2039
        tt = TransformPreview(branch.basis_tree())
 
2040
        self.addCleanup(tt.finalize)
 
2041
        e = self.assertRaises(ValueError, tt.commit, branch,
 
2042
                          'my message', ['rev1b-id'])
 
2043
        self.assertEqual('Cannot supply merge parents for first commit.',
 
2044
                         str(e))
 
2045
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
2046
 
 
2047
    def test_add_files(self):
 
2048
        branch, tt = self.get_branch_and_transform()
 
2049
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2050
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
2051
        if SymlinkFeature.available():
 
2052
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
2053
        rev = tt.commit(branch, 'message')
 
2054
        tree = branch.basis_tree()
 
2055
        self.assertEqual('file', tree.id2path('file-id'))
 
2056
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
2057
        self.assertEqual('dir', tree.id2path('dir-id'))
 
2058
        if SymlinkFeature.available():
 
2059
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
2060
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
2061
 
 
2062
    def test_add_unversioned(self):
 
2063
        branch, tt = self.get_branch_and_transform()
 
2064
        tt.new_file('file', tt.root, 'contents')
 
2065
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
2066
                          'message', strict=True)
 
2067
 
 
2068
    def test_modify_strict(self):
 
2069
        branch, tt = self.get_branch_and_transform()
 
2070
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2071
        tt.commit(branch, 'message', strict=True)
 
2072
        tt = TransformPreview(branch.basis_tree())
 
2073
        self.addCleanup(tt.finalize)
 
2074
        trans_id = tt.trans_id_file_id('file-id')
 
2075
        tt.delete_contents(trans_id)
 
2076
        tt.create_file('contents', trans_id)
 
2077
        tt.commit(branch, 'message', strict=True)
 
2078
 
 
2079
    def test_commit_malformed(self):
 
2080
        """Committing a malformed transform should raise an exception.
 
2081
 
 
2082
        In this case, we are adding a file without adding its parent.
 
2083
        """
 
2084
        branch, tt = self.get_branch_and_transform()
 
2085
        parent_id = tt.trans_id_file_id('parent-id')
 
2086
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2087
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2088
                          'message')
 
2089
 
 
2090
 
1857
2091
class MockTransform(object):
1858
2092
 
1859
2093
    def has_named_child(self, by_parent, parent_id, name):
2026
2260
    def create_tree(self):
2027
2261
        tree = self.make_branch_and_tree('.')
2028
2262
        self.build_tree_contents([('a', 'content 1')])
 
2263
        tree.set_root_id('TREE_ROOT')
2029
2264
        tree.add('a', 'a-id')
2030
2265
        tree.commit('rev1', rev_id='rev1')
2031
2266
        return tree.branch.repository.revision_tree('rev1')
2153
2388
        self.assertEqual(os.stat(limbo_path).st_mtime,
2154
2389
                         preview_tree.get_file_mtime('file-id'))
2155
2390
 
 
2391
    def test_get_file_mtime_renamed(self):
 
2392
        work_tree = self.make_branch_and_tree('tree')
 
2393
        self.build_tree(['tree/file'])
 
2394
        work_tree.add('file', 'file-id')
 
2395
        preview = TransformPreview(work_tree)
 
2396
        self.addCleanup(preview.finalize)
 
2397
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2398
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2399
        preview_tree = preview.get_preview_tree()
 
2400
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2401
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2402
 
2156
2403
    def test_get_file(self):
2157
2404
        preview = self.get_empty_preview()
2158
2405
        preview.new_file('file', preview.root, 'contents', 'file-id')
2306
2553
        self.assertEqual(('missing', None, None, None), summary)
2307
2554
 
2308
2555
    def test_file_content_summary_executable(self):
2309
 
        if not osutils.supports_executable():
2310
 
            raise TestNotApplicable()
2311
2556
        preview = self.get_empty_preview()
2312
2557
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2313
2558
        preview.set_executability(True, path_id)
2322
2567
        self.assertIs(None, summary[3])
2323
2568
 
2324
2569
    def test_change_executability(self):
2325
 
        if not osutils.supports_executable():
2326
 
            raise TestNotApplicable()
2327
2570
        tree = self.make_branch_and_tree('tree')
2328
2571
        self.build_tree(['tree/path'])
2329
2572
        tree.add('path')
2343
2586
        # size must be known
2344
2587
        self.assertEqual(len('contents'), summary[1])
2345
2588
        # not executable
2346
 
        if osutils.supports_executable():
2347
 
            self.assertEqual(False, summary[2])
2348
 
        else:
2349
 
            self.assertEqual(None, summary[2])
 
2589
        self.assertEqual(False, summary[2])
2350
2590
        # will not have hash (not cheap to determine)
2351
2591
        self.assertIs(None, summary[3])
2352
2592
 
2493
2733
 
2494
2734
    def test_walkdirs(self):
2495
2735
        preview = self.get_empty_preview()
2496
 
        preview.version_file('tree-root', preview.root)
 
2736
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2737
        # FIXME: new_directory should mark root.
 
2738
        preview.fixup_new_roots()
2497
2739
        preview_tree = preview.get_preview_tree()
2498
2740
        file_trans_id = preview.new_file('a', preview.root, 'contents',
2499
2741
                                         'a-id')
2530
2772
        self.addCleanup(work_tree.unlock)
2531
2773
        preview = TransformPreview(work_tree)
2532
2774
        self.addCleanup(preview.finalize)
2533
 
        preview_tree = preview.get_preview_tree()
2534
2775
        file_trans_id = preview.trans_id_file_id('file-id')
2535
2776
        preview.delete_contents(file_trans_id)
2536
2777
        preview.create_file('a\nb\n', file_trans_id)
2537
2778
        pb = progress.DummyProgress()
 
2779
        preview_tree = preview.get_preview_tree()
2538
2780
        merger = Merger.from_revision_ids(pb, preview_tree,
2539
2781
                                          child_tree.branch.last_revision(),
2540
2782
                                          other_branch=child_tree.branch,
2547
2789
 
2548
2790
    def test_merge_preview_into_workingtree(self):
2549
2791
        tree = self.make_branch_and_tree('tree')
 
2792
        tree.set_root_id('TREE_ROOT')
2550
2793
        tt = TransformPreview(tree)
2551
2794
        self.addCleanup(tt.finalize)
2552
2795
        tt.new_file('name', tt.root, 'content', 'file-id')
2553
2796
        tree2 = self.make_branch_and_tree('tree2')
 
2797
        tree2.set_root_id('TREE_ROOT')
2554
2798
        pb = progress.DummyProgress()
2555
2799
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2556
2800
                                         pb, tree.basis_tree())
2585
2829
                                                           'tree/foo'))
2586
2830
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
2587
2831
 
 
2832
    def test_commit_preview_tree(self):
 
2833
        tree = self.make_branch_and_tree('tree')
 
2834
        rev_id = tree.commit('rev1')
 
2835
        tree.branch.lock_write()
 
2836
        self.addCleanup(tree.branch.unlock)
 
2837
        tt = TransformPreview(tree)
 
2838
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2839
        self.addCleanup(tt.finalize)
 
2840
        preview = tt.get_preview_tree()
 
2841
        preview.set_parent_ids([rev_id])
 
2842
        builder = tree.branch.get_commit_builder([rev_id])
 
2843
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2844
        builder.finish_inventory()
 
2845
        rev2_id = builder.commit('rev2')
 
2846
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2847
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2848
 
 
2849
    def test_ascii_limbo_paths(self):
 
2850
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2851
        branch = self.make_branch('any')
 
2852
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2853
        tt = TransformPreview(tree)
 
2854
        self.addCleanup(tt.finalize)
 
2855
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2856
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2857
        limbo_path = tt._limbo_name(bar_id)
 
2858
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2859
 
2588
2860
 
2589
2861
class FakeSerializer(object):
2590
2862
    """Serializer implementation that simply returns the input.
2685
2957
        self.assertSerializesTo(self.symlink_creation_records(), tt)
2686
2958
 
2687
2959
    def test_deserialize_symlink_creation(self):
 
2960
        self.requireFeature(tests.SymlinkFeature)
2688
2961
        tt = self.get_preview()
2689
2962
        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')
 
2963
        abspath = tt._limbo_name('new-1')
 
2964
        foo_content = osutils.readlink(abspath)
2692
2965
        self.assertEqual(u'bar\u1234', foo_content)
2693
2966
 
2694
2967
    def make_destruction_preview(self):