/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2008-04-27 07:02:04 UTC
  • mfrom: (3381.1.4 fetch-locking)
  • Revision ID: pqm@pqm.ubuntu.com-20080427070204-4t5flfqnnmr6bmiw
Remove locking from generators

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Canonical Ltd
2
 
 
 
1
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
18
import os
19
19
 
20
20
import bzrlib
21
 
from bzrlib.tests import TestCaseWithTransport
 
21
from bzrlib import (
 
22
    bzrdir,
 
23
    errors,
 
24
    lockdir,
 
25
    osutils,
 
26
    tests,
 
27
    )
22
28
from bzrlib.branch import Branch
23
29
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
24
 
from bzrlib.workingtree import WorkingTree
25
 
from bzrlib.commit import Commit
 
30
from bzrlib.commit import Commit, NullCommitReporter
26
31
from bzrlib.config import BranchConfig
27
32
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
28
33
                           LockContention)
 
34
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
 
35
from bzrlib.workingtree import WorkingTree
29
36
 
30
37
 
31
38
# TODO: Test commit with some added, and added-but-missing files
45
52
        return "bzrlib.ahook bzrlib.ahook"
46
53
 
47
54
 
 
55
class CapturingReporter(NullCommitReporter):
 
56
    """This reporter captures the calls made to it for evaluation later."""
 
57
 
 
58
    def __init__(self):
 
59
        # a list of the calls this received
 
60
        self.calls = []
 
61
 
 
62
    def snapshot_change(self, change, path):
 
63
        self.calls.append(('change', change, path))
 
64
 
 
65
    def deleted(self, file_id):
 
66
        self.calls.append(('deleted', file_id))
 
67
 
 
68
    def missing(self, path):
 
69
        self.calls.append(('missing', path))
 
70
 
 
71
    def renamed(self, change, old_path, new_path):
 
72
        self.calls.append(('renamed', change, old_path, new_path))
 
73
 
 
74
    def is_verbose(self):
 
75
        return True
 
76
 
 
77
 
48
78
class TestCommit(TestCaseWithTransport):
49
79
 
50
80
    def test_simple_commit(self):
66
96
        eq(rev.message, 'add hello')
67
97
 
68
98
        tree1 = b.repository.revision_tree(rh[0])
 
99
        tree1.lock_read()
69
100
        text = tree1.get_file_text(file_id)
70
 
        eq(text, 'hello world')
 
101
        tree1.unlock()
 
102
        self.assertEqual('hello world', text)
71
103
 
72
104
        tree2 = b.repository.revision_tree(rh[1])
73
 
        eq(tree2.get_file_text(file_id), 'version 2')
 
105
        tree2.lock_read()
 
106
        text = tree2.get_file_text(file_id)
 
107
        tree2.unlock()
 
108
        self.assertEqual('version 2', text)
74
109
 
75
110
    def test_delete_commit(self):
76
111
        """Test a commit with a deleted file"""
139
174
        eq(b.revno(), 3)
140
175
 
141
176
        tree2 = b.repository.revision_tree('test@rev-2')
 
177
        tree2.lock_read()
 
178
        self.addCleanup(tree2.unlock)
142
179
        self.assertTrue(tree2.has_filename('hello'))
143
180
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
144
181
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
145
182
        
146
183
        tree3 = b.repository.revision_tree('test@rev-3')
 
184
        tree3.lock_read()
 
185
        self.addCleanup(tree3.unlock)
147
186
        self.assertFalse(tree3.has_filename('hello'))
148
187
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
149
188
 
160
199
 
161
200
        eq = self.assertEquals
162
201
        tree1 = b.repository.revision_tree('test@rev-1')
 
202
        tree1.lock_read()
 
203
        self.addCleanup(tree1.unlock)
163
204
        eq(tree1.id2path('hello-id'), 'hello')
164
205
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
165
206
        self.assertFalse(tree1.has_filename('fruity'))
168
209
        eq(ie.revision, 'test@rev-1')
169
210
 
170
211
        tree2 = b.repository.revision_tree('test@rev-2')
 
212
        tree2.lock_read()
 
213
        self.addCleanup(tree2.unlock)
171
214
        eq(tree2.id2path('hello-id'), 'fruity')
172
215
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
173
216
        self.check_inventory_shape(tree2.inventory, ['fruity'])
197
240
        wt.move(['hello'], 'a')
198
241
        r2 = 'test@rev-2'
199
242
        wt.commit('two', rev_id=r2, allow_pointless=False)
200
 
        self.check_inventory_shape(wt.read_working_inventory(),
201
 
                                   ['a', 'a/hello', 'b'])
 
243
        wt.lock_read()
 
244
        try:
 
245
            self.check_inventory_shape(wt.read_working_inventory(),
 
246
                                       ['a/', 'a/hello', 'b/'])
 
247
        finally:
 
248
            wt.unlock()
202
249
 
203
250
        wt.move(['b'], 'a')
204
251
        r3 = 'test@rev-3'
205
252
        wt.commit('three', rev_id=r3, allow_pointless=False)
206
 
        self.check_inventory_shape(wt.read_working_inventory(),
207
 
                                   ['a', 'a/hello', 'a/b'])
208
 
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
209
 
                                   ['a', 'a/hello', 'a/b'])
 
253
        wt.lock_read()
 
254
        try:
 
255
            self.check_inventory_shape(wt.read_working_inventory(),
 
256
                                       ['a/', 'a/hello', 'a/b/'])
 
257
            self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
258
                                       ['a/', 'a/hello', 'a/b/'])
 
259
        finally:
 
260
            wt.unlock()
210
261
 
211
262
        wt.move(['a/hello'], 'a/b')
212
263
        r4 = 'test@rev-4'
213
264
        wt.commit('four', rev_id=r4, allow_pointless=False)
214
 
        self.check_inventory_shape(wt.read_working_inventory(),
215
 
                                   ['a', 'a/b/hello', 'a/b'])
 
265
        wt.lock_read()
 
266
        try:
 
267
            self.check_inventory_shape(wt.read_working_inventory(),
 
268
                                       ['a/', 'a/b/hello', 'a/b/'])
 
269
        finally:
 
270
            wt.unlock()
216
271
 
217
272
        inv = b.repository.get_revision_inventory(r4)
218
273
        eq(inv['hello-id'].revision, r4)
219
274
        eq(inv['a-id'].revision, r1)
220
275
        eq(inv['b-id'].revision, r3)
221
 
        
 
276
 
222
277
    def test_removed_commit(self):
223
278
        """Commit with a removed file"""
224
279
        wt = self.make_branch_and_tree('.')
319
374
                                                      allow_pointless=True,
320
375
                                                      rev_id='B',
321
376
                                                      working_tree=wt)
322
 
            self.assertEqual(Testament.from_revision(branch.repository,
323
 
                             'B').as_short_text(),
 
377
            def sign(text):
 
378
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
379
            self.assertEqual(sign(Testament.from_revision(branch.repository,
 
380
                             'B').as_short_text()),
324
381
                             branch.repository.get_signature_text('B'))
325
382
        finally:
326
383
            bzrlib.gpg.GPGStrategy = oldstrategy
392
449
            self.assertRaises(LockContention, wt.commit, 'silly')
393
450
        finally:
394
451
            master_branch.unlock()
 
452
 
 
453
    def test_commit_bound_merge(self):
 
454
        # see bug #43959; commit of a merge in a bound branch fails to push
 
455
        # the new commit into the master
 
456
        master_branch = self.make_branch('master')
 
457
        bound_tree = self.make_branch_and_tree('bound')
 
458
        bound_tree.branch.bind(master_branch)
 
459
 
 
460
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
 
461
        bound_tree.add(['content_file'])
 
462
        bound_tree.commit(message='woo!')
 
463
 
 
464
        other_bzrdir = master_branch.bzrdir.sprout('other')
 
465
        other_tree = other_bzrdir.open_workingtree()
 
466
 
 
467
        # do a commit to the the other branch changing the content file so
 
468
        # that our commit after merging will have a merged revision in the
 
469
        # content file history.
 
470
        self.build_tree_contents([('other/content_file', 'change in other\n')])
 
471
        other_tree.commit('change in other')
 
472
 
 
473
        # do a merge into the bound branch from other, and then change the
 
474
        # content file locally to force a new revision (rather than using the
 
475
        # revision from other). This forces extra processing in commit.
 
476
        bound_tree.merge_from_branch(other_tree.branch)
 
477
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
 
478
 
 
479
        # before #34959 was fixed, this failed with 'revision not present in
 
480
        # weave' when trying to implicitly push from the bound branch to the master
 
481
        bound_tree.commit(message='commit of merge in bound tree')
 
482
 
 
483
    def test_commit_reporting_after_merge(self):
 
484
        # when doing a commit of a merge, the reporter needs to still 
 
485
        # be called for each item that is added/removed/deleted.
 
486
        this_tree = self.make_branch_and_tree('this')
 
487
        # we need a bunch of files and dirs, to perform one action on each.
 
488
        self.build_tree([
 
489
            'this/dirtorename/',
 
490
            'this/dirtoreparent/',
 
491
            'this/dirtoleave/',
 
492
            'this/dirtoremove/',
 
493
            'this/filetoreparent',
 
494
            'this/filetorename',
 
495
            'this/filetomodify',
 
496
            'this/filetoremove',
 
497
            'this/filetoleave']
 
498
            )
 
499
        this_tree.add([
 
500
            'dirtorename',
 
501
            'dirtoreparent',
 
502
            'dirtoleave',
 
503
            'dirtoremove',
 
504
            'filetoreparent',
 
505
            'filetorename',
 
506
            'filetomodify',
 
507
            'filetoremove',
 
508
            'filetoleave']
 
509
            )
 
510
        this_tree.commit('create_files')
 
511
        other_dir = this_tree.bzrdir.sprout('other')
 
512
        other_tree = other_dir.open_workingtree()
 
513
        other_tree.lock_write()
 
514
        # perform the needed actions on the files and dirs.
 
515
        try:
 
516
            other_tree.rename_one('dirtorename', 'renameddir')
 
517
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
 
518
            other_tree.rename_one('filetorename', 'renamedfile')
 
519
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
 
520
            other_tree.remove(['dirtoremove', 'filetoremove'])
 
521
            self.build_tree_contents([
 
522
                ('other/newdir/', ),
 
523
                ('other/filetomodify', 'new content'),
 
524
                ('other/newfile', 'new file content')])
 
525
            other_tree.add('newfile')
 
526
            other_tree.add('newdir/')
 
527
            other_tree.commit('modify all sample files and dirs.')
 
528
        finally:
 
529
            other_tree.unlock()
 
530
        this_tree.merge_from_branch(other_tree.branch)
 
531
        reporter = CapturingReporter()
 
532
        this_tree.commit('do the commit', reporter=reporter)
 
533
        self.assertEqual([
 
534
            ('change', 'unchanged', ''),
 
535
            ('change', 'unchanged', 'dirtoleave'),
 
536
            ('change', 'unchanged', 'filetoleave'),
 
537
            ('change', 'modified', 'filetomodify'),
 
538
            ('change', 'added', 'newdir'),
 
539
            ('change', 'added', 'newfile'),
 
540
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
541
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
 
542
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
 
543
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
544
            ('deleted', 'dirtoremove'),
 
545
            ('deleted', 'filetoremove'),
 
546
            ],
 
547
            reporter.calls)
 
548
 
 
549
    def test_commit_removals_respects_filespec(self):
 
550
        """Commit respects the specified_files for removals."""
 
551
        tree = self.make_branch_and_tree('.')
 
552
        self.build_tree(['a', 'b'])
 
553
        tree.add(['a', 'b'])
 
554
        tree.commit('added a, b')
 
555
        tree.remove(['a', 'b'])
 
556
        tree.commit('removed a', specific_files='a')
 
557
        basis = tree.basis_tree()
 
558
        tree.lock_read()
 
559
        try:
 
560
            self.assertIs(None, basis.path2id('a'))
 
561
            self.assertFalse(basis.path2id('b') is None)
 
562
        finally:
 
563
            tree.unlock()
 
564
 
 
565
    def test_commit_saves_1ms_timestamp(self):
 
566
        """Passing in a timestamp is saved with 1ms resolution"""
 
567
        tree = self.make_branch_and_tree('.')
 
568
        self.build_tree(['a'])
 
569
        tree.add('a')
 
570
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
 
571
                    rev_id='a1')
 
572
 
 
573
        rev = tree.branch.repository.get_revision('a1')
 
574
        self.assertEqual(1153248633.419, rev.timestamp)
 
575
 
 
576
    def test_commit_has_1ms_resolution(self):
 
577
        """Allowing commit to generate the timestamp also has 1ms resolution"""
 
578
        tree = self.make_branch_and_tree('.')
 
579
        self.build_tree(['a'])
 
580
        tree.add('a')
 
581
        tree.commit('added a', rev_id='a1')
 
582
 
 
583
        rev = tree.branch.repository.get_revision('a1')
 
584
        timestamp = rev.timestamp
 
585
        timestamp_1ms = round(timestamp, 3)
 
586
        self.assertEqual(timestamp_1ms, timestamp)
 
587
 
 
588
    def assertBasisTreeKind(self, kind, tree, file_id):
 
589
        basis = tree.basis_tree()
 
590
        basis.lock_read()
 
591
        try:
 
592
            self.assertEqual(kind, basis.kind(file_id))
 
593
        finally:
 
594
            basis.unlock()
 
595
 
 
596
    def test_commit_kind_changes(self):
 
597
        self.requireFeature(SymlinkFeature)
 
598
        tree = self.make_branch_and_tree('.')
 
599
        os.symlink('target', 'name')
 
600
        tree.add('name', 'a-file-id')
 
601
        tree.commit('Added a symlink')
 
602
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
603
 
 
604
        os.unlink('name')
 
605
        self.build_tree(['name'])
 
606
        tree.commit('Changed symlink to file')
 
607
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
608
 
 
609
        os.unlink('name')
 
610
        os.symlink('target', 'name')
 
611
        tree.commit('file to symlink')
 
612
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
613
 
 
614
        os.unlink('name')
 
615
        os.mkdir('name')
 
616
        tree.commit('symlink to directory')
 
617
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
618
 
 
619
        os.rmdir('name')
 
620
        os.symlink('target', 'name')
 
621
        tree.commit('directory to symlink')
 
622
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
623
 
 
624
        # prepare for directory <-> file tests
 
625
        os.unlink('name')
 
626
        os.mkdir('name')
 
627
        tree.commit('symlink to directory')
 
628
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
629
 
 
630
        os.rmdir('name')
 
631
        self.build_tree(['name'])
 
632
        tree.commit('Changed directory to file')
 
633
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
634
 
 
635
        os.unlink('name')
 
636
        os.mkdir('name')
 
637
        tree.commit('file to directory')
 
638
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
639
 
 
640
    def test_commit_unversioned_specified(self):
 
641
        """Commit should raise if specified files isn't in basis or worktree"""
 
642
        tree = self.make_branch_and_tree('.')
 
643
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
644
                          'message', specific_files=['bogus'])
 
645
 
 
646
    class Callback(object):
 
647
        
 
648
        def __init__(self, message, testcase):
 
649
            self.called = False
 
650
            self.message = message
 
651
            self.testcase = testcase
 
652
 
 
653
        def __call__(self, commit_obj):
 
654
            self.called = True
 
655
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
 
656
            return self.message
 
657
 
 
658
    def test_commit_callback(self):
 
659
        """Commit should invoke a callback to get the message"""
 
660
 
 
661
        tree = self.make_branch_and_tree('.')
 
662
        try:
 
663
            tree.commit()
 
664
        except Exception, e:
 
665
            self.assertTrue(isinstance(e, BzrError))
 
666
            self.assertEqual('The message or message_callback keyword'
 
667
                             ' parameter is required for commit().', str(e))
 
668
        else:
 
669
            self.fail('exception not raised')
 
670
        cb = self.Callback(u'commit 1', self)
 
671
        tree.commit(message_callback=cb)
 
672
        self.assertTrue(cb.called)
 
673
        repository = tree.branch.repository
 
674
        message = repository.get_revision(tree.last_revision()).message
 
675
        self.assertEqual('commit 1', message)
 
676
 
 
677
    def test_no_callback_pointless(self):
 
678
        """Callback should not be invoked for pointless commit"""
 
679
        tree = self.make_branch_and_tree('.')
 
680
        cb = self.Callback(u'commit 2', self)
 
681
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
 
682
                          allow_pointless=False)
 
683
        self.assertFalse(cb.called)
 
684
 
 
685
    def test_no_callback_netfailure(self):
 
686
        """Callback should not be invoked if connectivity fails"""
 
687
        tree = self.make_branch_and_tree('.')
 
688
        cb = self.Callback(u'commit 2', self)
 
689
        repository = tree.branch.repository
 
690
        # simulate network failure
 
691
        def raise_(self, arg, arg2):
 
692
            raise errors.NoSuchFile('foo')
 
693
        repository.add_inventory = raise_
 
694
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
 
695
        self.assertFalse(cb.called)
 
696
 
 
697
    def test_selected_file_merge_commit(self):
 
698
        """Ensure the correct error is raised"""
 
699
        tree = self.make_branch_and_tree('foo')
 
700
        # pending merge would turn into a left parent
 
701
        tree.commit('commit 1')
 
702
        tree.add_parent_tree_id('example')
 
703
        self.build_tree(['foo/bar', 'foo/baz'])
 
704
        tree.add(['bar', 'baz'])
 
705
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
706
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
707
        self.assertEqual(['bar', 'baz'], err.files)
 
708
        self.assertEqual('Selected-file commit of merges is not supported'
 
709
                         ' yet: files bar, baz', str(err))
 
710
 
 
711
    def test_commit_ordering(self):
 
712
        """Test of corner-case commit ordering error"""
 
713
        tree = self.make_branch_and_tree('.')
 
714
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
715
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
716
        tree.commit('setup')
 
717
        self.build_tree(['a/c/d/'])
 
718
        tree.add('a/c/d')
 
719
        tree.rename_one('a/z/x', 'a/c/d/x')
 
720
        tree.commit('test', specific_files=['a/z/y'])
 
721
 
 
722
    def test_commit_no_author(self):
 
723
        """The default kwarg author in MutableTree.commit should not add
 
724
        the 'author' revision property.
 
725
        """
 
726
        tree = self.make_branch_and_tree('foo')
 
727
        rev_id = tree.commit('commit 1')
 
728
        rev = tree.branch.repository.get_revision(rev_id)
 
729
        self.assertFalse('author' in rev.properties)
 
730
 
 
731
    def test_commit_author(self):
 
732
        """Passing a non-empty author kwarg to MutableTree.commit should add
 
733
        the 'author' revision property.
 
734
        """
 
735
        tree = self.make_branch_and_tree('foo')
 
736
        rev_id = tree.commit('commit 1', author='John Doe <jdoe@example.com>')
 
737
        rev = tree.branch.repository.get_revision(rev_id)
 
738
        self.assertEqual('John Doe <jdoe@example.com>',
 
739
                         rev.properties['author'])
 
740
 
 
741
    def test_commit_with_checkout_and_branch_sharing_repo(self):
 
742
        repo = self.make_repository('repo', shared=True)
 
743
        # make_branch_and_tree ignores shared repos
 
744
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
 
745
        tree2 = branch.create_checkout('repo/tree2')
 
746
        tree2.commit('message', rev_id='rev1')
 
747
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))