~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_commit.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008 Canonical Ltd
 
1
# Copyright (C) 2005-2011 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
 
18
18
import os
21
21
from bzrlib import (
22
22
    bzrdir,
23
23
    errors,
24
 
    lockdir,
25
 
    osutils,
26
 
    tests,
27
24
    )
28
25
from bzrlib.branch import Branch
29
 
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
 
26
from bzrlib.bzrdir import BzrDirMetaFormat1
30
27
from bzrlib.commit import Commit, NullCommitReporter
31
28
from bzrlib.config import BranchConfig
32
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
33
 
                           LockContention)
34
 
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
35
 
from bzrlib.workingtree import WorkingTree
 
29
from bzrlib.errors import (
 
30
    PointlessCommit,
 
31
    BzrError,
 
32
    SigningFailed,
 
33
    LockContention,
 
34
    )
 
35
from bzrlib.tests import (
 
36
    TestCaseWithTransport,
 
37
    test_foreign,
 
38
    )
 
39
from bzrlib.tests.features import (
 
40
    SymlinkFeature,
 
41
    )
 
42
from bzrlib.tests.matchers import MatchesAncestry
36
43
 
37
44
 
38
45
# TODO: Test commit with some added, and added-but-missing files
107
114
        tree2.unlock()
108
115
        self.assertEqual('version 2', text)
109
116
 
110
 
    def test_delete_commit(self):
111
 
        """Test a commit with a deleted file"""
 
117
    def test_commit_lossy_native(self):
 
118
        """Attempt a lossy commit to a native branch."""
 
119
        wt = self.make_branch_and_tree('.')
 
120
        b = wt.branch
 
121
        file('hello', 'w').write('hello world')
 
122
        wt.add('hello')
 
123
        revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
 
124
        self.assertEquals('revid', revid)
 
125
 
 
126
    def test_commit_lossy_foreign(self):
 
127
        """Attempt a lossy commit to a foreign branch."""
 
128
        test_foreign.register_dummy_foreign_for_test(self)
 
129
        wt = self.make_branch_and_tree('.',
 
130
            format=test_foreign.DummyForeignVcsDirFormat())
 
131
        b = wt.branch
 
132
        file('hello', 'w').write('hello world')
 
133
        wt.add('hello')
 
134
        revid = wt.commit(message='add hello', lossy=True,
 
135
            timestamp=1302659388, timezone=0)
 
136
        self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
 
137
 
 
138
    def test_commit_bound_lossy_foreign(self):
 
139
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
 
140
        test_foreign.register_dummy_foreign_for_test(self)
 
141
        foreign_branch = self.make_branch('foreign',
 
142
            format=test_foreign.DummyForeignVcsDirFormat())
 
143
        wt = foreign_branch.create_checkout("local")
 
144
        b = wt.branch
 
145
        file('local/hello', 'w').write('hello world')
 
146
        wt.add('hello')
 
147
        revid = wt.commit(message='add hello', lossy=True,
 
148
            timestamp=1302659388, timezone=0)
 
149
        self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
 
150
        self.assertEquals('dummy-v1:1302659388.0-0-0',
 
151
            foreign_branch.last_revision())
 
152
        self.assertEquals('dummy-v1:1302659388.0-0-0',
 
153
            wt.branch.last_revision())
 
154
 
 
155
    def test_missing_commit(self):
 
156
        """Test a commit with a missing file"""
112
157
        wt = self.make_branch_and_tree('.')
113
158
        b = wt.branch
114
159
        file('hello', 'w').write('hello world')
116
161
        wt.commit(message='add hello')
117
162
 
118
163
        os.remove('hello')
119
 
        wt.commit('removed hello', rev_id='rev2')
 
164
        reporter = CapturingReporter()
 
165
        wt.commit('removed hello', rev_id='rev2', reporter=reporter)
 
166
        self.assertEquals(
 
167
            [('missing', u'hello'), ('deleted', u'hello')],
 
168
            reporter.calls)
120
169
 
121
170
        tree = b.repository.revision_tree('rev2')
122
171
        self.assertFalse(tree.has_id('hello-id'))
125
174
        """Test a partial commit where a file was renamed but not committed.
126
175
 
127
176
        https://bugs.launchpad.net/bzr/+bug/83039
128
 
        
 
177
 
129
178
        If not handled properly, commit will try to snapshot
130
 
        dialog.py with olive/ as a parent, while 
 
179
        dialog.py with olive/ as a parent, while
131
180
        olive/ has not been snapshotted yet.
132
181
        """
133
182
        wt = self.make_branch_and_tree('.')
154
203
                          message='fails',
155
204
                          allow_pointless=False)
156
205
        self.assertEquals(b.revno(), 1)
157
 
        
 
206
 
158
207
    def test_commit_empty(self):
159
208
        """Commiting an empty tree works."""
160
209
        wt = self.make_branch_and_tree('.')
177
226
              ['hello-id', 'buongia-id'])
178
227
        wt.commit(message='add files',
179
228
                 rev_id='test@rev-1')
180
 
        
 
229
 
181
230
        os.remove('hello')
182
231
        file('buongia', 'w').write('new text')
183
232
        wt.commit(message='update text',
199
248
        self.assertTrue(tree2.has_filename('hello'))
200
249
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
201
250
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
202
 
        
 
251
 
203
252
        tree3 = b.repository.revision_tree('test@rev-3')
204
253
        tree3.lock_read()
205
254
        self.addCleanup(tree3.unlock)
224
273
        eq(tree1.id2path('hello-id'), 'hello')
225
274
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
226
275
        self.assertFalse(tree1.has_filename('fruity'))
227
 
        self.check_inventory_shape(tree1.inventory, ['hello'])
228
 
        ie = tree1.inventory['hello-id']
229
 
        eq(ie.revision, 'test@rev-1')
 
276
        self.check_tree_shape(tree1, ['hello'])
 
277
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
230
278
 
231
279
        tree2 = b.repository.revision_tree('test@rev-2')
232
280
        tree2.lock_read()
233
281
        self.addCleanup(tree2.unlock)
234
282
        eq(tree2.id2path('hello-id'), 'fruity')
235
283
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
236
 
        self.check_inventory_shape(tree2.inventory, ['fruity'])
237
 
        ie = tree2.inventory['hello-id']
238
 
        eq(ie.revision, 'test@rev-2')
 
284
        self.check_tree_shape(tree2, ['fruity'])
 
285
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
239
286
 
240
287
    def test_reused_rev_id(self):
241
288
        """Test that a revision id cannot be reused in a branch"""
262
309
        wt.commit('two', rev_id=r2, allow_pointless=False)
263
310
        wt.lock_read()
264
311
        try:
265
 
            self.check_inventory_shape(wt.read_working_inventory(),
266
 
                                       ['a/', 'a/hello', 'b/'])
 
312
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
267
313
        finally:
268
314
            wt.unlock()
269
315
 
272
318
        wt.commit('three', rev_id=r3, allow_pointless=False)
273
319
        wt.lock_read()
274
320
        try:
275
 
            self.check_inventory_shape(wt.read_working_inventory(),
 
321
            self.check_tree_shape(wt,
276
322
                                       ['a/', 'a/hello', 'a/b/'])
277
 
            self.check_inventory_shape(b.repository.get_revision_inventory(r3),
 
323
            self.check_tree_shape(b.repository.revision_tree(r3),
278
324
                                       ['a/', 'a/hello', 'a/b/'])
279
325
        finally:
280
326
            wt.unlock()
284
330
        wt.commit('four', rev_id=r4, allow_pointless=False)
285
331
        wt.lock_read()
286
332
        try:
287
 
            self.check_inventory_shape(wt.read_working_inventory(),
288
 
                                       ['a/', 'a/b/hello', 'a/b/'])
 
333
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
289
334
        finally:
290
335
            wt.unlock()
291
336
 
292
 
        inv = b.repository.get_revision_inventory(r4)
 
337
        inv = b.repository.get_inventory(r4)
293
338
        eq(inv['hello-id'].revision, r4)
294
339
        eq(inv['a-id'].revision, r1)
295
340
        eq(inv['b-id'].revision, r3)
323
368
        eq = self.assertEquals
324
369
        eq(b.revision_history(), rev_ids)
325
370
        for i in range(4):
326
 
            anc = b.repository.get_ancestry(rev_ids[i])
327
 
            eq(anc, [None] + rev_ids[:i+1])
 
371
            self.assertThat(rev_ids[:i+1],
 
372
                MatchesAncestry(b.repository, rev_ids[i]))
328
373
 
329
374
    def test_commit_new_subdir_child_selective(self):
330
375
        wt = self.make_branch_and_tree('.')
353
398
    def test_strict_commit_without_unknowns(self):
354
399
        """Try and commit with no unknown files and strict = True,
355
400
        should work."""
356
 
        from bzrlib.errors import StrictCommitFailed
357
401
        wt = self.make_branch_and_tree('.')
358
402
        b = wt.branch
359
403
        file('hello', 'w').write('hello world')
385
429
        wt = self.make_branch_and_tree('.')
386
430
        branch = wt.branch
387
431
        wt.commit("base", allow_pointless=True, rev_id='A')
388
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
432
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
389
433
        try:
390
434
            from bzrlib.testament import Testament
391
435
            # monkey patch gpg signing mechanism
409
453
        wt = self.make_branch_and_tree('.')
410
454
        branch = wt.branch
411
455
        wt.commit("base", allow_pointless=True, rev_id='A')
412
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
456
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
413
457
        try:
414
 
            from bzrlib.testament import Testament
415
458
            # monkey patch gpg signing mechanism
416
459
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
417
460
            config = MustSignConfig(branch)
423
466
                              working_tree=wt)
424
467
            branch = Branch.open(self.get_url('.'))
425
468
            self.assertEqual(branch.revision_history(), ['A'])
426
 
            self.failIf(branch.repository.has_revision('B'))
 
469
            self.assertFalse(branch.repository.has_revision('B'))
427
470
        finally:
428
471
            bzrlib.gpg.GPGStrategy = oldstrategy
429
472
 
484
527
        other_bzrdir = master_branch.bzrdir.sprout('other')
485
528
        other_tree = other_bzrdir.open_workingtree()
486
529
 
487
 
        # do a commit to the the other branch changing the content file so
 
530
        # do a commit to the other branch changing the content file so
488
531
        # that our commit after merging will have a merged revision in the
489
532
        # content file history.
490
533
        self.build_tree_contents([('other/content_file', 'change in other\n')])
501
544
        bound_tree.commit(message='commit of merge in bound tree')
502
545
 
503
546
    def test_commit_reporting_after_merge(self):
504
 
        # when doing a commit of a merge, the reporter needs to still 
 
547
        # when doing a commit of a merge, the reporter needs to still
505
548
        # be called for each item that is added/removed/deleted.
506
549
        this_tree = self.make_branch_and_tree('this')
507
550
        # we need a bunch of files and dirs, to perform one action on each.
550
593
        this_tree.merge_from_branch(other_tree.branch)
551
594
        reporter = CapturingReporter()
552
595
        this_tree.commit('do the commit', reporter=reporter)
553
 
        self.assertEqual([
554
 
            ('change', 'unchanged', ''),
555
 
            ('change', 'unchanged', 'dirtoleave'),
556
 
            ('change', 'unchanged', 'filetoleave'),
 
596
        expected = set([
557
597
            ('change', 'modified', 'filetomodify'),
558
598
            ('change', 'added', 'newdir'),
559
599
            ('change', 'added', 'newfile'),
563
603
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
564
604
            ('deleted', 'dirtoremove'),
565
605
            ('deleted', 'filetoremove'),
566
 
            ],
567
 
            reporter.calls)
 
606
            ])
 
607
        result = set(reporter.calls)
 
608
        missing = expected - result
 
609
        new = result - expected
 
610
        self.assertEqual((set(), set()), (missing, new))
568
611
 
569
612
    def test_commit_removals_respects_filespec(self):
570
613
        """Commit respects the specified_files for removals."""
660
703
    def test_commit_unversioned_specified(self):
661
704
        """Commit should raise if specified files isn't in basis or worktree"""
662
705
        tree = self.make_branch_and_tree('.')
663
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
706
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
664
707
                          'message', specific_files=['bogus'])
665
708
 
666
709
    class Callback(object):
667
 
        
 
710
 
668
711
        def __init__(self, message, testcase):
669
712
            self.called = False
670
713
            self.message = message
698
741
        """Callback should not be invoked for pointless commit"""
699
742
        tree = self.make_branch_and_tree('.')
700
743
        cb = self.Callback(u'commit 2', self)
701
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
 
744
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
702
745
                          allow_pointless=False)
703
746
        self.assertFalse(cb.called)
704
747
 
708
751
        cb = self.Callback(u'commit 2', self)
709
752
        repository = tree.branch.repository
710
753
        # simulate network failure
711
 
        def raise_(self, arg, arg2):
 
754
        def raise_(self, arg, arg2, arg3=None, arg4=None):
712
755
            raise errors.NoSuchFile('foo')
713
756
        repository.add_inventory = raise_
 
757
        repository.add_inventory_by_delta = raise_
714
758
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
715
759
        self.assertFalse(cb.called)
716
760
 
738
782
        tree.add('a/c/d')
739
783
        tree.rename_one('a/z/x', 'a/c/d/x')
740
784
        tree.commit('test', specific_files=['a/z/y'])
741
 
 
 
785
 
742
786
    def test_commit_no_author(self):
743
787
        """The default kwarg author in MutableTree.commit should not add
744
788
        the 'author' revision property.
747
791
        rev_id = tree.commit('commit 1')
748
792
        rev = tree.branch.repository.get_revision(rev_id)
749
793
        self.assertFalse('author' in rev.properties)
 
794
        self.assertFalse('authors' in rev.properties)
750
795
 
751
796
    def test_commit_author(self):
752
797
        """Passing a non-empty author kwarg to MutableTree.commit should add
753
798
        the 'author' revision property.
754
799
        """
755
800
        tree = self.make_branch_and_tree('foo')
756
 
        rev_id = tree.commit('commit 1', author='John Doe <jdoe@example.com>')
 
801
        rev_id = self.callDeprecated(['The parameter author was '
 
802
                'deprecated in version 1.13. Use authors instead'],
 
803
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
757
804
        rev = tree.branch.repository.get_revision(rev_id)
758
805
        self.assertEqual('John Doe <jdoe@example.com>',
759
 
                         rev.properties['author'])
 
806
                         rev.properties['authors'])
 
807
        self.assertFalse('author' in rev.properties)
 
808
 
 
809
    def test_commit_empty_authors_list(self):
 
810
        """Passing an empty list to authors shouldn't add the property."""
 
811
        tree = self.make_branch_and_tree('foo')
 
812
        rev_id = tree.commit('commit 1', authors=[])
 
813
        rev = tree.branch.repository.get_revision(rev_id)
 
814
        self.assertFalse('author' in rev.properties)
 
815
        self.assertFalse('authors' in rev.properties)
 
816
 
 
817
    def test_multiple_authors(self):
 
818
        tree = self.make_branch_and_tree('foo')
 
819
        rev_id = tree.commit('commit 1',
 
820
                authors=['John Doe <jdoe@example.com>',
 
821
                         'Jane Rey <jrey@example.com>'])
 
822
        rev = tree.branch.repository.get_revision(rev_id)
 
823
        self.assertEqual('John Doe <jdoe@example.com>\n'
 
824
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
 
825
        self.assertFalse('author' in rev.properties)
 
826
 
 
827
    def test_author_and_authors_incompatible(self):
 
828
        tree = self.make_branch_and_tree('foo')
 
829
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
830
                authors=['John Doe <jdoe@example.com>',
 
831
                         'Jane Rey <jrey@example.com>'],
 
832
                author="Jack Me <jme@example.com>")
 
833
 
 
834
    def test_author_with_newline_rejected(self):
 
835
        tree = self.make_branch_and_tree('foo')
 
836
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
837
                authors=['John\nDoe <jdoe@example.com>'])
760
838
 
761
839
    def test_commit_with_checkout_and_branch_sharing_repo(self):
762
840
        repo = self.make_repository('repo', shared=True)