~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-01-31 14:04:56 UTC
  • mfrom: (1551.10.2 Aaron's mergeable stuff)
  • Revision ID: pqm@pqm.ubuntu.com-20070131140456-56881c31a01089a3
Handle merge with dangling inventory entries

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
 
18
18
import os
19
19
 
20
20
import bzrlib
21
21
from bzrlib import (
22
 
    bzrdir,
23
22
    errors,
24
23
    lockdir,
25
 
    osutils,
26
 
    tests,
27
24
    )
28
25
from bzrlib.branch import Branch
29
26
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
30
27
from bzrlib.commit import Commit, NullCommitReporter
31
28
from bzrlib.config import BranchConfig
32
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
 
29
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
33
30
                           LockContention)
34
 
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
 
31
from bzrlib.tests import TestCaseWithTransport
35
32
from bzrlib.workingtree import WorkingTree
36
33
 
37
34
 
71
68
    def renamed(self, change, old_path, new_path):
72
69
        self.calls.append(('renamed', change, old_path, new_path))
73
70
 
74
 
    def is_verbose(self):
75
 
        return True
76
 
 
77
71
 
78
72
class TestCommit(TestCaseWithTransport):
79
73
 
96
90
        eq(rev.message, 'add hello')
97
91
 
98
92
        tree1 = b.repository.revision_tree(rh[0])
99
 
        tree1.lock_read()
100
93
        text = tree1.get_file_text(file_id)
101
 
        tree1.unlock()
102
 
        self.assertEqual('hello world', text)
 
94
        eq(text, 'hello world')
103
95
 
104
96
        tree2 = b.repository.revision_tree(rh[1])
105
 
        tree2.lock_read()
106
 
        text = tree2.get_file_text(file_id)
107
 
        tree2.unlock()
108
 
        self.assertEqual('version 2', text)
 
97
        eq(tree2.get_file_text(file_id), 'version 2')
109
98
 
110
 
    def test_missing_commit(self):
111
 
        """Test a commit with a missing file"""
 
99
    def test_delete_commit(self):
 
100
        """Test a commit with a deleted file"""
112
101
        wt = self.make_branch_and_tree('.')
113
102
        b = wt.branch
114
103
        file('hello', 'w').write('hello world')
121
110
        tree = b.repository.revision_tree('rev2')
122
111
        self.assertFalse(tree.has_id('hello-id'))
123
112
 
124
 
    def test_partial_commit_move(self):
125
 
        """Test a partial commit where a file was renamed but not committed.
126
 
 
127
 
        https://bugs.launchpad.net/bzr/+bug/83039
128
 
 
129
 
        If not handled properly, commit will try to snapshot
130
 
        dialog.py with olive/ as a parent, while
131
 
        olive/ has not been snapshotted yet.
132
 
        """
133
 
        wt = self.make_branch_and_tree('.')
134
 
        b = wt.branch
135
 
        self.build_tree(['annotate/', 'annotate/foo.py',
136
 
                         'olive/', 'olive/dialog.py'
137
 
                        ])
138
 
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
139
 
        wt.commit(message='add files')
140
 
        wt.rename_one("olive/dialog.py", "aaa")
141
 
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
142
 
        wt.commit('renamed hello', specific_files=["annotate"])
143
 
 
144
113
    def test_pointless_commit(self):
145
114
        """Commit refuses unless there are changes or it's forced."""
146
115
        wt = self.make_branch_and_tree('.')
154
123
                          message='fails',
155
124
                          allow_pointless=False)
156
125
        self.assertEquals(b.revno(), 1)
157
 
 
 
126
        
158
127
    def test_commit_empty(self):
159
128
        """Commiting an empty tree works."""
160
129
        wt = self.make_branch_and_tree('.')
177
146
              ['hello-id', 'buongia-id'])
178
147
        wt.commit(message='add files',
179
148
                 rev_id='test@rev-1')
180
 
 
 
149
        
181
150
        os.remove('hello')
182
151
        file('buongia', 'w').write('new text')
183
152
        wt.commit(message='update text',
194
163
        eq(b.revno(), 3)
195
164
 
196
165
        tree2 = b.repository.revision_tree('test@rev-2')
197
 
        tree2.lock_read()
198
 
        self.addCleanup(tree2.unlock)
199
166
        self.assertTrue(tree2.has_filename('hello'))
200
167
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
201
168
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
202
 
 
 
169
        
203
170
        tree3 = b.repository.revision_tree('test@rev-3')
204
 
        tree3.lock_read()
205
 
        self.addCleanup(tree3.unlock)
206
171
        self.assertFalse(tree3.has_filename('hello'))
207
172
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
208
173
 
219
184
 
220
185
        eq = self.assertEquals
221
186
        tree1 = b.repository.revision_tree('test@rev-1')
222
 
        tree1.lock_read()
223
 
        self.addCleanup(tree1.unlock)
224
187
        eq(tree1.id2path('hello-id'), 'hello')
225
188
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
226
189
        self.assertFalse(tree1.has_filename('fruity'))
229
192
        eq(ie.revision, 'test@rev-1')
230
193
 
231
194
        tree2 = b.repository.revision_tree('test@rev-2')
232
 
        tree2.lock_read()
233
 
        self.addCleanup(tree2.unlock)
234
195
        eq(tree2.id2path('hello-id'), 'fruity')
235
196
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
236
197
        self.check_inventory_shape(tree2.inventory, ['fruity'])
260
221
        wt.move(['hello'], 'a')
261
222
        r2 = 'test@rev-2'
262
223
        wt.commit('two', rev_id=r2, allow_pointless=False)
263
 
        wt.lock_read()
264
 
        try:
265
 
            self.check_inventory_shape(wt.read_working_inventory(),
266
 
                                       ['a/', 'a/hello', 'b/'])
267
 
        finally:
268
 
            wt.unlock()
 
224
        self.check_inventory_shape(wt.read_working_inventory(),
 
225
                                   ['a', 'a/hello', 'b'])
269
226
 
270
227
        wt.move(['b'], 'a')
271
228
        r3 = 'test@rev-3'
272
229
        wt.commit('three', rev_id=r3, allow_pointless=False)
273
 
        wt.lock_read()
274
 
        try:
275
 
            self.check_inventory_shape(wt.read_working_inventory(),
276
 
                                       ['a/', 'a/hello', 'a/b/'])
277
 
            self.check_inventory_shape(b.repository.get_inventory(r3),
278
 
                                       ['a/', 'a/hello', 'a/b/'])
279
 
        finally:
280
 
            wt.unlock()
 
230
        self.check_inventory_shape(wt.read_working_inventory(),
 
231
                                   ['a', 'a/hello', 'a/b'])
 
232
        self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
233
                                   ['a', 'a/hello', 'a/b'])
281
234
 
282
235
        wt.move(['a/hello'], 'a/b')
283
236
        r4 = 'test@rev-4'
284
237
        wt.commit('four', rev_id=r4, allow_pointless=False)
285
 
        wt.lock_read()
286
 
        try:
287
 
            self.check_inventory_shape(wt.read_working_inventory(),
288
 
                                       ['a/', 'a/b/hello', 'a/b/'])
289
 
        finally:
290
 
            wt.unlock()
 
238
        self.check_inventory_shape(wt.read_working_inventory(),
 
239
                                   ['a', 'a/b/hello', 'a/b'])
291
240
 
292
 
        inv = b.repository.get_inventory(r4)
 
241
        inv = b.repository.get_revision_inventory(r4)
293
242
        eq(inv['hello-id'].revision, r4)
294
243
        eq(inv['a-id'].revision, r1)
295
244
        eq(inv['b-id'].revision, r3)
296
 
 
 
245
        
297
246
    def test_removed_commit(self):
298
247
        """Commit with a removed file"""
299
248
        wt = self.make_branch_and_tree('.')
394
343
                                                      allow_pointless=True,
395
344
                                                      rev_id='B',
396
345
                                                      working_tree=wt)
397
 
            def sign(text):
398
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
399
 
            self.assertEqual(sign(Testament.from_revision(branch.repository,
400
 
                             'B').as_short_text()),
 
346
            self.assertEqual(Testament.from_revision(branch.repository,
 
347
                             'B').as_short_text(),
401
348
                             branch.repository.get_signature_text('B'))
402
349
        finally:
403
350
            bzrlib.gpg.GPGStrategy = oldstrategy
464
411
        bound = master.sprout('bound')
465
412
        wt = bound.open_workingtree()
466
413
        wt.branch.set_bound_location(os.path.realpath('master'))
 
414
 
 
415
        orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
467
416
        master_branch.lock_write()
468
417
        try:
 
418
            lockdir._DEFAULT_TIMEOUT_SECONDS = 1
469
419
            self.assertRaises(LockContention, wt.commit, 'silly')
470
420
        finally:
 
421
            lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
471
422
            master_branch.unlock()
472
423
 
473
424
    def test_commit_bound_merge(self):
484
435
        other_bzrdir = master_branch.bzrdir.sprout('other')
485
436
        other_tree = other_bzrdir.open_workingtree()
486
437
 
487
 
        # do a commit to the other branch changing the content file so
 
438
        # do a commit to the the other branch changing the content file so
488
439
        # that our commit after merging will have a merged revision in the
489
440
        # content file history.
490
441
        self.build_tree_contents([('other/content_file', 'change in other\n')])
501
452
        bound_tree.commit(message='commit of merge in bound tree')
502
453
 
503
454
    def test_commit_reporting_after_merge(self):
504
 
        # when doing a commit of a merge, the reporter needs to still
 
455
        # when doing a commit of a merge, the reporter needs to still 
505
456
        # be called for each item that is added/removed/deleted.
506
457
        this_tree = self.make_branch_and_tree('this')
507
458
        # we need a bunch of files and dirs, to perform one action on each.
550
501
        this_tree.merge_from_branch(other_tree.branch)
551
502
        reporter = CapturingReporter()
552
503
        this_tree.commit('do the commit', reporter=reporter)
553
 
        expected = set([
 
504
        self.assertEqual([
 
505
            ('change', 'unchanged', ''),
 
506
            ('change', 'unchanged', 'dirtoleave'),
 
507
            ('change', 'unchanged', 'filetoleave'),
554
508
            ('change', 'modified', 'filetomodify'),
555
509
            ('change', 'added', 'newdir'),
556
510
            ('change', 'added', 'newfile'),
557
511
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
558
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
559
512
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
560
513
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
514
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
561
515
            ('deleted', 'dirtoremove'),
562
516
            ('deleted', 'filetoremove'),
563
 
            ])
564
 
        result = set(reporter.calls)
565
 
        missing = expected - result
566
 
        new = result - expected
567
 
        self.assertEqual((set(), set()), (missing, new))
 
517
            ],
 
518
            reporter.calls)
568
519
 
569
520
    def test_commit_removals_respects_filespec(self):
570
521
        """Commit respects the specified_files for removals."""
574
525
        tree.commit('added a, b')
575
526
        tree.remove(['a', 'b'])
576
527
        tree.commit('removed a', specific_files='a')
577
 
        basis = tree.basis_tree()
578
 
        tree.lock_read()
579
 
        try:
580
 
            self.assertIs(None, basis.path2id('a'))
581
 
            self.assertFalse(basis.path2id('b') is None)
582
 
        finally:
583
 
            tree.unlock()
 
528
        basis = tree.basis_tree().inventory
 
529
        self.assertIs(None, basis.path2id('a'))
 
530
        self.assertFalse(basis.path2id('b') is None)
584
531
 
585
532
    def test_commit_saves_1ms_timestamp(self):
586
533
        """Passing in a timestamp is saved with 1ms resolution"""
605
552
        timestamp_1ms = round(timestamp, 3)
606
553
        self.assertEqual(timestamp_1ms, timestamp)
607
554
 
608
 
    def assertBasisTreeKind(self, kind, tree, file_id):
609
 
        basis = tree.basis_tree()
610
 
        basis.lock_read()
611
 
        try:
612
 
            self.assertEqual(kind, basis.kind(file_id))
613
 
        finally:
614
 
            basis.unlock()
615
 
 
616
 
    def test_commit_kind_changes(self):
617
 
        self.requireFeature(SymlinkFeature)
618
 
        tree = self.make_branch_and_tree('.')
619
 
        os.symlink('target', 'name')
620
 
        tree.add('name', 'a-file-id')
621
 
        tree.commit('Added a symlink')
622
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
623
 
 
624
 
        os.unlink('name')
625
 
        self.build_tree(['name'])
626
 
        tree.commit('Changed symlink to file')
627
 
        self.assertBasisTreeKind('file', tree, 'a-file-id')
628
 
 
629
 
        os.unlink('name')
630
 
        os.symlink('target', 'name')
631
 
        tree.commit('file to symlink')
632
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
633
 
 
634
 
        os.unlink('name')
635
 
        os.mkdir('name')
636
 
        tree.commit('symlink to directory')
637
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
638
 
 
639
 
        os.rmdir('name')
640
 
        os.symlink('target', 'name')
641
 
        tree.commit('directory to symlink')
642
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
643
 
 
644
 
        # prepare for directory <-> file tests
645
 
        os.unlink('name')
646
 
        os.mkdir('name')
647
 
        tree.commit('symlink to directory')
648
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
649
 
 
650
 
        os.rmdir('name')
651
 
        self.build_tree(['name'])
652
 
        tree.commit('Changed directory to file')
653
 
        self.assertBasisTreeKind('file', tree, 'a-file-id')
654
 
 
655
 
        os.unlink('name')
656
 
        os.mkdir('name')
657
 
        tree.commit('file to directory')
658
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
659
 
 
660
555
    def test_commit_unversioned_specified(self):
661
556
        """Commit should raise if specified files isn't in basis or worktree"""
662
557
        tree = self.make_branch_and_tree('.')
663
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
 
558
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
664
559
                          'message', specific_files=['bogus'])
665
560
 
666
561
    class Callback(object):
667
 
 
 
562
        
668
563
        def __init__(self, message, testcase):
669
564
            self.called = False
670
565
            self.message = message
698
593
        """Callback should not be invoked for pointless commit"""
699
594
        tree = self.make_branch_and_tree('.')
700
595
        cb = self.Callback(u'commit 2', self)
701
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
 
596
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
702
597
                          allow_pointless=False)
703
598
        self.assertFalse(cb.called)
704
599
 
708
603
        cb = self.Callback(u'commit 2', self)
709
604
        repository = tree.branch.repository
710
605
        # simulate network failure
711
 
        def raise_(self, arg, arg2, arg3=None, arg4=None):
 
606
        def raise_(self, arg, arg2):
712
607
            raise errors.NoSuchFile('foo')
713
608
        repository.add_inventory = raise_
714
 
        repository.add_inventory_by_delta = raise_
715
609
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
716
610
        self.assertFalse(cb.called)
717
 
 
718
 
    def test_selected_file_merge_commit(self):
719
 
        """Ensure the correct error is raised"""
720
 
        tree = self.make_branch_and_tree('foo')
721
 
        # pending merge would turn into a left parent
722
 
        tree.commit('commit 1')
723
 
        tree.add_parent_tree_id('example')
724
 
        self.build_tree(['foo/bar', 'foo/baz'])
725
 
        tree.add(['bar', 'baz'])
726
 
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
727
 
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
728
 
        self.assertEqual(['bar', 'baz'], err.files)
729
 
        self.assertEqual('Selected-file commit of merges is not supported'
730
 
                         ' yet: files bar, baz', str(err))
731
 
 
732
 
    def test_commit_ordering(self):
733
 
        """Test of corner-case commit ordering error"""
734
 
        tree = self.make_branch_and_tree('.')
735
 
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
736
 
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
737
 
        tree.commit('setup')
738
 
        self.build_tree(['a/c/d/'])
739
 
        tree.add('a/c/d')
740
 
        tree.rename_one('a/z/x', 'a/c/d/x')
741
 
        tree.commit('test', specific_files=['a/z/y'])
742
 
 
743
 
    def test_commit_no_author(self):
744
 
        """The default kwarg author in MutableTree.commit should not add
745
 
        the 'author' revision property.
746
 
        """
747
 
        tree = self.make_branch_and_tree('foo')
748
 
        rev_id = tree.commit('commit 1')
749
 
        rev = tree.branch.repository.get_revision(rev_id)
750
 
        self.assertFalse('author' in rev.properties)
751
 
        self.assertFalse('authors' in rev.properties)
752
 
 
753
 
    def test_commit_author(self):
754
 
        """Passing a non-empty author kwarg to MutableTree.commit should add
755
 
        the 'author' revision property.
756
 
        """
757
 
        tree = self.make_branch_and_tree('foo')
758
 
        rev_id = self.callDeprecated(['The parameter author was '
759
 
                'deprecated in version 1.13. Use authors instead'],
760
 
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
761
 
        rev = tree.branch.repository.get_revision(rev_id)
762
 
        self.assertEqual('John Doe <jdoe@example.com>',
763
 
                         rev.properties['authors'])
764
 
        self.assertFalse('author' in rev.properties)
765
 
 
766
 
    def test_commit_empty_authors_list(self):
767
 
        """Passing an empty list to authors shouldn't add the property."""
768
 
        tree = self.make_branch_and_tree('foo')
769
 
        rev_id = tree.commit('commit 1', authors=[])
770
 
        rev = tree.branch.repository.get_revision(rev_id)
771
 
        self.assertFalse('author' in rev.properties)
772
 
        self.assertFalse('authors' in rev.properties)
773
 
 
774
 
    def test_multiple_authors(self):
775
 
        tree = self.make_branch_and_tree('foo')
776
 
        rev_id = tree.commit('commit 1',
777
 
                authors=['John Doe <jdoe@example.com>',
778
 
                         'Jane Rey <jrey@example.com>'])
779
 
        rev = tree.branch.repository.get_revision(rev_id)
780
 
        self.assertEqual('John Doe <jdoe@example.com>\n'
781
 
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
782
 
        self.assertFalse('author' in rev.properties)
783
 
 
784
 
    def test_author_and_authors_incompatible(self):
785
 
        tree = self.make_branch_and_tree('foo')
786
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
787
 
                authors=['John Doe <jdoe@example.com>',
788
 
                         'Jane Rey <jrey@example.com>'],
789
 
                author="Jack Me <jme@example.com>")
790
 
 
791
 
    def test_author_with_newline_rejected(self):
792
 
        tree = self.make_branch_and_tree('foo')
793
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
794
 
                authors=['John\nDoe <jdoe@example.com>'])
795
 
 
796
 
    def test_commit_with_checkout_and_branch_sharing_repo(self):
797
 
        repo = self.make_repository('repo', shared=True)
798
 
        # make_branch_and_tree ignores shared repos
799
 
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
800
 
        tree2 = branch.create_checkout('repo/tree2')
801
 
        tree2.commit('message', rev_id='rev1')
802
 
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))