~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 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,
 
23
    lockdir,
 
24
    osutils,
 
25
    tests,
24
26
    )
25
27
from bzrlib.branch import Branch
26
 
from bzrlib.bzrdir import BzrDirMetaFormat1
 
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
27
29
from bzrlib.commit import Commit, NullCommitReporter
28
30
from bzrlib.config import BranchConfig
29
 
from bzrlib.errors import (
30
 
    PointlessCommit,
31
 
    BzrError,
32
 
    SigningFailed,
33
 
    LockContention,
34
 
    )
35
 
from bzrlib.tests import (
36
 
    SymlinkFeature,
37
 
    TestCaseWithTransport,
38
 
    test_foreign,
39
 
    )
40
 
from bzrlib.tests.matchers import MatchesAncestry
 
31
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
 
32
                           LockContention)
 
33
from bzrlib.tests import TestCaseWithTransport
 
34
from bzrlib.workingtree import WorkingTree
41
35
 
42
36
 
43
37
# TODO: Test commit with some added, and added-but-missing files
76
70
    def renamed(self, change, old_path, new_path):
77
71
        self.calls.append(('renamed', change, old_path, new_path))
78
72
 
79
 
    def is_verbose(self):
80
 
        return True
81
 
 
82
73
 
83
74
class TestCommit(TestCaseWithTransport):
84
75
 
101
92
        eq(rev.message, 'add hello')
102
93
 
103
94
        tree1 = b.repository.revision_tree(rh[0])
104
 
        tree1.lock_read()
105
95
        text = tree1.get_file_text(file_id)
106
 
        tree1.unlock()
107
 
        self.assertEqual('hello world', text)
 
96
        eq(text, 'hello world')
108
97
 
109
98
        tree2 = b.repository.revision_tree(rh[1])
110
 
        tree2.lock_read()
111
 
        text = tree2.get_file_text(file_id)
112
 
        tree2.unlock()
113
 
        self.assertEqual('version 2', text)
114
 
 
115
 
    def test_commit_lossy_native(self):
116
 
        """Attempt a lossy commit to a native branch."""
117
 
        wt = self.make_branch_and_tree('.')
118
 
        b = wt.branch
119
 
        file('hello', 'w').write('hello world')
120
 
        wt.add('hello')
121
 
        revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
122
 
        self.assertEquals('revid', revid)
123
 
 
124
 
    def test_commit_lossy_foreign(self):
125
 
        """Attempt a lossy commit to a foreign branch."""
126
 
        test_foreign.register_dummy_foreign_for_test(self)
127
 
        wt = self.make_branch_and_tree('.',
128
 
            format=test_foreign.DummyForeignVcsDirFormat())
129
 
        b = wt.branch
130
 
        file('hello', 'w').write('hello world')
131
 
        wt.add('hello')
132
 
        revid = wt.commit(message='add hello', lossy=True,
133
 
            timestamp=1302659388, timezone=0)
134
 
        self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
135
 
 
136
 
    def test_commit_bound_lossy_foreign(self):
137
 
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
138
 
        test_foreign.register_dummy_foreign_for_test(self)
139
 
        foreign_branch = self.make_branch('foreign',
140
 
            format=test_foreign.DummyForeignVcsDirFormat())
141
 
        wt = foreign_branch.create_checkout("local")
142
 
        b = wt.branch
143
 
        file('local/hello', 'w').write('hello world')
144
 
        wt.add('hello')
145
 
        revid = wt.commit(message='add hello', lossy=True,
146
 
            timestamp=1302659388, timezone=0)
147
 
        self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
148
 
        self.assertEquals('dummy-v1:1302659388.0-0-0',
149
 
            foreign_branch.last_revision())
150
 
        self.assertEquals('dummy-v1:1302659388.0-0-0',
151
 
            wt.branch.last_revision())
152
 
 
153
 
    def test_missing_commit(self):
154
 
        """Test a commit with a missing file"""
 
99
        eq(tree2.get_file_text(file_id), 'version 2')
 
100
 
 
101
    def test_delete_commit(self):
 
102
        """Test a commit with a deleted file"""
155
103
        wt = self.make_branch_and_tree('.')
156
104
        b = wt.branch
157
105
        file('hello', 'w').write('hello world')
164
112
        tree = b.repository.revision_tree('rev2')
165
113
        self.assertFalse(tree.has_id('hello-id'))
166
114
 
167
 
    def test_partial_commit_move(self):
168
 
        """Test a partial commit where a file was renamed but not committed.
169
 
 
170
 
        https://bugs.launchpad.net/bzr/+bug/83039
171
 
 
172
 
        If not handled properly, commit will try to snapshot
173
 
        dialog.py with olive/ as a parent, while
174
 
        olive/ has not been snapshotted yet.
175
 
        """
176
 
        wt = self.make_branch_and_tree('.')
177
 
        b = wt.branch
178
 
        self.build_tree(['annotate/', 'annotate/foo.py',
179
 
                         'olive/', 'olive/dialog.py'
180
 
                        ])
181
 
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
182
 
        wt.commit(message='add files')
183
 
        wt.rename_one("olive/dialog.py", "aaa")
184
 
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
185
 
        wt.commit('renamed hello', specific_files=["annotate"])
186
 
 
187
115
    def test_pointless_commit(self):
188
116
        """Commit refuses unless there are changes or it's forced."""
189
117
        wt = self.make_branch_and_tree('.')
197
125
                          message='fails',
198
126
                          allow_pointless=False)
199
127
        self.assertEquals(b.revno(), 1)
200
 
 
 
128
        
201
129
    def test_commit_empty(self):
202
130
        """Commiting an empty tree works."""
203
131
        wt = self.make_branch_and_tree('.')
220
148
              ['hello-id', 'buongia-id'])
221
149
        wt.commit(message='add files',
222
150
                 rev_id='test@rev-1')
223
 
 
 
151
        
224
152
        os.remove('hello')
225
153
        file('buongia', 'w').write('new text')
226
154
        wt.commit(message='update text',
237
165
        eq(b.revno(), 3)
238
166
 
239
167
        tree2 = b.repository.revision_tree('test@rev-2')
240
 
        tree2.lock_read()
241
 
        self.addCleanup(tree2.unlock)
242
168
        self.assertTrue(tree2.has_filename('hello'))
243
169
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
244
170
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
245
 
 
 
171
        
246
172
        tree3 = b.repository.revision_tree('test@rev-3')
247
 
        tree3.lock_read()
248
 
        self.addCleanup(tree3.unlock)
249
173
        self.assertFalse(tree3.has_filename('hello'))
250
174
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
251
175
 
262
186
 
263
187
        eq = self.assertEquals
264
188
        tree1 = b.repository.revision_tree('test@rev-1')
265
 
        tree1.lock_read()
266
 
        self.addCleanup(tree1.unlock)
267
189
        eq(tree1.id2path('hello-id'), 'hello')
268
190
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
269
191
        self.assertFalse(tree1.has_filename('fruity'))
270
 
        self.check_tree_shape(tree1, ['hello'])
271
 
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
 
192
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
193
        ie = tree1.inventory['hello-id']
 
194
        eq(ie.revision, 'test@rev-1')
272
195
 
273
196
        tree2 = b.repository.revision_tree('test@rev-2')
274
 
        tree2.lock_read()
275
 
        self.addCleanup(tree2.unlock)
276
197
        eq(tree2.id2path('hello-id'), 'fruity')
277
198
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
278
 
        self.check_tree_shape(tree2, ['fruity'])
279
 
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
 
199
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
200
        ie = tree2.inventory['hello-id']
 
201
        eq(ie.revision, 'test@rev-2')
280
202
 
281
203
    def test_reused_rev_id(self):
282
204
        """Test that a revision id cannot be reused in a branch"""
303
225
        wt.commit('two', rev_id=r2, allow_pointless=False)
304
226
        wt.lock_read()
305
227
        try:
306
 
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
 
228
            self.check_inventory_shape(wt.read_working_inventory(),
 
229
                                       ['a', 'a/hello', 'b'])
307
230
        finally:
308
231
            wt.unlock()
309
232
 
312
235
        wt.commit('three', rev_id=r3, allow_pointless=False)
313
236
        wt.lock_read()
314
237
        try:
315
 
            self.check_tree_shape(wt,
316
 
                                       ['a/', 'a/hello', 'a/b/'])
317
 
            self.check_tree_shape(b.repository.revision_tree(r3),
318
 
                                       ['a/', 'a/hello', 'a/b/'])
 
238
            self.check_inventory_shape(wt.read_working_inventory(),
 
239
                                       ['a', 'a/hello', 'a/b'])
 
240
            self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
241
                                       ['a', 'a/hello', 'a/b'])
319
242
        finally:
320
243
            wt.unlock()
321
244
 
324
247
        wt.commit('four', rev_id=r4, allow_pointless=False)
325
248
        wt.lock_read()
326
249
        try:
327
 
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
 
250
            self.check_inventory_shape(wt.read_working_inventory(),
 
251
                                       ['a', 'a/b/hello', 'a/b'])
328
252
        finally:
329
253
            wt.unlock()
330
254
 
331
 
        inv = b.repository.get_inventory(r4)
 
255
        inv = b.repository.get_revision_inventory(r4)
332
256
        eq(inv['hello-id'].revision, r4)
333
257
        eq(inv['a-id'].revision, r1)
334
258
        eq(inv['b-id'].revision, r3)
362
286
        eq = self.assertEquals
363
287
        eq(b.revision_history(), rev_ids)
364
288
        for i in range(4):
365
 
            self.assertThat(rev_ids[:i+1],
366
 
                MatchesAncestry(b.repository, rev_ids[i]))
 
289
            anc = b.repository.get_ancestry(rev_ids[i])
 
290
            eq(anc, [None] + rev_ids[:i+1])
367
291
 
368
292
    def test_commit_new_subdir_child_selective(self):
369
293
        wt = self.make_branch_and_tree('.')
392
316
    def test_strict_commit_without_unknowns(self):
393
317
        """Try and commit with no unknown files and strict = True,
394
318
        should work."""
 
319
        from bzrlib.errors import StrictCommitFailed
395
320
        wt = self.make_branch_and_tree('.')
396
321
        b = wt.branch
397
322
        file('hello', 'w').write('hello world')
423
348
        wt = self.make_branch_and_tree('.')
424
349
        branch = wt.branch
425
350
        wt.commit("base", allow_pointless=True, rev_id='A')
426
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
351
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
427
352
        try:
428
353
            from bzrlib.testament import Testament
429
354
            # monkey patch gpg signing mechanism
447
372
        wt = self.make_branch_and_tree('.')
448
373
        branch = wt.branch
449
374
        wt.commit("base", allow_pointless=True, rev_id='A')
450
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
375
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
451
376
        try:
 
377
            from bzrlib.testament import Testament
452
378
            # monkey patch gpg signing mechanism
453
379
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
454
380
            config = MustSignConfig(branch)
460
386
                              working_tree=wt)
461
387
            branch = Branch.open(self.get_url('.'))
462
388
            self.assertEqual(branch.revision_history(), ['A'])
463
 
            self.assertFalse(branch.repository.has_revision('B'))
 
389
            self.failIf(branch.repository.has_revision('B'))
464
390
        finally:
465
391
            bzrlib.gpg.GPGStrategy = oldstrategy
466
392
 
501
427
        bound = master.sprout('bound')
502
428
        wt = bound.open_workingtree()
503
429
        wt.branch.set_bound_location(os.path.realpath('master'))
 
430
 
 
431
        orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
504
432
        master_branch.lock_write()
505
433
        try:
 
434
            lockdir._DEFAULT_TIMEOUT_SECONDS = 1
506
435
            self.assertRaises(LockContention, wt.commit, 'silly')
507
436
        finally:
 
437
            lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
508
438
            master_branch.unlock()
509
439
 
510
440
    def test_commit_bound_merge(self):
521
451
        other_bzrdir = master_branch.bzrdir.sprout('other')
522
452
        other_tree = other_bzrdir.open_workingtree()
523
453
 
524
 
        # do a commit to the other branch changing the content file so
 
454
        # do a commit to the the other branch changing the content file so
525
455
        # that our commit after merging will have a merged revision in the
526
456
        # content file history.
527
457
        self.build_tree_contents([('other/content_file', 'change in other\n')])
538
468
        bound_tree.commit(message='commit of merge in bound tree')
539
469
 
540
470
    def test_commit_reporting_after_merge(self):
541
 
        # when doing a commit of a merge, the reporter needs to still
 
471
        # when doing a commit of a merge, the reporter needs to still 
542
472
        # be called for each item that is added/removed/deleted.
543
473
        this_tree = self.make_branch_and_tree('this')
544
474
        # we need a bunch of files and dirs, to perform one action on each.
587
517
        this_tree.merge_from_branch(other_tree.branch)
588
518
        reporter = CapturingReporter()
589
519
        this_tree.commit('do the commit', reporter=reporter)
590
 
        expected = set([
 
520
        self.assertEqual([
 
521
            ('change', 'unchanged', ''),
 
522
            ('change', 'unchanged', 'dirtoleave'),
 
523
            ('change', 'unchanged', 'filetoleave'),
591
524
            ('change', 'modified', 'filetomodify'),
592
525
            ('change', 'added', 'newdir'),
593
526
            ('change', 'added', 'newfile'),
594
527
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
595
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
596
528
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
597
529
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
 
530
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
598
531
            ('deleted', 'dirtoremove'),
599
532
            ('deleted', 'filetoremove'),
600
 
            ])
601
 
        result = set(reporter.calls)
602
 
        missing = expected - result
603
 
        new = result - expected
604
 
        self.assertEqual((set(), set()), (missing, new))
 
533
            ],
 
534
            reporter.calls)
605
535
 
606
536
    def test_commit_removals_respects_filespec(self):
607
537
        """Commit respects the specified_files for removals."""
651
581
            basis.unlock()
652
582
 
653
583
    def test_commit_kind_changes(self):
654
 
        self.requireFeature(SymlinkFeature)
 
584
        if not osutils.has_symlinks():
 
585
            raise tests.TestSkipped('Test requires symlink support')
655
586
        tree = self.make_branch_and_tree('.')
656
587
        os.symlink('target', 'name')
657
588
        tree.add('name', 'a-file-id')
697
628
    def test_commit_unversioned_specified(self):
698
629
        """Commit should raise if specified files isn't in basis or worktree"""
699
630
        tree = self.make_branch_and_tree('.')
700
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
 
631
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
701
632
                          'message', specific_files=['bogus'])
702
633
 
703
634
    class Callback(object):
704
 
 
 
635
        
705
636
        def __init__(self, message, testcase):
706
637
            self.called = False
707
638
            self.message = message
735
666
        """Callback should not be invoked for pointless commit"""
736
667
        tree = self.make_branch_and_tree('.')
737
668
        cb = self.Callback(u'commit 2', self)
738
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
 
669
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
739
670
                          allow_pointless=False)
740
671
        self.assertFalse(cb.called)
741
672
 
745
676
        cb = self.Callback(u'commit 2', self)
746
677
        repository = tree.branch.repository
747
678
        # simulate network failure
748
 
        def raise_(self, arg, arg2, arg3=None, arg4=None):
 
679
        def raise_(self, arg, arg2):
749
680
            raise errors.NoSuchFile('foo')
750
681
        repository.add_inventory = raise_
751
 
        repository.add_inventory_by_delta = raise_
752
682
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
753
683
        self.assertFalse(cb.called)
754
 
 
755
 
    def test_selected_file_merge_commit(self):
756
 
        """Ensure the correct error is raised"""
757
 
        tree = self.make_branch_and_tree('foo')
758
 
        # pending merge would turn into a left parent
759
 
        tree.commit('commit 1')
760
 
        tree.add_parent_tree_id('example')
761
 
        self.build_tree(['foo/bar', 'foo/baz'])
762
 
        tree.add(['bar', 'baz'])
763
 
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
764
 
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
765
 
        self.assertEqual(['bar', 'baz'], err.files)
766
 
        self.assertEqual('Selected-file commit of merges is not supported'
767
 
                         ' yet: files bar, baz', str(err))
768
 
 
769
 
    def test_commit_ordering(self):
770
 
        """Test of corner-case commit ordering error"""
771
 
        tree = self.make_branch_and_tree('.')
772
 
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
773
 
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
774
 
        tree.commit('setup')
775
 
        self.build_tree(['a/c/d/'])
776
 
        tree.add('a/c/d')
777
 
        tree.rename_one('a/z/x', 'a/c/d/x')
778
 
        tree.commit('test', specific_files=['a/z/y'])
779
 
 
780
 
    def test_commit_no_author(self):
781
 
        """The default kwarg author in MutableTree.commit should not add
782
 
        the 'author' revision property.
783
 
        """
784
 
        tree = self.make_branch_and_tree('foo')
785
 
        rev_id = tree.commit('commit 1')
786
 
        rev = tree.branch.repository.get_revision(rev_id)
787
 
        self.assertFalse('author' in rev.properties)
788
 
        self.assertFalse('authors' in rev.properties)
789
 
 
790
 
    def test_commit_author(self):
791
 
        """Passing a non-empty author kwarg to MutableTree.commit should add
792
 
        the 'author' revision property.
793
 
        """
794
 
        tree = self.make_branch_and_tree('foo')
795
 
        rev_id = self.callDeprecated(['The parameter author was '
796
 
                'deprecated in version 1.13. Use authors instead'],
797
 
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
798
 
        rev = tree.branch.repository.get_revision(rev_id)
799
 
        self.assertEqual('John Doe <jdoe@example.com>',
800
 
                         rev.properties['authors'])
801
 
        self.assertFalse('author' in rev.properties)
802
 
 
803
 
    def test_commit_empty_authors_list(self):
804
 
        """Passing an empty list to authors shouldn't add the property."""
805
 
        tree = self.make_branch_and_tree('foo')
806
 
        rev_id = tree.commit('commit 1', authors=[])
807
 
        rev = tree.branch.repository.get_revision(rev_id)
808
 
        self.assertFalse('author' in rev.properties)
809
 
        self.assertFalse('authors' in rev.properties)
810
 
 
811
 
    def test_multiple_authors(self):
812
 
        tree = self.make_branch_and_tree('foo')
813
 
        rev_id = tree.commit('commit 1',
814
 
                authors=['John Doe <jdoe@example.com>',
815
 
                         'Jane Rey <jrey@example.com>'])
816
 
        rev = tree.branch.repository.get_revision(rev_id)
817
 
        self.assertEqual('John Doe <jdoe@example.com>\n'
818
 
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
819
 
        self.assertFalse('author' in rev.properties)
820
 
 
821
 
    def test_author_and_authors_incompatible(self):
822
 
        tree = self.make_branch_and_tree('foo')
823
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
824
 
                authors=['John Doe <jdoe@example.com>',
825
 
                         'Jane Rey <jrey@example.com>'],
826
 
                author="Jack Me <jme@example.com>")
827
 
 
828
 
    def test_author_with_newline_rejected(self):
829
 
        tree = self.make_branch_and_tree('foo')
830
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
831
 
                authors=['John\nDoe <jdoe@example.com>'])
832
 
 
833
 
    def test_commit_with_checkout_and_branch_sharing_repo(self):
834
 
        repo = self.make_repository('repo', shared=True)
835
 
        # make_branch_and_tree ignores shared repos
836
 
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
837
 
        tree2 = branch.create_checkout('repo/tree2')
838
 
        tree2.commit('message', rev_id='rev1')
839
 
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))