/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: 2007-11-30 05:43:20 UTC
  • mfrom: (3054.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20071130054320-b4oer0rcbiy2ouzg
Bazaar User Guide for 1.0rc (Ian Clatworthy)

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