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 (
33
TestCaseWithTransport,
38
# TODO: Test commit with some added, and added-but-missing files
40
class MustSignConfig(BranchConfig):
42
def signature_needed(self):
45
def gpg_signing_command(self):
49
class BranchWithHooks(BranchConfig):
51
def post_commit(self):
52
return "bzrlib.ahook bzrlib.ahook"
55
class CapturingReporter(NullCommitReporter):
56
"""This reporter captures the calls made to it for evaluation later."""
59
# a list of the calls this received
62
def snapshot_change(self, change, path):
63
self.calls.append(('change', change, path))
65
def deleted(self, file_id):
66
self.calls.append(('deleted', file_id))
68
def missing(self, path):
69
self.calls.append(('missing', path))
71
def renamed(self, change, old_path, new_path):
72
self.calls.append(('renamed', change, old_path, new_path))
78
class TestCommit(TestCaseWithTransport):
80
def test_simple_commit(self):
81
"""Commit and check two versions of a single file."""
82
wt = self.make_branch_and_tree('.')
84
file('hello', 'w').write('hello world')
86
wt.commit(message='add hello')
87
file_id = wt.path2id('hello')
89
file('hello', 'w').write('version 2')
90
wt.commit(message='commit 2')
92
eq = self.assertEquals
94
rh = b.revision_history()
95
rev = b.repository.get_revision(rh[0])
96
eq(rev.message, 'add hello')
98
tree1 = b.repository.revision_tree(rh[0])
100
text = tree1.get_file_text(file_id)
102
self.assertEqual('hello world', text)
104
tree2 = b.repository.revision_tree(rh[1])
106
text = tree2.get_file_text(file_id)
108
self.assertEqual('version 2', text)
110
def test_commit_lossy_native(self):
111
"""Attempt a lossy commit to a native branch."""
112
wt = self.make_branch_and_tree('.')
114
file('hello', 'w').write('hello world')
116
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
117
self.assertEquals('revid', revid)
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())
125
file('hello', 'w').write('hello world')
127
revid = wt.commit(message='add hello', lossy=True,
128
timestamp=1302659388, timezone=0)
129
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
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")
138
file('local/hello', 'w').write('hello world')
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())
148
def test_missing_commit(self):
149
"""Test a commit with a missing file"""
150
wt = self.make_branch_and_tree('.')
152
file('hello', 'w').write('hello world')
153
wt.add(['hello'], ['hello-id'])
154
wt.commit(message='add hello')
157
wt.commit('removed hello', rev_id='rev2')
159
tree = b.repository.revision_tree('rev2')
160
self.assertFalse(tree.has_id('hello-id'))
162
def test_partial_commit_move(self):
163
"""Test a partial commit where a file was renamed but not committed.
165
https://bugs.launchpad.net/bzr/+bug/83039
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.
171
wt = self.make_branch_and_tree('.')
173
self.build_tree(['annotate/', 'annotate/foo.py',
174
'olive/', 'olive/dialog.py'
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"])
182
def test_pointless_commit(self):
183
"""Commit refuses unless there are changes or it's forced."""
184
wt = self.make_branch_and_tree('.')
186
file('hello', 'w').write('hello')
188
wt.commit(message='add hello')
189
self.assertEquals(b.revno(), 1)
190
self.assertRaises(PointlessCommit,
193
allow_pointless=False)
194
self.assertEquals(b.revno(), 1)
196
def test_commit_empty(self):
197
"""Commiting an empty tree works."""
198
wt = self.make_branch_and_tree('.')
200
wt.commit(message='empty tree', allow_pointless=True)
201
self.assertRaises(PointlessCommit,
203
message='empty tree',
204
allow_pointless=False)
205
wt.commit(message='empty tree', allow_pointless=True)
206
self.assertEquals(b.revno(), 2)
208
def test_selective_delete(self):
209
"""Selective commit in tree with deletions"""
210
wt = self.make_branch_and_tree('.')
212
file('hello', 'w').write('hello')
213
file('buongia', 'w').write('buongia')
214
wt.add(['hello', 'buongia'],
215
['hello-id', 'buongia-id'])
216
wt.commit(message='add files',
220
file('buongia', 'w').write('new text')
221
wt.commit(message='update text',
222
specific_files=['buongia'],
223
allow_pointless=False,
226
wt.commit(message='remove hello',
227
specific_files=['hello'],
228
allow_pointless=False,
231
eq = self.assertEquals
234
tree2 = b.repository.revision_tree('test@rev-2')
236
self.addCleanup(tree2.unlock)
237
self.assertTrue(tree2.has_filename('hello'))
238
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
239
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
241
tree3 = b.repository.revision_tree('test@rev-3')
243
self.addCleanup(tree3.unlock)
244
self.assertFalse(tree3.has_filename('hello'))
245
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
247
def test_commit_rename(self):
248
"""Test commit of a revision where a file is renamed."""
249
tree = self.make_branch_and_tree('.')
251
self.build_tree(['hello'], line_endings='binary')
252
tree.add(['hello'], ['hello-id'])
253
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
255
tree.rename_one('hello', 'fruity')
256
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
258
eq = self.assertEquals
259
tree1 = b.repository.revision_tree('test@rev-1')
261
self.addCleanup(tree1.unlock)
262
eq(tree1.id2path('hello-id'), 'hello')
263
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
264
self.assertFalse(tree1.has_filename('fruity'))
265
self.check_inventory_shape(tree1.inventory, ['hello'])
266
ie = tree1.inventory['hello-id']
267
eq(ie.revision, 'test@rev-1')
269
tree2 = b.repository.revision_tree('test@rev-2')
271
self.addCleanup(tree2.unlock)
272
eq(tree2.id2path('hello-id'), 'fruity')
273
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
274
self.check_inventory_shape(tree2.inventory, ['fruity'])
275
ie = tree2.inventory['hello-id']
276
eq(ie.revision, 'test@rev-2')
278
def test_reused_rev_id(self):
279
"""Test that a revision id cannot be reused in a branch"""
280
wt = self.make_branch_and_tree('.')
282
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
283
self.assertRaises(Exception,
287
allow_pointless=True)
289
def test_commit_move(self):
290
"""Test commit of revisions with moved files and directories"""
291
eq = self.assertEquals
292
wt = self.make_branch_and_tree('.')
295
self.build_tree(['hello', 'a/', 'b/'])
296
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
297
wt.commit('initial', rev_id=r1, allow_pointless=False)
298
wt.move(['hello'], 'a')
300
wt.commit('two', rev_id=r2, allow_pointless=False)
303
self.check_inventory_shape(wt.read_working_inventory(),
304
['a/', 'a/hello', 'b/'])
310
wt.commit('three', rev_id=r3, allow_pointless=False)
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/'])
320
wt.move(['a/hello'], 'a/b')
322
wt.commit('four', rev_id=r4, allow_pointless=False)
325
self.check_inventory_shape(wt.read_working_inventory(),
326
['a/', 'a/b/hello', 'a/b/'])
330
inv = b.repository.get_inventory(r4)
331
eq(inv['hello-id'].revision, r4)
332
eq(inv['a-id'].revision, r1)
333
eq(inv['b-id'].revision, r3)
335
def test_removed_commit(self):
336
"""Commit with a removed file"""
337
wt = self.make_branch_and_tree('.')
339
file('hello', 'w').write('hello world')
340
wt.add(['hello'], ['hello-id'])
341
wt.commit(message='add hello')
343
wt.commit('removed hello', rev_id='rev2')
345
tree = b.repository.revision_tree('rev2')
346
self.assertFalse(tree.has_id('hello-id'))
348
def test_committed_ancestry(self):
349
"""Test commit appends revisions to ancestry."""
350
wt = self.make_branch_and_tree('.')
354
file('hello', 'w').write((str(i) * 4) + '\n')
356
wt.add(['hello'], ['hello-id'])
357
rev_id = 'test@rev-%d' % (i+1)
358
rev_ids.append(rev_id)
359
wt.commit(message='rev %d' % (i+1),
361
eq = self.assertEquals
362
eq(b.revision_history(), rev_ids)
364
anc = b.repository.get_ancestry(rev_ids[i])
365
eq(anc, [None] + rev_ids[:i+1])
367
def test_commit_new_subdir_child_selective(self):
368
wt = self.make_branch_and_tree('.')
370
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
371
wt.add(['dir', 'dir/file1', 'dir/file2'],
372
['dirid', 'file1id', 'file2id'])
373
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
374
inv = b.repository.get_inventory('1')
375
self.assertEqual('1', inv['dirid'].revision)
376
self.assertEqual('1', inv['file1id'].revision)
377
# FIXME: This should raise a KeyError I think, rbc20051006
378
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
380
def test_strict_commit(self):
381
"""Try and commit with unknown files and strict = True, should fail."""
382
from bzrlib.errors import StrictCommitFailed
383
wt = self.make_branch_and_tree('.')
385
file('hello', 'w').write('hello world')
387
file('goodbye', 'w').write('goodbye cruel world!')
388
self.assertRaises(StrictCommitFailed, wt.commit,
389
message='add hello but not goodbye', strict=True)
391
def test_strict_commit_without_unknowns(self):
392
"""Try and commit with no unknown files and strict = True,
394
wt = self.make_branch_and_tree('.')
396
file('hello', 'w').write('hello world')
398
wt.commit(message='add hello', strict=True)
400
def test_nonstrict_commit(self):
401
"""Try and commit with unknown files and strict = False, should work."""
402
wt = self.make_branch_and_tree('.')
404
file('hello', 'w').write('hello world')
406
file('goodbye', 'w').write('goodbye cruel world!')
407
wt.commit(message='add hello but not goodbye', strict=False)
409
def test_nonstrict_commit_without_unknowns(self):
410
"""Try and commit with no unknown files and strict = False,
412
wt = self.make_branch_and_tree('.')
414
file('hello', 'w').write('hello world')
416
wt.commit(message='add hello', strict=False)
418
def test_signed_commit(self):
420
import bzrlib.commit as commit
421
oldstrategy = bzrlib.gpg.GPGStrategy
422
wt = self.make_branch_and_tree('.')
424
wt.commit("base", allow_pointless=True, rev_id='A')
425
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
427
from bzrlib.testament import Testament
428
# monkey patch gpg signing mechanism
429
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
430
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
431
allow_pointless=True,
435
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
436
self.assertEqual(sign(Testament.from_revision(branch.repository,
437
'B').as_short_text()),
438
branch.repository.get_signature_text('B'))
440
bzrlib.gpg.GPGStrategy = oldstrategy
442
def test_commit_failed_signature(self):
444
import bzrlib.commit as commit
445
oldstrategy = bzrlib.gpg.GPGStrategy
446
wt = self.make_branch_and_tree('.')
448
wt.commit("base", allow_pointless=True, rev_id='A')
449
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
451
# monkey patch gpg signing mechanism
452
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
453
config = MustSignConfig(branch)
454
self.assertRaises(SigningFailed,
455
commit.Commit(config=config).commit,
457
allow_pointless=True,
460
branch = Branch.open(self.get_url('.'))
461
self.assertEqual(branch.revision_history(), ['A'])
462
self.assertFalse(branch.repository.has_revision('B'))
464
bzrlib.gpg.GPGStrategy = oldstrategy
466
def test_commit_invokes_hooks(self):
467
import bzrlib.commit as commit
468
wt = self.make_branch_and_tree('.')
471
def called(branch, rev_id):
472
calls.append('called')
473
bzrlib.ahook = called
475
config = BranchWithHooks(branch)
476
commit.Commit(config=config).commit(
478
allow_pointless=True,
479
rev_id='A', working_tree = wt)
480
self.assertEqual(['called', 'called'], calls)
484
def test_commit_object_doesnt_set_nick(self):
485
# using the Commit object directly does not set the branch nick.
486
wt = self.make_branch_and_tree('.')
488
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
489
self.assertEquals(wt.branch.revno(), 1)
491
wt.branch.repository.get_revision(
492
wt.branch.last_revision()).properties)
494
def test_safe_master_lock(self):
496
master = BzrDirMetaFormat1().initialize('master')
497
master.create_repository()
498
master_branch = master.create_branch()
499
master.create_workingtree()
500
bound = master.sprout('bound')
501
wt = bound.open_workingtree()
502
wt.branch.set_bound_location(os.path.realpath('master'))
503
master_branch.lock_write()
505
self.assertRaises(LockContention, wt.commit, 'silly')
507
master_branch.unlock()
509
def test_commit_bound_merge(self):
510
# see bug #43959; commit of a merge in a bound branch fails to push
511
# the new commit into the master
512
master_branch = self.make_branch('master')
513
bound_tree = self.make_branch_and_tree('bound')
514
bound_tree.branch.bind(master_branch)
516
self.build_tree_contents([('bound/content_file', 'initial contents\n')])
517
bound_tree.add(['content_file'])
518
bound_tree.commit(message='woo!')
520
other_bzrdir = master_branch.bzrdir.sprout('other')
521
other_tree = other_bzrdir.open_workingtree()
523
# do a commit to the other branch changing the content file so
524
# that our commit after merging will have a merged revision in the
525
# content file history.
526
self.build_tree_contents([('other/content_file', 'change in other\n')])
527
other_tree.commit('change in other')
529
# do a merge into the bound branch from other, and then change the
530
# content file locally to force a new revision (rather than using the
531
# revision from other). This forces extra processing in commit.
532
bound_tree.merge_from_branch(other_tree.branch)
533
self.build_tree_contents([('bound/content_file', 'change in bound\n')])
535
# before #34959 was fixed, this failed with 'revision not present in
536
# weave' when trying to implicitly push from the bound branch to the master
537
bound_tree.commit(message='commit of merge in bound tree')
539
def test_commit_reporting_after_merge(self):
540
# when doing a commit of a merge, the reporter needs to still
541
# be called for each item that is added/removed/deleted.
542
this_tree = self.make_branch_and_tree('this')
543
# we need a bunch of files and dirs, to perform one action on each.
546
'this/dirtoreparent/',
549
'this/filetoreparent',
566
this_tree.commit('create_files')
567
other_dir = this_tree.bzrdir.sprout('other')
568
other_tree = other_dir.open_workingtree()
569
other_tree.lock_write()
570
# perform the needed actions on the files and dirs.
572
other_tree.rename_one('dirtorename', 'renameddir')
573
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
574
other_tree.rename_one('filetorename', 'renamedfile')
575
other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
576
other_tree.remove(['dirtoremove', 'filetoremove'])
577
self.build_tree_contents([
579
('other/filetomodify', 'new content'),
580
('other/newfile', 'new file content')])
581
other_tree.add('newfile')
582
other_tree.add('newdir/')
583
other_tree.commit('modify all sample files and dirs.')
586
this_tree.merge_from_branch(other_tree.branch)
587
reporter = CapturingReporter()
588
this_tree.commit('do the commit', reporter=reporter)
590
('change', 'modified', 'filetomodify'),
591
('change', 'added', 'newdir'),
592
('change', 'added', 'newfile'),
593
('renamed', 'renamed', 'dirtorename', 'renameddir'),
594
('renamed', 'renamed', 'filetorename', 'renamedfile'),
595
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
596
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
597
('deleted', 'dirtoremove'),
598
('deleted', 'filetoremove'),
600
result = set(reporter.calls)
601
missing = expected - result
602
new = result - expected
603
self.assertEqual((set(), set()), (missing, new))
605
def test_commit_removals_respects_filespec(self):
606
"""Commit respects the specified_files for removals."""
607
tree = self.make_branch_and_tree('.')
608
self.build_tree(['a', 'b'])
610
tree.commit('added a, b')
611
tree.remove(['a', 'b'])
612
tree.commit('removed a', specific_files='a')
613
basis = tree.basis_tree()
616
self.assertIs(None, basis.path2id('a'))
617
self.assertFalse(basis.path2id('b') is None)
621
def test_commit_saves_1ms_timestamp(self):
622
"""Passing in a timestamp is saved with 1ms resolution"""
623
tree = self.make_branch_and_tree('.')
624
self.build_tree(['a'])
626
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
629
rev = tree.branch.repository.get_revision('a1')
630
self.assertEqual(1153248633.419, rev.timestamp)
632
def test_commit_has_1ms_resolution(self):
633
"""Allowing commit to generate the timestamp also has 1ms resolution"""
634
tree = self.make_branch_and_tree('.')
635
self.build_tree(['a'])
637
tree.commit('added a', rev_id='a1')
639
rev = tree.branch.repository.get_revision('a1')
640
timestamp = rev.timestamp
641
timestamp_1ms = round(timestamp, 3)
642
self.assertEqual(timestamp_1ms, timestamp)
644
def assertBasisTreeKind(self, kind, tree, file_id):
645
basis = tree.basis_tree()
648
self.assertEqual(kind, basis.kind(file_id))
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')
661
self.build_tree(['name'])
662
tree.commit('Changed symlink to file')
663
self.assertBasisTreeKind('file', tree, 'a-file-id')
666
os.symlink('target', 'name')
667
tree.commit('file to symlink')
668
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
672
tree.commit('symlink to directory')
673
self.assertBasisTreeKind('directory', tree, 'a-file-id')
676
os.symlink('target', 'name')
677
tree.commit('directory to symlink')
678
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
680
# prepare for directory <-> file tests
683
tree.commit('symlink to directory')
684
self.assertBasisTreeKind('directory', tree, 'a-file-id')
687
self.build_tree(['name'])
688
tree.commit('Changed directory to file')
689
self.assertBasisTreeKind('file', tree, 'a-file-id')
693
tree.commit('file to directory')
694
self.assertBasisTreeKind('directory', tree, 'a-file-id')
696
def test_commit_unversioned_specified(self):
697
"""Commit should raise if specified files isn't in basis or worktree"""
698
tree = self.make_branch_and_tree('.')
699
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
700
'message', specific_files=['bogus'])
702
class Callback(object):
704
def __init__(self, message, testcase):
706
self.message = message
707
self.testcase = testcase
709
def __call__(self, commit_obj):
711
self.testcase.assertTrue(isinstance(commit_obj, Commit))
714
def test_commit_callback(self):
715
"""Commit should invoke a callback to get the message"""
717
tree = self.make_branch_and_tree('.')
721
self.assertTrue(isinstance(e, BzrError))
722
self.assertEqual('The message or message_callback keyword'
723
' parameter is required for commit().', str(e))
725
self.fail('exception not raised')
726
cb = self.Callback(u'commit 1', self)
727
tree.commit(message_callback=cb)
728
self.assertTrue(cb.called)
729
repository = tree.branch.repository
730
message = repository.get_revision(tree.last_revision()).message
731
self.assertEqual('commit 1', message)
733
def test_no_callback_pointless(self):
734
"""Callback should not be invoked for pointless commit"""
735
tree = self.make_branch_and_tree('.')
736
cb = self.Callback(u'commit 2', self)
737
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
738
allow_pointless=False)
739
self.assertFalse(cb.called)
741
def test_no_callback_netfailure(self):
742
"""Callback should not be invoked if connectivity fails"""
743
tree = self.make_branch_and_tree('.')
744
cb = self.Callback(u'commit 2', self)
745
repository = tree.branch.repository
746
# simulate network failure
747
def raise_(self, arg, arg2, arg3=None, arg4=None):
748
raise errors.NoSuchFile('foo')
749
repository.add_inventory = raise_
750
repository.add_inventory_by_delta = raise_
751
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
752
self.assertFalse(cb.called)
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))
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'])
774
self.build_tree(['a/c/d/'])
776
tree.rename_one('a/z/x', 'a/c/d/x')
777
tree.commit('test', specific_files=['a/z/y'])
779
def test_commit_no_author(self):
780
"""The default kwarg author in MutableTree.commit should not add
781
the 'author' revision property.
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)
789
def test_commit_author(self):
790
"""Passing a non-empty author kwarg to MutableTree.commit should add
791
the 'author' revision property.
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)
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)
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)
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>")
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>'])
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'))