1
# Copyright (C) 2005-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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
25
from bzrlib.branch import Branch
26
from bzrlib.bzrdir import BzrDirMetaFormat1
27
from bzrlib.commit import Commit, NullCommitReporter
28
from bzrlib.config import BranchConfig
29
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
31
from bzrlib.tests import SymlinkFeature, TestCaseWithTransport
34
# TODO: Test commit with some added, and added-but-missing files
36
class MustSignConfig(BranchConfig):
38
def signature_needed(self):
41
def gpg_signing_command(self):
45
class BranchWithHooks(BranchConfig):
47
def post_commit(self):
48
return "bzrlib.ahook bzrlib.ahook"
51
class CapturingReporter(NullCommitReporter):
52
"""This reporter captures the calls made to it for evaluation later."""
55
# a list of the calls this received
58
def snapshot_change(self, change, path):
59
self.calls.append(('change', change, path))
61
def deleted(self, file_id):
62
self.calls.append(('deleted', file_id))
64
def missing(self, path):
65
self.calls.append(('missing', path))
67
def renamed(self, change, old_path, new_path):
68
self.calls.append(('renamed', change, old_path, new_path))
74
class TestCommit(TestCaseWithTransport):
76
def test_simple_commit(self):
77
"""Commit and check two versions of a single file."""
78
wt = self.make_branch_and_tree('.')
80
file('hello', 'w').write('hello world')
82
wt.commit(message='add hello')
83
file_id = wt.path2id('hello')
85
file('hello', 'w').write('version 2')
86
wt.commit(message='commit 2')
88
eq = self.assertEquals
90
rh = b.revision_history()
91
rev = b.repository.get_revision(rh[0])
92
eq(rev.message, 'add hello')
94
tree1 = b.repository.revision_tree(rh[0])
96
text = tree1.get_file_text(file_id)
98
self.assertEqual('hello world', text)
100
tree2 = b.repository.revision_tree(rh[1])
102
text = tree2.get_file_text(file_id)
104
self.assertEqual('version 2', text)
106
def test_missing_commit(self):
107
"""Test a commit with a missing file"""
108
wt = self.make_branch_and_tree('.')
110
file('hello', 'w').write('hello world')
111
wt.add(['hello'], ['hello-id'])
112
wt.commit(message='add hello')
115
wt.commit('removed hello', rev_id='rev2')
117
tree = b.repository.revision_tree('rev2')
118
self.assertFalse(tree.has_id('hello-id'))
120
def test_partial_commit_move(self):
121
"""Test a partial commit where a file was renamed but not committed.
123
https://bugs.launchpad.net/bzr/+bug/83039
125
If not handled properly, commit will try to snapshot
126
dialog.py with olive/ as a parent, while
127
olive/ has not been snapshotted yet.
129
wt = self.make_branch_and_tree('.')
131
self.build_tree(['annotate/', 'annotate/foo.py',
132
'olive/', 'olive/dialog.py'
134
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
135
wt.commit(message='add files')
136
wt.rename_one("olive/dialog.py", "aaa")
137
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
138
wt.commit('renamed hello', specific_files=["annotate"])
140
def test_pointless_commit(self):
141
"""Commit refuses unless there are changes or it's forced."""
142
wt = self.make_branch_and_tree('.')
144
file('hello', 'w').write('hello')
146
wt.commit(message='add hello')
147
self.assertEquals(b.revno(), 1)
148
self.assertRaises(PointlessCommit,
151
allow_pointless=False)
152
self.assertEquals(b.revno(), 1)
154
def test_commit_empty(self):
155
"""Commiting an empty tree works."""
156
wt = self.make_branch_and_tree('.')
158
wt.commit(message='empty tree', allow_pointless=True)
159
self.assertRaises(PointlessCommit,
161
message='empty tree',
162
allow_pointless=False)
163
wt.commit(message='empty tree', allow_pointless=True)
164
self.assertEquals(b.revno(), 2)
166
def test_selective_delete(self):
167
"""Selective commit in tree with deletions"""
168
wt = self.make_branch_and_tree('.')
170
file('hello', 'w').write('hello')
171
file('buongia', 'w').write('buongia')
172
wt.add(['hello', 'buongia'],
173
['hello-id', 'buongia-id'])
174
wt.commit(message='add files',
178
file('buongia', 'w').write('new text')
179
wt.commit(message='update text',
180
specific_files=['buongia'],
181
allow_pointless=False,
184
wt.commit(message='remove hello',
185
specific_files=['hello'],
186
allow_pointless=False,
189
eq = self.assertEquals
192
tree2 = b.repository.revision_tree('test@rev-2')
194
self.addCleanup(tree2.unlock)
195
self.assertTrue(tree2.has_filename('hello'))
196
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
197
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
199
tree3 = b.repository.revision_tree('test@rev-3')
201
self.addCleanup(tree3.unlock)
202
self.assertFalse(tree3.has_filename('hello'))
203
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
205
def test_commit_rename(self):
206
"""Test commit of a revision where a file is renamed."""
207
tree = self.make_branch_and_tree('.')
209
self.build_tree(['hello'], line_endings='binary')
210
tree.add(['hello'], ['hello-id'])
211
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
213
tree.rename_one('hello', 'fruity')
214
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
216
eq = self.assertEquals
217
tree1 = b.repository.revision_tree('test@rev-1')
219
self.addCleanup(tree1.unlock)
220
eq(tree1.id2path('hello-id'), 'hello')
221
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
222
self.assertFalse(tree1.has_filename('fruity'))
223
self.check_inventory_shape(tree1.inventory, ['hello'])
224
ie = tree1.inventory['hello-id']
225
eq(ie.revision, 'test@rev-1')
227
tree2 = b.repository.revision_tree('test@rev-2')
229
self.addCleanup(tree2.unlock)
230
eq(tree2.id2path('hello-id'), 'fruity')
231
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
232
self.check_inventory_shape(tree2.inventory, ['fruity'])
233
ie = tree2.inventory['hello-id']
234
eq(ie.revision, 'test@rev-2')
236
def test_reused_rev_id(self):
237
"""Test that a revision id cannot be reused in a branch"""
238
wt = self.make_branch_and_tree('.')
240
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
241
self.assertRaises(Exception,
245
allow_pointless=True)
247
def test_commit_move(self):
248
"""Test commit of revisions with moved files and directories"""
249
eq = self.assertEquals
250
wt = self.make_branch_and_tree('.')
253
self.build_tree(['hello', 'a/', 'b/'])
254
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
255
wt.commit('initial', rev_id=r1, allow_pointless=False)
256
wt.move(['hello'], 'a')
258
wt.commit('two', rev_id=r2, allow_pointless=False)
261
self.check_inventory_shape(wt.read_working_inventory(),
262
['a/', 'a/hello', 'b/'])
268
wt.commit('three', rev_id=r3, allow_pointless=False)
271
self.check_inventory_shape(wt.read_working_inventory(),
272
['a/', 'a/hello', 'a/b/'])
273
self.check_inventory_shape(b.repository.get_inventory(r3),
274
['a/', 'a/hello', 'a/b/'])
278
wt.move(['a/hello'], 'a/b')
280
wt.commit('four', rev_id=r4, allow_pointless=False)
283
self.check_inventory_shape(wt.read_working_inventory(),
284
['a/', 'a/b/hello', 'a/b/'])
288
inv = b.repository.get_inventory(r4)
289
eq(inv['hello-id'].revision, r4)
290
eq(inv['a-id'].revision, r1)
291
eq(inv['b-id'].revision, r3)
293
def test_removed_commit(self):
294
"""Commit with a removed file"""
295
wt = self.make_branch_and_tree('.')
297
file('hello', 'w').write('hello world')
298
wt.add(['hello'], ['hello-id'])
299
wt.commit(message='add hello')
301
wt.commit('removed hello', rev_id='rev2')
303
tree = b.repository.revision_tree('rev2')
304
self.assertFalse(tree.has_id('hello-id'))
306
def test_committed_ancestry(self):
307
"""Test commit appends revisions to ancestry."""
308
wt = self.make_branch_and_tree('.')
312
file('hello', 'w').write((str(i) * 4) + '\n')
314
wt.add(['hello'], ['hello-id'])
315
rev_id = 'test@rev-%d' % (i+1)
316
rev_ids.append(rev_id)
317
wt.commit(message='rev %d' % (i+1),
319
eq = self.assertEquals
320
eq(b.revision_history(), rev_ids)
322
anc = b.repository.get_ancestry(rev_ids[i])
323
eq(anc, [None] + rev_ids[:i+1])
325
def test_commit_new_subdir_child_selective(self):
326
wt = self.make_branch_and_tree('.')
328
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
329
wt.add(['dir', 'dir/file1', 'dir/file2'],
330
['dirid', 'file1id', 'file2id'])
331
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
332
inv = b.repository.get_inventory('1')
333
self.assertEqual('1', inv['dirid'].revision)
334
self.assertEqual('1', inv['file1id'].revision)
335
# FIXME: This should raise a KeyError I think, rbc20051006
336
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
338
def test_strict_commit(self):
339
"""Try and commit with unknown files and strict = True, should fail."""
340
from bzrlib.errors import StrictCommitFailed
341
wt = self.make_branch_and_tree('.')
343
file('hello', 'w').write('hello world')
345
file('goodbye', 'w').write('goodbye cruel world!')
346
self.assertRaises(StrictCommitFailed, wt.commit,
347
message='add hello but not goodbye', strict=True)
349
def test_strict_commit_without_unknowns(self):
350
"""Try and commit with no unknown files and strict = True,
352
from bzrlib.errors import StrictCommitFailed
353
wt = self.make_branch_and_tree('.')
355
file('hello', 'w').write('hello world')
357
wt.commit(message='add hello', strict=True)
359
def test_nonstrict_commit(self):
360
"""Try and commit with unknown files and strict = False, should work."""
361
wt = self.make_branch_and_tree('.')
363
file('hello', 'w').write('hello world')
365
file('goodbye', 'w').write('goodbye cruel world!')
366
wt.commit(message='add hello but not goodbye', strict=False)
368
def test_nonstrict_commit_without_unknowns(self):
369
"""Try and commit with no unknown files and strict = False,
371
wt = self.make_branch_and_tree('.')
373
file('hello', 'w').write('hello world')
375
wt.commit(message='add hello', strict=False)
377
def test_signed_commit(self):
379
import bzrlib.commit as commit
380
oldstrategy = bzrlib.gpg.GPGStrategy
381
wt = self.make_branch_and_tree('.')
383
wt.commit("base", allow_pointless=True, rev_id='A')
384
self.failIf(branch.repository.has_signature_for_revision_id('A'))
386
from bzrlib.testament import Testament
387
# monkey patch gpg signing mechanism
388
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
389
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
390
allow_pointless=True,
394
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
395
self.assertEqual(sign(Testament.from_revision(branch.repository,
396
'B').as_short_text()),
397
branch.repository.get_signature_text('B'))
399
bzrlib.gpg.GPGStrategy = oldstrategy
401
def test_commit_failed_signature(self):
403
import bzrlib.commit as commit
404
oldstrategy = bzrlib.gpg.GPGStrategy
405
wt = self.make_branch_and_tree('.')
407
wt.commit("base", allow_pointless=True, rev_id='A')
408
self.failIf(branch.repository.has_signature_for_revision_id('A'))
410
from bzrlib.testament import Testament
411
# monkey patch gpg signing mechanism
412
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
413
config = MustSignConfig(branch)
414
self.assertRaises(SigningFailed,
415
commit.Commit(config=config).commit,
417
allow_pointless=True,
420
branch = Branch.open(self.get_url('.'))
421
self.assertEqual(branch.revision_history(), ['A'])
422
self.failIf(branch.repository.has_revision('B'))
424
bzrlib.gpg.GPGStrategy = oldstrategy
426
def test_commit_invokes_hooks(self):
427
import bzrlib.commit as commit
428
wt = self.make_branch_and_tree('.')
431
def called(branch, rev_id):
432
calls.append('called')
433
bzrlib.ahook = called
435
config = BranchWithHooks(branch)
436
commit.Commit(config=config).commit(
438
allow_pointless=True,
439
rev_id='A', working_tree = wt)
440
self.assertEqual(['called', 'called'], calls)
444
def test_commit_object_doesnt_set_nick(self):
445
# using the Commit object directly does not set the branch nick.
446
wt = self.make_branch_and_tree('.')
448
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
449
self.assertEquals(wt.branch.revno(), 1)
451
wt.branch.repository.get_revision(
452
wt.branch.last_revision()).properties)
454
def test_safe_master_lock(self):
456
master = BzrDirMetaFormat1().initialize('master')
457
master.create_repository()
458
master_branch = master.create_branch()
459
master.create_workingtree()
460
bound = master.sprout('bound')
461
wt = bound.open_workingtree()
462
wt.branch.set_bound_location(os.path.realpath('master'))
463
master_branch.lock_write()
465
self.assertRaises(LockContention, wt.commit, 'silly')
467
master_branch.unlock()
469
def test_commit_bound_merge(self):
470
# see bug #43959; commit of a merge in a bound branch fails to push
471
# the new commit into the master
472
master_branch = self.make_branch('master')
473
bound_tree = self.make_branch_and_tree('bound')
474
bound_tree.branch.bind(master_branch)
476
self.build_tree_contents([('bound/content_file', 'initial contents\n')])
477
bound_tree.add(['content_file'])
478
bound_tree.commit(message='woo!')
480
other_bzrdir = master_branch.bzrdir.sprout('other')
481
other_tree = other_bzrdir.open_workingtree()
483
# do a commit to the other branch changing the content file so
484
# that our commit after merging will have a merged revision in the
485
# content file history.
486
self.build_tree_contents([('other/content_file', 'change in other\n')])
487
other_tree.commit('change in other')
489
# do a merge into the bound branch from other, and then change the
490
# content file locally to force a new revision (rather than using the
491
# revision from other). This forces extra processing in commit.
492
bound_tree.merge_from_branch(other_tree.branch)
493
self.build_tree_contents([('bound/content_file', 'change in bound\n')])
495
# before #34959 was fixed, this failed with 'revision not present in
496
# weave' when trying to implicitly push from the bound branch to the master
497
bound_tree.commit(message='commit of merge in bound tree')
499
def test_commit_reporting_after_merge(self):
500
# when doing a commit of a merge, the reporter needs to still
501
# be called for each item that is added/removed/deleted.
502
this_tree = self.make_branch_and_tree('this')
503
# we need a bunch of files and dirs, to perform one action on each.
506
'this/dirtoreparent/',
509
'this/filetoreparent',
526
this_tree.commit('create_files')
527
other_dir = this_tree.bzrdir.sprout('other')
528
other_tree = other_dir.open_workingtree()
529
other_tree.lock_write()
530
# perform the needed actions on the files and dirs.
532
other_tree.rename_one('dirtorename', 'renameddir')
533
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
534
other_tree.rename_one('filetorename', 'renamedfile')
535
other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
536
other_tree.remove(['dirtoremove', 'filetoremove'])
537
self.build_tree_contents([
539
('other/filetomodify', 'new content'),
540
('other/newfile', 'new file content')])
541
other_tree.add('newfile')
542
other_tree.add('newdir/')
543
other_tree.commit('modify all sample files and dirs.')
546
this_tree.merge_from_branch(other_tree.branch)
547
reporter = CapturingReporter()
548
this_tree.commit('do the commit', reporter=reporter)
550
('change', 'modified', 'filetomodify'),
551
('change', 'added', 'newdir'),
552
('change', 'added', 'newfile'),
553
('renamed', 'renamed', 'dirtorename', 'renameddir'),
554
('renamed', 'renamed', 'filetorename', 'renamedfile'),
555
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
556
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
557
('deleted', 'dirtoremove'),
558
('deleted', 'filetoremove'),
560
result = set(reporter.calls)
561
missing = expected - result
562
new = result - expected
563
self.assertEqual((set(), set()), (missing, new))
565
def test_commit_removals_respects_filespec(self):
566
"""Commit respects the specified_files for removals."""
567
tree = self.make_branch_and_tree('.')
568
self.build_tree(['a', 'b'])
570
tree.commit('added a, b')
571
tree.remove(['a', 'b'])
572
tree.commit('removed a', specific_files='a')
573
basis = tree.basis_tree()
576
self.assertIs(None, basis.path2id('a'))
577
self.assertFalse(basis.path2id('b') is None)
581
def test_commit_saves_1ms_timestamp(self):
582
"""Passing in a timestamp is saved with 1ms resolution"""
583
tree = self.make_branch_and_tree('.')
584
self.build_tree(['a'])
586
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
589
rev = tree.branch.repository.get_revision('a1')
590
self.assertEqual(1153248633.419, rev.timestamp)
592
def test_commit_has_1ms_resolution(self):
593
"""Allowing commit to generate the timestamp also has 1ms resolution"""
594
tree = self.make_branch_and_tree('.')
595
self.build_tree(['a'])
597
tree.commit('added a', rev_id='a1')
599
rev = tree.branch.repository.get_revision('a1')
600
timestamp = rev.timestamp
601
timestamp_1ms = round(timestamp, 3)
602
self.assertEqual(timestamp_1ms, timestamp)
604
def assertBasisTreeKind(self, kind, tree, file_id):
605
basis = tree.basis_tree()
608
self.assertEqual(kind, basis.kind(file_id))
612
def test_commit_kind_changes(self):
613
self.requireFeature(SymlinkFeature)
614
tree = self.make_branch_and_tree('.')
615
os.symlink('target', 'name')
616
tree.add('name', 'a-file-id')
617
tree.commit('Added a symlink')
618
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
621
self.build_tree(['name'])
622
tree.commit('Changed symlink to file')
623
self.assertBasisTreeKind('file', tree, 'a-file-id')
626
os.symlink('target', 'name')
627
tree.commit('file to symlink')
628
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
632
tree.commit('symlink to directory')
633
self.assertBasisTreeKind('directory', tree, 'a-file-id')
636
os.symlink('target', 'name')
637
tree.commit('directory to symlink')
638
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
640
# prepare for directory <-> file tests
643
tree.commit('symlink to directory')
644
self.assertBasisTreeKind('directory', tree, 'a-file-id')
647
self.build_tree(['name'])
648
tree.commit('Changed directory to file')
649
self.assertBasisTreeKind('file', tree, 'a-file-id')
653
tree.commit('file to directory')
654
self.assertBasisTreeKind('directory', tree, 'a-file-id')
656
def test_commit_unversioned_specified(self):
657
"""Commit should raise if specified files isn't in basis or worktree"""
658
tree = self.make_branch_and_tree('.')
659
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
660
'message', specific_files=['bogus'])
662
class Callback(object):
664
def __init__(self, message, testcase):
666
self.message = message
667
self.testcase = testcase
669
def __call__(self, commit_obj):
671
self.testcase.assertTrue(isinstance(commit_obj, Commit))
674
def test_commit_callback(self):
675
"""Commit should invoke a callback to get the message"""
677
tree = self.make_branch_and_tree('.')
681
self.assertTrue(isinstance(e, BzrError))
682
self.assertEqual('The message or message_callback keyword'
683
' parameter is required for commit().', str(e))
685
self.fail('exception not raised')
686
cb = self.Callback(u'commit 1', self)
687
tree.commit(message_callback=cb)
688
self.assertTrue(cb.called)
689
repository = tree.branch.repository
690
message = repository.get_revision(tree.last_revision()).message
691
self.assertEqual('commit 1', message)
693
def test_no_callback_pointless(self):
694
"""Callback should not be invoked for pointless commit"""
695
tree = self.make_branch_and_tree('.')
696
cb = self.Callback(u'commit 2', self)
697
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
698
allow_pointless=False)
699
self.assertFalse(cb.called)
701
def test_no_callback_netfailure(self):
702
"""Callback should not be invoked if connectivity fails"""
703
tree = self.make_branch_and_tree('.')
704
cb = self.Callback(u'commit 2', self)
705
repository = tree.branch.repository
706
# simulate network failure
707
def raise_(self, arg, arg2, arg3=None, arg4=None):
708
raise errors.NoSuchFile('foo')
709
repository.add_inventory = raise_
710
repository.add_inventory_by_delta = raise_
711
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
712
self.assertFalse(cb.called)
714
def test_selected_file_merge_commit(self):
715
"""Ensure the correct error is raised"""
716
tree = self.make_branch_and_tree('foo')
717
# pending merge would turn into a left parent
718
tree.commit('commit 1')
719
tree.add_parent_tree_id('example')
720
self.build_tree(['foo/bar', 'foo/baz'])
721
tree.add(['bar', 'baz'])
722
err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
723
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
724
self.assertEqual(['bar', 'baz'], err.files)
725
self.assertEqual('Selected-file commit of merges is not supported'
726
' yet: files bar, baz', str(err))
728
def test_commit_ordering(self):
729
"""Test of corner-case commit ordering error"""
730
tree = self.make_branch_and_tree('.')
731
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
732
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
734
self.build_tree(['a/c/d/'])
736
tree.rename_one('a/z/x', 'a/c/d/x')
737
tree.commit('test', specific_files=['a/z/y'])
739
def test_commit_no_author(self):
740
"""The default kwarg author in MutableTree.commit should not add
741
the 'author' revision property.
743
tree = self.make_branch_and_tree('foo')
744
rev_id = tree.commit('commit 1')
745
rev = tree.branch.repository.get_revision(rev_id)
746
self.assertFalse('author' in rev.properties)
747
self.assertFalse('authors' in rev.properties)
749
def test_commit_author(self):
750
"""Passing a non-empty author kwarg to MutableTree.commit should add
751
the 'author' revision property.
753
tree = self.make_branch_and_tree('foo')
754
rev_id = self.callDeprecated(['The parameter author was '
755
'deprecated in version 1.13. Use authors instead'],
756
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
757
rev = tree.branch.repository.get_revision(rev_id)
758
self.assertEqual('John Doe <jdoe@example.com>',
759
rev.properties['authors'])
760
self.assertFalse('author' in rev.properties)
762
def test_commit_empty_authors_list(self):
763
"""Passing an empty list to authors shouldn't add the property."""
764
tree = self.make_branch_and_tree('foo')
765
rev_id = tree.commit('commit 1', authors=[])
766
rev = tree.branch.repository.get_revision(rev_id)
767
self.assertFalse('author' in rev.properties)
768
self.assertFalse('authors' in rev.properties)
770
def test_multiple_authors(self):
771
tree = self.make_branch_and_tree('foo')
772
rev_id = tree.commit('commit 1',
773
authors=['John Doe <jdoe@example.com>',
774
'Jane Rey <jrey@example.com>'])
775
rev = tree.branch.repository.get_revision(rev_id)
776
self.assertEqual('John Doe <jdoe@example.com>\n'
777
'Jane Rey <jrey@example.com>', rev.properties['authors'])
778
self.assertFalse('author' in rev.properties)
780
def test_author_and_authors_incompatible(self):
781
tree = self.make_branch_and_tree('foo')
782
self.assertRaises(AssertionError, tree.commit, 'commit 1',
783
authors=['John Doe <jdoe@example.com>',
784
'Jane Rey <jrey@example.com>'],
785
author="Jack Me <jme@example.com>")
787
def test_author_with_newline_rejected(self):
788
tree = self.make_branch_and_tree('foo')
789
self.assertRaises(AssertionError, tree.commit, 'commit 1',
790
authors=['John\nDoe <jdoe@example.com>'])
792
def test_commit_with_checkout_and_branch_sharing_repo(self):
793
repo = self.make_repository('repo', shared=True)
794
# make_branch_and_tree ignores shared repos
795
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
796
tree2 = branch.create_checkout('repo/tree2')
797
tree2.commit('message', rev_id='rev1')
798
self.assertTrue(tree2.branch.repository.has_revision('rev1'))