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_tree_shape(tree1, ['hello'])
266
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
268
tree2 = b.repository.revision_tree('test@rev-2')
270
self.addCleanup(tree2.unlock)
271
eq(tree2.id2path('hello-id'), 'fruity')
272
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')
276
def test_reused_rev_id(self):
277
"""Test that a revision id cannot be reused in a branch"""
278
wt = self.make_branch_and_tree('.')
280
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
281
self.assertRaises(Exception,
285
allow_pointless=True)
287
def test_commit_move(self):
288
"""Test commit of revisions with moved files and directories"""
289
eq = self.assertEquals
290
wt = self.make_branch_and_tree('.')
293
self.build_tree(['hello', 'a/', 'b/'])
294
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
295
wt.commit('initial', rev_id=r1, allow_pointless=False)
296
wt.move(['hello'], 'a')
298
wt.commit('two', rev_id=r2, allow_pointless=False)
301
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
307
wt.commit('three', rev_id=r3, allow_pointless=False)
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/'])
317
wt.move(['a/hello'], 'a/b')
319
wt.commit('four', rev_id=r4, allow_pointless=False)
322
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
326
inv = b.repository.get_inventory(r4)
327
eq(inv['hello-id'].revision, r4)
328
eq(inv['a-id'].revision, r1)
329
eq(inv['b-id'].revision, r3)
331
def test_removed_commit(self):
332
"""Commit with a removed file"""
333
wt = self.make_branch_and_tree('.')
335
file('hello', 'w').write('hello world')
336
wt.add(['hello'], ['hello-id'])
337
wt.commit(message='add hello')
339
wt.commit('removed hello', rev_id='rev2')
341
tree = b.repository.revision_tree('rev2')
342
self.assertFalse(tree.has_id('hello-id'))
344
def test_committed_ancestry(self):
345
"""Test commit appends revisions to ancestry."""
346
wt = self.make_branch_and_tree('.')
350
file('hello', 'w').write((str(i) * 4) + '\n')
352
wt.add(['hello'], ['hello-id'])
353
rev_id = 'test@rev-%d' % (i+1)
354
rev_ids.append(rev_id)
355
wt.commit(message='rev %d' % (i+1),
357
eq = self.assertEquals
358
eq(b.revision_history(), rev_ids)
360
anc = b.repository.get_ancestry(rev_ids[i])
361
eq(anc, [None] + rev_ids[:i+1])
363
def test_commit_new_subdir_child_selective(self):
364
wt = self.make_branch_and_tree('.')
366
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
367
wt.add(['dir', 'dir/file1', 'dir/file2'],
368
['dirid', 'file1id', 'file2id'])
369
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
370
inv = b.repository.get_inventory('1')
371
self.assertEqual('1', inv['dirid'].revision)
372
self.assertEqual('1', inv['file1id'].revision)
373
# FIXME: This should raise a KeyError I think, rbc20051006
374
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
376
def test_strict_commit(self):
377
"""Try and commit with unknown files and strict = True, should fail."""
378
from bzrlib.errors import StrictCommitFailed
379
wt = self.make_branch_and_tree('.')
381
file('hello', 'w').write('hello world')
383
file('goodbye', 'w').write('goodbye cruel world!')
384
self.assertRaises(StrictCommitFailed, wt.commit,
385
message='add hello but not goodbye', strict=True)
387
def test_strict_commit_without_unknowns(self):
388
"""Try and commit with no unknown files and strict = True,
390
wt = self.make_branch_and_tree('.')
392
file('hello', 'w').write('hello world')
394
wt.commit(message='add hello', strict=True)
396
def test_nonstrict_commit(self):
397
"""Try and commit with unknown files and strict = False, should work."""
398
wt = self.make_branch_and_tree('.')
400
file('hello', 'w').write('hello world')
402
file('goodbye', 'w').write('goodbye cruel world!')
403
wt.commit(message='add hello but not goodbye', strict=False)
405
def test_nonstrict_commit_without_unknowns(self):
406
"""Try and commit with no unknown files and strict = False,
408
wt = self.make_branch_and_tree('.')
410
file('hello', 'w').write('hello world')
412
wt.commit(message='add hello', strict=False)
414
def test_signed_commit(self):
416
import bzrlib.commit as commit
417
oldstrategy = bzrlib.gpg.GPGStrategy
418
wt = self.make_branch_and_tree('.')
420
wt.commit("base", allow_pointless=True, rev_id='A')
421
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
423
from bzrlib.testament import Testament
424
# monkey patch gpg signing mechanism
425
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
426
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
427
allow_pointless=True,
431
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
432
self.assertEqual(sign(Testament.from_revision(branch.repository,
433
'B').as_short_text()),
434
branch.repository.get_signature_text('B'))
436
bzrlib.gpg.GPGStrategy = oldstrategy
438
def test_commit_failed_signature(self):
440
import bzrlib.commit as commit
441
oldstrategy = bzrlib.gpg.GPGStrategy
442
wt = self.make_branch_and_tree('.')
444
wt.commit("base", allow_pointless=True, rev_id='A')
445
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
447
# monkey patch gpg signing mechanism
448
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
449
config = MustSignConfig(branch)
450
self.assertRaises(SigningFailed,
451
commit.Commit(config=config).commit,
453
allow_pointless=True,
456
branch = Branch.open(self.get_url('.'))
457
self.assertEqual(branch.revision_history(), ['A'])
458
self.assertFalse(branch.repository.has_revision('B'))
460
bzrlib.gpg.GPGStrategy = oldstrategy
462
def test_commit_invokes_hooks(self):
463
import bzrlib.commit as commit
464
wt = self.make_branch_and_tree('.')
467
def called(branch, rev_id):
468
calls.append('called')
469
bzrlib.ahook = called
471
config = BranchWithHooks(branch)
472
commit.Commit(config=config).commit(
474
allow_pointless=True,
475
rev_id='A', working_tree = wt)
476
self.assertEqual(['called', 'called'], calls)
480
def test_commit_object_doesnt_set_nick(self):
481
# using the Commit object directly does not set the branch nick.
482
wt = self.make_branch_and_tree('.')
484
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
485
self.assertEquals(wt.branch.revno(), 1)
487
wt.branch.repository.get_revision(
488
wt.branch.last_revision()).properties)
490
def test_safe_master_lock(self):
492
master = BzrDirMetaFormat1().initialize('master')
493
master.create_repository()
494
master_branch = master.create_branch()
495
master.create_workingtree()
496
bound = master.sprout('bound')
497
wt = bound.open_workingtree()
498
wt.branch.set_bound_location(os.path.realpath('master'))
499
master_branch.lock_write()
501
self.assertRaises(LockContention, wt.commit, 'silly')
503
master_branch.unlock()
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)
512
self.build_tree_contents([('bound/content_file', 'initial contents\n')])
513
bound_tree.add(['content_file'])
514
bound_tree.commit(message='woo!')
516
other_bzrdir = master_branch.bzrdir.sprout('other')
517
other_tree = other_bzrdir.open_workingtree()
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')
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')])
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')
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.
542
'this/dirtoreparent/',
545
'this/filetoreparent',
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.
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([
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.')
582
this_tree.merge_from_branch(other_tree.branch)
583
reporter = CapturingReporter()
584
this_tree.commit('do the commit', reporter=reporter)
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'),
596
result = set(reporter.calls)
597
missing = expected - result
598
new = result - expected
599
self.assertEqual((set(), set()), (missing, new))
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'])
606
tree.commit('added a, b')
607
tree.remove(['a', 'b'])
608
tree.commit('removed a', specific_files='a')
609
basis = tree.basis_tree()
612
self.assertIs(None, basis.path2id('a'))
613
self.assertFalse(basis.path2id('b') is None)
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'])
622
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
625
rev = tree.branch.repository.get_revision('a1')
626
self.assertEqual(1153248633.419, rev.timestamp)
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'])
633
tree.commit('added a', rev_id='a1')
635
rev = tree.branch.repository.get_revision('a1')
636
timestamp = rev.timestamp
637
timestamp_1ms = round(timestamp, 3)
638
self.assertEqual(timestamp_1ms, timestamp)
640
def assertBasisTreeKind(self, kind, tree, file_id):
641
basis = tree.basis_tree()
644
self.assertEqual(kind, basis.kind(file_id))
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')
657
self.build_tree(['name'])
658
tree.commit('Changed symlink to file')
659
self.assertBasisTreeKind('file', tree, 'a-file-id')
662
os.symlink('target', 'name')
663
tree.commit('file to symlink')
664
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
668
tree.commit('symlink to directory')
669
self.assertBasisTreeKind('directory', tree, 'a-file-id')
672
os.symlink('target', 'name')
673
tree.commit('directory to symlink')
674
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
676
# prepare for directory <-> file tests
679
tree.commit('symlink to directory')
680
self.assertBasisTreeKind('directory', tree, 'a-file-id')
683
self.build_tree(['name'])
684
tree.commit('Changed directory to file')
685
self.assertBasisTreeKind('file', tree, 'a-file-id')
689
tree.commit('file to directory')
690
self.assertBasisTreeKind('directory', tree, 'a-file-id')
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'])
698
class Callback(object):
700
def __init__(self, message, testcase):
702
self.message = message
703
self.testcase = testcase
705
def __call__(self, commit_obj):
707
self.testcase.assertTrue(isinstance(commit_obj, Commit))
710
def test_commit_callback(self):
711
"""Commit should invoke a callback to get the message"""
713
tree = self.make_branch_and_tree('.')
717
self.assertTrue(isinstance(e, BzrError))
718
self.assertEqual('The message or message_callback keyword'
719
' parameter is required for commit().', str(e))
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)
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)
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)
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))
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'])
770
self.build_tree(['a/c/d/'])
772
tree.rename_one('a/z/x', 'a/c/d/x')
773
tree.commit('test', specific_files=['a/z/y'])
775
def test_commit_no_author(self):
776
"""The default kwarg author in MutableTree.commit should not add
777
the 'author' revision property.
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)
785
def test_commit_author(self):
786
"""Passing a non-empty author kwarg to MutableTree.commit should add
787
the 'author' revision property.
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)
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)
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)
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>")
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>'])
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'))