~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: 2011-05-04 12:10:51 UTC
  • mfrom: (5819.1.4 777007-developer-doc)
  • Revision ID: pqm@pqm.ubuntu.com-20110504121051-aovlsmqiivjmc4fc
(jelmer) Small fixes to developer documentation. (Jonathan Riddell)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 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
19
19
 
20
20
import bzrlib
21
21
from bzrlib import (
 
22
    bzrdir,
22
23
    errors,
23
 
    lockdir,
24
24
    )
25
25
from bzrlib.branch import Branch
26
 
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
 
26
from bzrlib.bzrdir import BzrDirMetaFormat1
27
27
from bzrlib.commit import Commit, NullCommitReporter
28
28
from bzrlib.config import BranchConfig
29
 
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed, 
 
29
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
30
30
                           LockContention)
31
 
from bzrlib.tests import TestCaseWithTransport
32
 
from bzrlib.workingtree import WorkingTree
 
31
from bzrlib.tests import (
 
32
    SymlinkFeature,
 
33
    TestCaseWithTransport,
 
34
    test_foreign,
 
35
    )
33
36
 
34
37
 
35
38
# TODO: Test commit with some added, and added-but-missing files
68
71
    def renamed(self, change, old_path, new_path):
69
72
        self.calls.append(('renamed', change, old_path, new_path))
70
73
 
 
74
    def is_verbose(self):
 
75
        return True
 
76
 
71
77
 
72
78
class TestCommit(TestCaseWithTransport):
73
79
 
90
96
        eq(rev.message, 'add hello')
91
97
 
92
98
        tree1 = b.repository.revision_tree(rh[0])
 
99
        tree1.lock_read()
93
100
        text = tree1.get_file_text(file_id)
94
 
        eq(text, 'hello world')
 
101
        tree1.unlock()
 
102
        self.assertEqual('hello world', text)
95
103
 
96
104
        tree2 = b.repository.revision_tree(rh[1])
97
 
        eq(tree2.get_file_text(file_id), 'version 2')
98
 
 
99
 
    def test_delete_commit(self):
100
 
        """Test a commit with a deleted file"""
 
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"""
101
150
        wt = self.make_branch_and_tree('.')
102
151
        b = wt.branch
103
152
        file('hello', 'w').write('hello world')
110
159
        tree = b.repository.revision_tree('rev2')
111
160
        self.assertFalse(tree.has_id('hello-id'))
112
161
 
 
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
 
113
182
    def test_pointless_commit(self):
114
183
        """Commit refuses unless there are changes or it's forced."""
115
184
        wt = self.make_branch_and_tree('.')
123
192
                          message='fails',
124
193
                          allow_pointless=False)
125
194
        self.assertEquals(b.revno(), 1)
126
 
        
 
195
 
127
196
    def test_commit_empty(self):
128
197
        """Commiting an empty tree works."""
129
198
        wt = self.make_branch_and_tree('.')
146
215
              ['hello-id', 'buongia-id'])
147
216
        wt.commit(message='add files',
148
217
                 rev_id='test@rev-1')
149
 
        
 
218
 
150
219
        os.remove('hello')
151
220
        file('buongia', 'w').write('new text')
152
221
        wt.commit(message='update text',
163
232
        eq(b.revno(), 3)
164
233
 
165
234
        tree2 = b.repository.revision_tree('test@rev-2')
 
235
        tree2.lock_read()
 
236
        self.addCleanup(tree2.unlock)
166
237
        self.assertTrue(tree2.has_filename('hello'))
167
238
        self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
168
239
        self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
169
 
        
 
240
 
170
241
        tree3 = b.repository.revision_tree('test@rev-3')
 
242
        tree3.lock_read()
 
243
        self.addCleanup(tree3.unlock)
171
244
        self.assertFalse(tree3.has_filename('hello'))
172
245
        self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
173
246
 
184
257
 
185
258
        eq = self.assertEquals
186
259
        tree1 = b.repository.revision_tree('test@rev-1')
 
260
        tree1.lock_read()
 
261
        self.addCleanup(tree1.unlock)
187
262
        eq(tree1.id2path('hello-id'), 'hello')
188
263
        eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
189
264
        self.assertFalse(tree1.has_filename('fruity'))
192
267
        eq(ie.revision, 'test@rev-1')
193
268
 
194
269
        tree2 = b.repository.revision_tree('test@rev-2')
 
270
        tree2.lock_read()
 
271
        self.addCleanup(tree2.unlock)
195
272
        eq(tree2.id2path('hello-id'), 'fruity')
196
273
        eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
197
274
        self.check_inventory_shape(tree2.inventory, ['fruity'])
221
298
        wt.move(['hello'], 'a')
222
299
        r2 = 'test@rev-2'
223
300
        wt.commit('two', rev_id=r2, allow_pointless=False)
224
 
        self.check_inventory_shape(wt.read_working_inventory(),
225
 
                                   ['a', 'a/hello', 'b'])
 
301
        wt.lock_read()
 
302
        try:
 
303
            self.check_inventory_shape(wt.read_working_inventory(),
 
304
                                       ['a/', 'a/hello', 'b/'])
 
305
        finally:
 
306
            wt.unlock()
226
307
 
227
308
        wt.move(['b'], 'a')
228
309
        r3 = 'test@rev-3'
229
310
        wt.commit('three', rev_id=r3, allow_pointless=False)
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'])
 
311
        wt.lock_read()
 
312
        try:
 
313
            self.check_inventory_shape(wt.read_working_inventory(),
 
314
                                       ['a/', 'a/hello', 'a/b/'])
 
315
            self.check_inventory_shape(b.repository.get_inventory(r3),
 
316
                                       ['a/', 'a/hello', 'a/b/'])
 
317
        finally:
 
318
            wt.unlock()
234
319
 
235
320
        wt.move(['a/hello'], 'a/b')
236
321
        r4 = 'test@rev-4'
237
322
        wt.commit('four', rev_id=r4, allow_pointless=False)
238
 
        self.check_inventory_shape(wt.read_working_inventory(),
239
 
                                   ['a', 'a/b/hello', 'a/b'])
 
323
        wt.lock_read()
 
324
        try:
 
325
            self.check_inventory_shape(wt.read_working_inventory(),
 
326
                                       ['a/', 'a/b/hello', 'a/b/'])
 
327
        finally:
 
328
            wt.unlock()
240
329
 
241
 
        inv = b.repository.get_revision_inventory(r4)
 
330
        inv = b.repository.get_inventory(r4)
242
331
        eq(inv['hello-id'].revision, r4)
243
332
        eq(inv['a-id'].revision, r1)
244
333
        eq(inv['b-id'].revision, r3)
245
 
        
 
334
 
246
335
    def test_removed_commit(self):
247
336
        """Commit with a removed file"""
248
337
        wt = self.make_branch_and_tree('.')
302
391
    def test_strict_commit_without_unknowns(self):
303
392
        """Try and commit with no unknown files and strict = True,
304
393
        should work."""
305
 
        from bzrlib.errors import StrictCommitFailed
306
394
        wt = self.make_branch_and_tree('.')
307
395
        b = wt.branch
308
396
        file('hello', 'w').write('hello world')
334
422
        wt = self.make_branch_and_tree('.')
335
423
        branch = wt.branch
336
424
        wt.commit("base", allow_pointless=True, rev_id='A')
337
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
425
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
338
426
        try:
339
427
            from bzrlib.testament import Testament
340
428
            # monkey patch gpg signing mechanism
343
431
                                                      allow_pointless=True,
344
432
                                                      rev_id='B',
345
433
                                                      working_tree=wt)
346
 
            self.assertEqual(Testament.from_revision(branch.repository,
347
 
                             'B').as_short_text(),
 
434
            def sign(text):
 
435
                return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
 
436
            self.assertEqual(sign(Testament.from_revision(branch.repository,
 
437
                             'B').as_short_text()),
348
438
                             branch.repository.get_signature_text('B'))
349
439
        finally:
350
440
            bzrlib.gpg.GPGStrategy = oldstrategy
356
446
        wt = self.make_branch_and_tree('.')
357
447
        branch = wt.branch
358
448
        wt.commit("base", allow_pointless=True, rev_id='A')
359
 
        self.failIf(branch.repository.has_signature_for_revision_id('A'))
 
449
        self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
360
450
        try:
361
 
            from bzrlib.testament import Testament
362
451
            # monkey patch gpg signing mechanism
363
452
            bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
364
453
            config = MustSignConfig(branch)
370
459
                              working_tree=wt)
371
460
            branch = Branch.open(self.get_url('.'))
372
461
            self.assertEqual(branch.revision_history(), ['A'])
373
 
            self.failIf(branch.repository.has_revision('B'))
 
462
            self.assertFalse(branch.repository.has_revision('B'))
374
463
        finally:
375
464
            bzrlib.gpg.GPGStrategy = oldstrategy
376
465
 
411
500
        bound = master.sprout('bound')
412
501
        wt = bound.open_workingtree()
413
502
        wt.branch.set_bound_location(os.path.realpath('master'))
414
 
 
415
 
        orig_default = lockdir._DEFAULT_TIMEOUT_SECONDS
416
503
        master_branch.lock_write()
417
504
        try:
418
 
            lockdir._DEFAULT_TIMEOUT_SECONDS = 1
419
505
            self.assertRaises(LockContention, wt.commit, 'silly')
420
506
        finally:
421
 
            lockdir._DEFAULT_TIMEOUT_SECONDS = orig_default
422
507
            master_branch.unlock()
423
508
 
424
509
    def test_commit_bound_merge(self):
435
520
        other_bzrdir = master_branch.bzrdir.sprout('other')
436
521
        other_tree = other_bzrdir.open_workingtree()
437
522
 
438
 
        # do a commit to the the other branch changing the content file so
 
523
        # do a commit to the other branch changing the content file so
439
524
        # that our commit after merging will have a merged revision in the
440
525
        # content file history.
441
526
        self.build_tree_contents([('other/content_file', 'change in other\n')])
452
537
        bound_tree.commit(message='commit of merge in bound tree')
453
538
 
454
539
    def test_commit_reporting_after_merge(self):
455
 
        # when doing a commit of a merge, the reporter needs to still 
 
540
        # when doing a commit of a merge, the reporter needs to still
456
541
        # be called for each item that is added/removed/deleted.
457
542
        this_tree = self.make_branch_and_tree('this')
458
543
        # we need a bunch of files and dirs, to perform one action on each.
501
586
        this_tree.merge_from_branch(other_tree.branch)
502
587
        reporter = CapturingReporter()
503
588
        this_tree.commit('do the commit', reporter=reporter)
504
 
        self.assertEqual([
505
 
            ('change', 'unchanged', ''),
506
 
            ('change', 'unchanged', 'dirtoleave'),
507
 
            ('change', 'unchanged', 'filetoleave'),
 
589
        expected = set([
508
590
            ('change', 'modified', 'filetomodify'),
509
591
            ('change', 'added', 'newdir'),
510
592
            ('change', 'added', 'newfile'),
511
593
            ('renamed', 'renamed', 'dirtorename', 'renameddir'),
 
594
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
512
595
            ('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
513
596
            ('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
514
 
            ('renamed', 'renamed', 'filetorename', 'renamedfile'),
515
597
            ('deleted', 'dirtoremove'),
516
598
            ('deleted', 'filetoremove'),
517
 
            ],
518
 
            reporter.calls)
 
599
            ])
 
600
        result = set(reporter.calls)
 
601
        missing = expected - result
 
602
        new = result - expected
 
603
        self.assertEqual((set(), set()), (missing, new))
519
604
 
520
605
    def test_commit_removals_respects_filespec(self):
521
606
        """Commit respects the specified_files for removals."""
525
610
        tree.commit('added a, b')
526
611
        tree.remove(['a', 'b'])
527
612
        tree.commit('removed a', specific_files='a')
528
 
        basis = tree.basis_tree().inventory
529
 
        self.assertIs(None, basis.path2id('a'))
530
 
        self.assertFalse(basis.path2id('b') is None)
 
613
        basis = tree.basis_tree()
 
614
        tree.lock_read()
 
615
        try:
 
616
            self.assertIs(None, basis.path2id('a'))
 
617
            self.assertFalse(basis.path2id('b') is None)
 
618
        finally:
 
619
            tree.unlock()
531
620
 
532
621
    def test_commit_saves_1ms_timestamp(self):
533
622
        """Passing in a timestamp is saved with 1ms resolution"""
552
641
        timestamp_1ms = round(timestamp, 3)
553
642
        self.assertEqual(timestamp_1ms, timestamp)
554
643
 
 
644
    def assertBasisTreeKind(self, kind, tree, file_id):
 
645
        basis = tree.basis_tree()
 
646
        basis.lock_read()
 
647
        try:
 
648
            self.assertEqual(kind, basis.kind(file_id))
 
649
        finally:
 
650
            basis.unlock()
 
651
 
 
652
    def test_commit_kind_changes(self):
 
653
        self.requireFeature(SymlinkFeature)
 
654
        tree = self.make_branch_and_tree('.')
 
655
        os.symlink('target', 'name')
 
656
        tree.add('name', 'a-file-id')
 
657
        tree.commit('Added a symlink')
 
658
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
659
 
 
660
        os.unlink('name')
 
661
        self.build_tree(['name'])
 
662
        tree.commit('Changed symlink to file')
 
663
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
664
 
 
665
        os.unlink('name')
 
666
        os.symlink('target', 'name')
 
667
        tree.commit('file to symlink')
 
668
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
669
 
 
670
        os.unlink('name')
 
671
        os.mkdir('name')
 
672
        tree.commit('symlink to directory')
 
673
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
674
 
 
675
        os.rmdir('name')
 
676
        os.symlink('target', 'name')
 
677
        tree.commit('directory to symlink')
 
678
        self.assertBasisTreeKind('symlink', tree, 'a-file-id')
 
679
 
 
680
        # prepare for directory <-> file tests
 
681
        os.unlink('name')
 
682
        os.mkdir('name')
 
683
        tree.commit('symlink to directory')
 
684
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
685
 
 
686
        os.rmdir('name')
 
687
        self.build_tree(['name'])
 
688
        tree.commit('Changed directory to file')
 
689
        self.assertBasisTreeKind('file', tree, 'a-file-id')
 
690
 
 
691
        os.unlink('name')
 
692
        os.mkdir('name')
 
693
        tree.commit('file to directory')
 
694
        self.assertBasisTreeKind('directory', tree, 'a-file-id')
 
695
 
555
696
    def test_commit_unversioned_specified(self):
556
697
        """Commit should raise if specified files isn't in basis or worktree"""
557
698
        tree = self.make_branch_and_tree('.')
558
 
        self.assertRaises(errors.PathsNotVersionedError, tree.commit, 
 
699
        self.assertRaises(errors.PathsNotVersionedError, tree.commit,
559
700
                          'message', specific_files=['bogus'])
560
701
 
561
702
    class Callback(object):
562
 
        
 
703
 
563
704
        def __init__(self, message, testcase):
564
705
            self.called = False
565
706
            self.message = message
593
734
        """Callback should not be invoked for pointless commit"""
594
735
        tree = self.make_branch_and_tree('.')
595
736
        cb = self.Callback(u'commit 2', self)
596
 
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb, 
 
737
        self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
597
738
                          allow_pointless=False)
598
739
        self.assertFalse(cb.called)
599
740
 
603
744
        cb = self.Callback(u'commit 2', self)
604
745
        repository = tree.branch.repository
605
746
        # simulate network failure
606
 
        def raise_(self, arg, arg2):
 
747
        def raise_(self, arg, arg2, arg3=None, arg4=None):
607
748
            raise errors.NoSuchFile('foo')
608
749
        repository.add_inventory = raise_
 
750
        repository.add_inventory_by_delta = raise_
609
751
        self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
610
752
        self.assertFalse(cb.called)
 
753
 
 
754
    def test_selected_file_merge_commit(self):
 
755
        """Ensure the correct error is raised"""
 
756
        tree = self.make_branch_and_tree('foo')
 
757
        # pending merge would turn into a left parent
 
758
        tree.commit('commit 1')
 
759
        tree.add_parent_tree_id('example')
 
760
        self.build_tree(['foo/bar', 'foo/baz'])
 
761
        tree.add(['bar', 'baz'])
 
762
        err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
763
            tree.commit, 'commit 2', specific_files=['bar', 'baz'])
 
764
        self.assertEqual(['bar', 'baz'], err.files)
 
765
        self.assertEqual('Selected-file commit of merges is not supported'
 
766
                         ' yet: files bar, baz', str(err))
 
767
 
 
768
    def test_commit_ordering(self):
 
769
        """Test of corner-case commit ordering error"""
 
770
        tree = self.make_branch_and_tree('.')
 
771
        self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
772
        tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
 
773
        tree.commit('setup')
 
774
        self.build_tree(['a/c/d/'])
 
775
        tree.add('a/c/d')
 
776
        tree.rename_one('a/z/x', 'a/c/d/x')
 
777
        tree.commit('test', specific_files=['a/z/y'])
 
778
 
 
779
    def test_commit_no_author(self):
 
780
        """The default kwarg author in MutableTree.commit should not add
 
781
        the 'author' revision property.
 
782
        """
 
783
        tree = self.make_branch_and_tree('foo')
 
784
        rev_id = tree.commit('commit 1')
 
785
        rev = tree.branch.repository.get_revision(rev_id)
 
786
        self.assertFalse('author' in rev.properties)
 
787
        self.assertFalse('authors' in rev.properties)
 
788
 
 
789
    def test_commit_author(self):
 
790
        """Passing a non-empty author kwarg to MutableTree.commit should add
 
791
        the 'author' revision property.
 
792
        """
 
793
        tree = self.make_branch_and_tree('foo')
 
794
        rev_id = self.callDeprecated(['The parameter author was '
 
795
                'deprecated in version 1.13. Use authors instead'],
 
796
                tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
 
797
        rev = tree.branch.repository.get_revision(rev_id)
 
798
        self.assertEqual('John Doe <jdoe@example.com>',
 
799
                         rev.properties['authors'])
 
800
        self.assertFalse('author' in rev.properties)
 
801
 
 
802
    def test_commit_empty_authors_list(self):
 
803
        """Passing an empty list to authors shouldn't add the property."""
 
804
        tree = self.make_branch_and_tree('foo')
 
805
        rev_id = tree.commit('commit 1', authors=[])
 
806
        rev = tree.branch.repository.get_revision(rev_id)
 
807
        self.assertFalse('author' in rev.properties)
 
808
        self.assertFalse('authors' in rev.properties)
 
809
 
 
810
    def test_multiple_authors(self):
 
811
        tree = self.make_branch_and_tree('foo')
 
812
        rev_id = tree.commit('commit 1',
 
813
                authors=['John Doe <jdoe@example.com>',
 
814
                         'Jane Rey <jrey@example.com>'])
 
815
        rev = tree.branch.repository.get_revision(rev_id)
 
816
        self.assertEqual('John Doe <jdoe@example.com>\n'
 
817
                'Jane Rey <jrey@example.com>', rev.properties['authors'])
 
818
        self.assertFalse('author' in rev.properties)
 
819
 
 
820
    def test_author_and_authors_incompatible(self):
 
821
        tree = self.make_branch_and_tree('foo')
 
822
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
823
                authors=['John Doe <jdoe@example.com>',
 
824
                         'Jane Rey <jrey@example.com>'],
 
825
                author="Jack Me <jme@example.com>")
 
826
 
 
827
    def test_author_with_newline_rejected(self):
 
828
        tree = self.make_branch_and_tree('foo')
 
829
        self.assertRaises(AssertionError, tree.commit, 'commit 1',
 
830
                authors=['John\nDoe <jdoe@example.com>'])
 
831
 
 
832
    def test_commit_with_checkout_and_branch_sharing_repo(self):
 
833
        repo = self.make_repository('repo', shared=True)
 
834
        # make_branch_and_tree ignores shared repos
 
835
        branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
 
836
        tree2 = branch.create_checkout('repo/tree2')
 
837
        tree2.commit('message', rev_id='rev1')
 
838
        self.assertTrue(tree2.branch.repository.has_revision('rev1'))