~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: 2006-04-24 10:31:28 UTC
  • mfrom: (1684.1.2 bzr.mbp.integration)
  • Revision ID: pqm@pqm.ubuntu.com-20060424103128-a637f56a7c529bad
(mbp) tutorial improvements

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005-2011 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by 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
 
# 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
 
from bzrlib import (
22
 
    bzrdir,
23
 
    errors,
24
 
    )
 
21
from bzrlib.tests import TestCaseWithTransport
25
22
from bzrlib.branch import Branch
26
 
from bzrlib.bzrdir import BzrDirMetaFormat1
27
 
from bzrlib.commit import Commit, NullCommitReporter
 
23
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
 
24
from bzrlib.workingtree import WorkingTree
 
25
from bzrlib.commit import Commit
28
26
from bzrlib.config import BranchConfig
29
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
 
27
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
30
28
                           LockContention)
31
 
from bzrlib.tests import (
32
 
    SymlinkFeature,
33
 
    TestCaseWithTransport,
34
 
    test_foreign,
35
 
    )
36
29
 
37
30
 
38
31
# TODO: Test commit with some added, and added-but-missing files
52
45
        return "bzrlib.ahook bzrlib.ahook"
53
46
 
54
47
 
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
 
 
78
48
class TestCommit(TestCaseWithTransport):
79
49
 
80
50
    def test_simple_commit(self):
96
66
        eq(rev.message, 'add hello')
97
67
 
98
68
        tree1 = b.repository.revision_tree(rh[0])
99
 
        tree1.lock_read()
100
69
        text = tree1.get_file_text(file_id)
101
 
        tree1.unlock()
102
 
        self.assertEqual('hello world', text)
 
70
        eq(text, 'hello world')
103
71
 
104
72
        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)
109
 
 
110
 
    def test_commit_lossy_native(self):
111
 
        """Attempt a lossy commit to a native branch."""
112
 
        wt = self.make_branch_and_tree('.')
113
 
        b = wt.branch
114
 
        file('hello', 'w').write('hello world')
115
 
        wt.add('hello')
116
 
        revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
117
 
        self.assertEquals('revid', revid)
118
 
 
119
 
    def test_commit_lossy_foreign(self):
120
 
        """Attempt a lossy commit to a foreign branch."""
121
 
        test_foreign.register_dummy_foreign_for_test(self)
122
 
        wt = self.make_branch_and_tree('.',
123
 
            format=test_foreign.DummyForeignVcsDirFormat())
124
 
        b = wt.branch
125
 
        file('hello', 'w').write('hello world')
126
 
        wt.add('hello')
127
 
        revid = wt.commit(message='add hello', lossy=True,
128
 
            timestamp=1302659388, timezone=0)
129
 
        self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
130
 
 
131
 
    def test_commit_bound_lossy_foreign(self):
132
 
        """Attempt a lossy commit to a bzr branch bound to a foreign branch."""
133
 
        test_foreign.register_dummy_foreign_for_test(self)
134
 
        foreign_branch = self.make_branch('foreign',
135
 
            format=test_foreign.DummyForeignVcsDirFormat())
136
 
        wt = foreign_branch.create_checkout("local")
137
 
        b = wt.branch
138
 
        file('local/hello', 'w').write('hello world')
139
 
        wt.add('hello')
140
 
        revid = wt.commit(message='add hello', lossy=True,
141
 
            timestamp=1302659388, timezone=0)
142
 
        self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
143
 
        self.assertEquals('dummy-v1:1302659388.0-0-0',
144
 
            foreign_branch.last_revision())
145
 
        self.assertEquals('dummy-v1:1302659388.0-0-0',
146
 
            wt.branch.last_revision())
147
 
 
148
 
    def test_missing_commit(self):
149
 
        """Test a commit with a missing file"""
 
73
        eq(tree2.get_file_text(file_id), 'version 2')
 
74
 
 
75
    def test_delete_commit(self):
 
76
        """Test a commit with a deleted file"""
150
77
        wt = self.make_branch_and_tree('.')
151
78
        b = wt.branch
152
79
        file('hello', 'w').write('hello world')
159
86
        tree = b.repository.revision_tree('rev2')
160
87
        self.assertFalse(tree.has_id('hello-id'))
161
88
 
162
 
    def test_partial_commit_move(self):
163
 
        """Test a partial commit where a file was renamed but not committed.
164
 
 
165
 
        https://bugs.launchpad.net/bzr/+bug/83039
166
 
 
167
 
        If not handled properly, commit will try to snapshot
168
 
        dialog.py with olive/ as a parent, while
169
 
        olive/ has not been snapshotted yet.
170
 
        """
171
 
        wt = self.make_branch_and_tree('.')
172
 
        b = wt.branch
173
 
        self.build_tree(['annotate/', 'annotate/foo.py',
174
 
                         'olive/', 'olive/dialog.py'
175
 
                        ])
176
 
        wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
177
 
        wt.commit(message='add files')
178
 
        wt.rename_one("olive/dialog.py", "aaa")
179
 
        self.build_tree_contents([('annotate/foo.py', 'modified\n')])
180
 
        wt.commit('renamed hello', specific_files=["annotate"])
181
 
 
182
89
    def test_pointless_commit(self):
183
90
        """Commit refuses unless there are changes or it's forced."""
184
91
        wt = self.make_branch_and_tree('.')
192
99
                          message='fails',
193
100
                          allow_pointless=False)
194
101
        self.assertEquals(b.revno(), 1)
195
 
 
 
102
        
196
103
    def test_commit_empty(self):
197
104
        """Commiting an empty tree works."""
198
105
        wt = self.make_branch_and_tree('.')
215
122
              ['hello-id', 'buongia-id'])
216
123
        wt.commit(message='add files',
217
124
                 rev_id='test@rev-1')
218
 
 
 
125
        
219
126
        os.remove('hello')
220
127
        file('buongia', 'w').write('new text')
221
128
        wt.commit(message='update text',
232
139
        eq(b.revno(), 3)
233
140
 
234
141
        tree2 = b.repository.revision_tree('test@rev-2')
235
 
        tree2.lock_read()
236
 
        self.addCleanup(tree2.unlock)
237
142
        self.assertTrue(tree2.has_filename('hello'))
238
143
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
239
144
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
240
 
 
 
145
        
241
146
        tree3 = b.repository.revision_tree('test@rev-3')
242
 
        tree3.lock_read()
243
 
        self.addCleanup(tree3.unlock)
244
147
        self.assertFalse(tree3.has_filename('hello'))
245
148
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
246
149
 
257
160
 
258
161
        eq = self.assertEquals
259
162
        tree1 = b.repository.revision_tree('test@rev-1')
260
 
        tree1.lock_read()
261
 
        self.addCleanup(tree1.unlock)
262
163
        eq(tree1.id2path('hello-id'), 'hello')
263
164
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
264
165
        self.assertFalse(tree1.has_filename('fruity'))
265
 
        self.check_tree_shape(tree1, ['hello'])
266
 
        eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
 
166
        self.check_inventory_shape(tree1.inventory, ['hello'])
 
167
        ie = tree1.inventory['hello-id']
 
168
        eq(ie.revision, 'test@rev-1')
267
169
 
268
170
        tree2 = b.repository.revision_tree('test@rev-2')
269
 
        tree2.lock_read()
270
 
        self.addCleanup(tree2.unlock)
271
171
        eq(tree2.id2path('hello-id'), 'fruity')
272
172
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
273
 
        self.check_tree_shape(tree2, ['fruity'])
274
 
        eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
 
173
        self.check_inventory_shape(tree2.inventory, ['fruity'])
 
174
        ie = tree2.inventory['hello-id']
 
175
        eq(ie.revision, 'test@rev-2')
275
176
 
276
177
    def test_reused_rev_id(self):
277
178
        """Test that a revision id cannot be reused in a branch"""
296
197
        wt.move(['hello'], 'a')
297
198
        r2 = 'test@rev-2'
298
199
        wt.commit('two', rev_id=r2, allow_pointless=False)
299
 
        wt.lock_read()
300
 
        try:
301
 
            self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
302
 
        finally:
303
 
            wt.unlock()
 
200
        self.check_inventory_shape(wt.read_working_inventory(),
 
201
                                   ['a', 'a/hello', 'b'])
304
202
 
305
203
        wt.move(['b'], 'a')
306
204
        r3 = 'test@rev-3'
307
205
        wt.commit('three', rev_id=r3, allow_pointless=False)
308
 
        wt.lock_read()
309
 
        try:
310
 
            self.check_tree_shape(wt,
311
 
                                       ['a/', 'a/hello', 'a/b/'])
312
 
            self.check_tree_shape(b.repository.revision_tree(r3),
313
 
                                       ['a/', 'a/hello', 'a/b/'])
314
 
        finally:
315
 
            wt.unlock()
 
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'])
316
210
 
317
211
        wt.move(['a/hello'], 'a/b')
318
212
        r4 = 'test@rev-4'
319
213
        wt.commit('four', rev_id=r4, allow_pointless=False)
320
 
        wt.lock_read()
321
 
        try:
322
 
            self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
323
 
        finally:
324
 
            wt.unlock()
 
214
        self.check_inventory_shape(wt.read_working_inventory(),
 
215
                                   ['a', 'a/b/hello', 'a/b'])
325
216
 
326
 
        inv = b.repository.get_inventory(r4)
 
217
        inv = b.repository.get_revision_inventory(r4)
327
218
        eq(inv['hello-id'].revision, r4)
328
219
        eq(inv['a-id'].revision, r1)
329
220
        eq(inv['b-id'].revision, r3)
330
 
 
 
221
        
331
222
    def test_removed_commit(self):
332
223
        """Commit with a removed file"""
333
224
        wt = self.make_branch_and_tree('.')
387
278
    def test_strict_commit_without_unknowns(self):
388
279
        """Try and commit with no unknown files and strict = True,
389
280
        should work."""
 
281
        from bzrlib.errors import StrictCommitFailed
390
282
        wt = self.make_branch_and_tree('.')
391
283
        b = wt.branch
392
284
        file('hello', 'w').write('hello world')
418
310
        wt = self.make_branch_and_tree('.')
419
311
        branch = wt.branch
420
312
        wt.commit("base", allow_pointless=True, rev_id='A')
421
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
313
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
422
314
        try:
423
315
            from bzrlib.testament import Testament
424
316
            # monkey patch gpg signing mechanism
427
319
                                                      allow_pointless=True,
428
320
                                                      rev_id='B',
429
321
                                                      working_tree=wt)
430
 
            def sign(text):
431
 
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
432
 
            self.assertEqual(sign(Testament.from_revision(branch.repository,
433
 
                             'B').as_short_text()),
 
322
            self.assertEqual(Testament.from_revision(branch.repository,
 
323
                             'B').as_short_text(),
434
324
                             branch.repository.get_signature_text('B'))
435
325
        finally:
436
326
            bzrlib.gpg.GPGStrategy = oldstrategy
442
332
        wt = self.make_branch_and_tree('.')
443
333
        branch = wt.branch
444
334
        wt.commit("base", allow_pointless=True, rev_id='A')
445
 
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
 
335
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
446
336
        try:
 
337
            from bzrlib.testament import Testament
447
338
            # monkey patch gpg signing mechanism
448
339
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
449
340
            config = MustSignConfig(branch)
455
346
                              working_tree=wt)
456
347
            branch = Branch.open(self.get_url('.'))
457
348
            self.assertEqual(branch.revision_history(), ['A'])
458
 
            self.assertFalse(branch.repository.has_revision('B'))
 
349
            self.failIf(branch.repository.has_revision('B'))
459
350
        finally:
460
351
            bzrlib.gpg.GPGStrategy = oldstrategy
461
352
 
501
392
            self.assertRaises(LockContention, wt.commit, 'silly')
502
393
        finally:
503
394
            master_branch.unlock()
504
 
 
505
 
    def test_commit_bound_merge(self):
506
 
        # see bug #43959; commit of a merge in a bound branch fails to push
507
 
        # the new commit into the master
508
 
        master_branch = self.make_branch('master')
509
 
        bound_tree = self.make_branch_and_tree('bound')
510
 
        bound_tree.branch.bind(master_branch)
511
 
 
512
 
        self.build_tree_contents([('bound/content_file', 'initial contents\n')])
513
 
        bound_tree.add(['content_file'])
514
 
        bound_tree.commit(message='woo!')
515
 
 
516
 
        other_bzrdir = master_branch.bzrdir.sprout('other')
517
 
        other_tree = other_bzrdir.open_workingtree()
518
 
 
519
 
        # do a commit to the other branch changing the content file so
520
 
        # that our commit after merging will have a merged revision in the
521
 
        # content file history.
522
 
        self.build_tree_contents([('other/content_file', 'change in other\n')])
523
 
        other_tree.commit('change in other')
524
 
 
525
 
        # do a merge into the bound branch from other, and then change the
526
 
        # content file locally to force a new revision (rather than using the
527
 
        # revision from other). This forces extra processing in commit.
528
 
        bound_tree.merge_from_branch(other_tree.branch)
529
 
        self.build_tree_contents([('bound/content_file', 'change in bound\n')])
530
 
 
531
 
        # before #34959 was fixed, this failed with 'revision not present in
532
 
        # weave' when trying to implicitly push from the bound branch to the master
533
 
        bound_tree.commit(message='commit of merge in bound tree')
534
 
 
535
 
    def test_commit_reporting_after_merge(self):
536
 
        # when doing a commit of a merge, the reporter needs to still
537
 
        # be called for each item that is added/removed/deleted.
538
 
        this_tree = self.make_branch_and_tree('this')
539
 
        # we need a bunch of files and dirs, to perform one action on each.
540
 
        self.build_tree([
541
 
            'this/dirtorename/',
542
 
            'this/dirtoreparent/',
543
 
            'this/dirtoleave/',
544
 
            'this/dirtoremove/',
545
 
            'this/filetoreparent',
546
 
            'this/filetorename',
547
 
            'this/filetomodify',
548
 
            'this/filetoremove',
549
 
            'this/filetoleave']
550
 
            )
551
 
        this_tree.add([
552
 
            'dirtorename',
553
 
            'dirtoreparent',
554
 
            'dirtoleave',
555
 
            'dirtoremove',
556
 
            'filetoreparent',
557
 
            'filetorename',
558
 
            'filetomodify',
559
 
            'filetoremove',
560
 
            'filetoleave']
561
 
            )
562
 
        this_tree.commit('create_files')
563
 
        other_dir = this_tree.bzrdir.sprout('other')
564
 
        other_tree = other_dir.open_workingtree()
565
 
        other_tree.lock_write()
566
 
        # perform the needed actions on the files and dirs.
567
 
        try:
568
 
            other_tree.rename_one('dirtorename', 'renameddir')
569
 
            other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
570
 
            other_tree.rename_one('filetorename', 'renamedfile')
571
 
            other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
572
 
            other_tree.remove(['dirtoremove', 'filetoremove'])
573
 
            self.build_tree_contents([
574
 
                ('other/newdir/', ),
575
 
                ('other/filetomodify', 'new content'),
576
 
                ('other/newfile', 'new file content')])
577
 
            other_tree.add('newfile')
578
 
            other_tree.add('newdir/')
579
 
            other_tree.commit('modify all sample files and dirs.')
580
 
        finally:
581
 
            other_tree.unlock()
582
 
        this_tree.merge_from_branch(other_tree.branch)
583
 
        reporter = CapturingReporter()
584
 
        this_tree.commit('do the commit', reporter=reporter)
585
 
        expected = set([
586
 
            ('change', 'modified', 'filetomodify'),
587
 
            ('change', 'added', 'newdir'),
588
 
            ('change', 'added', 'newfile'),
589
 
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
590
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
591
 
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
592
 
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
593
 
            ('deleted', 'dirtoremove'),
594
 
            ('deleted', 'filetoremove'),
595
 
            ])
596
 
        result = set(reporter.calls)
597
 
        missing = expected - result
598
 
        new = result - expected
599
 
        self.assertEqual((set(), set()), (missing, new))
600
 
 
601
 
    def test_commit_removals_respects_filespec(self):
602
 
        """Commit respects the specified_files for removals."""
603
 
        tree = self.make_branch_and_tree('.')
604
 
        self.build_tree(['a', 'b'])
605
 
        tree.add(['a', 'b'])
606
 
        tree.commit('added a, b')
607
 
        tree.remove(['a', 'b'])
608
 
        tree.commit('removed a', specific_files='a')
609
 
        basis = tree.basis_tree()
610
 
        tree.lock_read()
611
 
        try:
612
 
            self.assertIs(None, basis.path2id('a'))
613
 
            self.assertFalse(basis.path2id('b') is None)
614
 
        finally:
615
 
            tree.unlock()
616
 
 
617
 
    def test_commit_saves_1ms_timestamp(self):
618
 
        """Passing in a timestamp is saved with 1ms resolution"""
619
 
        tree = self.make_branch_and_tree('.')
620
 
        self.build_tree(['a'])
621
 
        tree.add('a')
622
 
        tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
623
 
                    rev_id='a1')
624
 
 
625
 
        rev = tree.branch.repository.get_revision('a1')
626
 
        self.assertEqual(1153248633.419, rev.timestamp)
627
 
 
628
 
    def test_commit_has_1ms_resolution(self):
629
 
        """Allowing commit to generate the timestamp also has 1ms resolution"""
630
 
        tree = self.make_branch_and_tree('.')
631
 
        self.build_tree(['a'])
632
 
        tree.add('a')
633
 
        tree.commit('added a', rev_id='a1')
634
 
 
635
 
        rev = tree.branch.repository.get_revision('a1')
636
 
        timestamp = rev.timestamp
637
 
        timestamp_1ms = round(timestamp, 3)
638
 
        self.assertEqual(timestamp_1ms, timestamp)
639
 
 
640
 
    def assertBasisTreeKind(self, kind, tree, file_id):
641
 
        basis = tree.basis_tree()
642
 
        basis.lock_read()
643
 
        try:
644
 
            self.assertEqual(kind, basis.kind(file_id))
645
 
        finally:
646
 
            basis.unlock()
647
 
 
648
 
    def test_commit_kind_changes(self):
649
 
        self.requireFeature(SymlinkFeature)
650
 
        tree = self.make_branch_and_tree('.')
651
 
        os.symlink('target', 'name')
652
 
        tree.add('name', 'a-file-id')
653
 
        tree.commit('Added a symlink')
654
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
655
 
 
656
 
        os.unlink('name')
657
 
        self.build_tree(['name'])
658
 
        tree.commit('Changed symlink to file')
659
 
        self.assertBasisTreeKind('file', tree, 'a-file-id')
660
 
 
661
 
        os.unlink('name')
662
 
        os.symlink('target', 'name')
663
 
        tree.commit('file to symlink')
664
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
665
 
 
666
 
        os.unlink('name')
667
 
        os.mkdir('name')
668
 
        tree.commit('symlink to directory')
669
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
670
 
 
671
 
        os.rmdir('name')
672
 
        os.symlink('target', 'name')
673
 
        tree.commit('directory to symlink')
674
 
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
675
 
 
676
 
        # prepare for directory <-> file tests
677
 
        os.unlink('name')
678
 
        os.mkdir('name')
679
 
        tree.commit('symlink to directory')
680
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
681
 
 
682
 
        os.rmdir('name')
683
 
        self.build_tree(['name'])
684
 
        tree.commit('Changed directory to file')
685
 
        self.assertBasisTreeKind('file', tree, 'a-file-id')
686
 
 
687
 
        os.unlink('name')
688
 
        os.mkdir('name')
689
 
        tree.commit('file to directory')
690
 
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
691
 
 
692
 
    def test_commit_unversioned_specified(self):
693
 
        """Commit should raise if specified files isn't in basis or worktree"""
694
 
        tree = self.make_branch_and_tree('.')
695
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
696
 
                          'message', specific_files=['bogus'])
697
 
 
698
 
    class Callback(object):
699
 
 
700
 
        def __init__(self, message, testcase):
701
 
            self.called = False
702
 
            self.message = message
703
 
            self.testcase = testcase
704
 
 
705
 
        def __call__(self, commit_obj):
706
 
            self.called = True
707
 
            self.testcase.assertTrue(isinstance(commit_obj, Commit))
708
 
            return self.message
709
 
 
710
 
    def test_commit_callback(self):
711
 
        """Commit should invoke a callback to get the message"""
712
 
 
713
 
        tree = self.make_branch_and_tree('.')
714
 
        try:
715
 
            tree.commit()
716
 
        except Exception, e:
717
 
            self.assertTrue(isinstance(e, BzrError))
718
 
            self.assertEqual('The message or message_callback keyword'
719
 
                             ' parameter is required for commit().', str(e))
720
 
        else:
721
 
            self.fail('exception not raised')
722
 
        cb = self.Callback(u'commit 1', self)
723
 
        tree.commit(message_callback=cb)
724
 
        self.assertTrue(cb.called)
725
 
        repository = tree.branch.repository
726
 
        message = repository.get_revision(tree.last_revision()).message
727
 
        self.assertEqual('commit 1', message)
728
 
 
729
 
    def test_no_callback_pointless(self):
730
 
        """Callback should not be invoked for pointless commit"""
731
 
        tree = self.make_branch_and_tree('.')
732
 
        cb = self.Callback(u'commit 2', self)
733
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
734
 
                          allow_pointless=False)
735
 
        self.assertFalse(cb.called)
736
 
 
737
 
    def test_no_callback_netfailure(self):
738
 
        """Callback should not be invoked if connectivity fails"""
739
 
        tree = self.make_branch_and_tree('.')
740
 
        cb = self.Callback(u'commit 2', self)
741
 
        repository = tree.branch.repository
742
 
        # simulate network failure
743
 
        def raise_(self, arg, arg2, arg3=None, arg4=None):
744
 
            raise errors.NoSuchFile('foo')
745
 
        repository.add_inventory = raise_
746
 
        repository.add_inventory_by_delta = raise_
747
 
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
748
 
        self.assertFalse(cb.called)
749
 
 
750
 
    def test_selected_file_merge_commit(self):
751
 
        """Ensure the correct error is raised"""
752
 
        tree = self.make_branch_and_tree('foo')
753
 
        # pending merge would turn into a left parent
754
 
        tree.commit('commit 1')
755
 
        tree.add_parent_tree_id('example')
756
 
        self.build_tree(['foo/bar', 'foo/baz'])
757
 
        tree.add(['bar', 'baz'])
758
 
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
759
 
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
760
 
        self.assertEqual(['bar', 'baz'], err.files)
761
 
        self.assertEqual('Selected-file commit of merges is not supported'
762
 
                         ' yet: files bar, baz', str(err))
763
 
 
764
 
    def test_commit_ordering(self):
765
 
        """Test of corner-case commit ordering error"""
766
 
        tree = self.make_branch_and_tree('.')
767
 
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
768
 
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
769
 
        tree.commit('setup')
770
 
        self.build_tree(['a/c/d/'])
771
 
        tree.add('a/c/d')
772
 
        tree.rename_one('a/z/x', 'a/c/d/x')
773
 
        tree.commit('test', specific_files=['a/z/y'])
774
 
 
775
 
    def test_commit_no_author(self):
776
 
        """The default kwarg author in MutableTree.commit should not add
777
 
        the 'author' revision property.
778
 
        """
779
 
        tree = self.make_branch_and_tree('foo')
780
 
        rev_id = tree.commit('commit 1')
781
 
        rev = tree.branch.repository.get_revision(rev_id)
782
 
        self.assertFalse('author' in rev.properties)
783
 
        self.assertFalse('authors' in rev.properties)
784
 
 
785
 
    def test_commit_author(self):
786
 
        """Passing a non-empty author kwarg to MutableTree.commit should add
787
 
        the 'author' revision property.
788
 
        """
789
 
        tree = self.make_branch_and_tree('foo')
790
 
        rev_id = self.callDeprecated(['The parameter author was '
791
 
                'deprecated in version 1.13. Use authors instead'],
792
 
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
793
 
        rev = tree.branch.repository.get_revision(rev_id)
794
 
        self.assertEqual('John Doe <jdoe@example.com>',
795
 
                         rev.properties['authors'])
796
 
        self.assertFalse('author' in rev.properties)
797
 
 
798
 
    def test_commit_empty_authors_list(self):
799
 
        """Passing an empty list to authors shouldn't add the property."""
800
 
        tree = self.make_branch_and_tree('foo')
801
 
        rev_id = tree.commit('commit 1', authors=[])
802
 
        rev = tree.branch.repository.get_revision(rev_id)
803
 
        self.assertFalse('author' in rev.properties)
804
 
        self.assertFalse('authors' in rev.properties)
805
 
 
806
 
    def test_multiple_authors(self):
807
 
        tree = self.make_branch_and_tree('foo')
808
 
        rev_id = tree.commit('commit 1',
809
 
                authors=['John Doe <jdoe@example.com>',
810
 
                         'Jane Rey <jrey@example.com>'])
811
 
        rev = tree.branch.repository.get_revision(rev_id)
812
 
        self.assertEqual('John Doe <jdoe@example.com>\n'
813
 
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
814
 
        self.assertFalse('author' in rev.properties)
815
 
 
816
 
    def test_author_and_authors_incompatible(self):
817
 
        tree = self.make_branch_and_tree('foo')
818
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
819
 
                authors=['John Doe <jdoe@example.com>',
820
 
                         'Jane Rey <jrey@example.com>'],
821
 
                author="Jack Me <jme@example.com>")
822
 
 
823
 
    def test_author_with_newline_rejected(self):
824
 
        tree = self.make_branch_and_tree('foo')
825
 
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
826
 
                authors=['John\nDoe <jdoe@example.com>'])
827
 
 
828
 
    def test_commit_with_checkout_and_branch_sharing_repo(self):
829
 
        repo = self.make_repository('repo', shared=True)
830
 
        # make_branch_and_tree ignores shared repos
831
 
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
832
 
        tree2 = branch.create_checkout('repo/tree2')
833
 
        tree2.commit('message', rev_id='rev1')
834
 
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))