1
# Copyright (C) 2005-2012, 2016 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
26
from ..branch import Branch
27
from ..bzr.bzrdir import BzrDirMetaFormat1
28
from ..commit import (
29
CannotCommitSelectedFileMerge,
35
from ..errors import (
41
TestCaseWithTransport,
44
from .features import (
47
from .matchers import MatchesAncestry
50
# TODO: Test commit with some added, and added-but-missing files
52
class MustSignConfig(config.MemoryStack):
55
super(MustSignConfig, self).__init__(b'''
56
create_signatures=always
60
class CapturingReporter(NullCommitReporter):
61
"""This reporter captures the calls made to it for evaluation later."""
64
# a list of the calls this received
67
def snapshot_change(self, change, path):
68
self.calls.append(('change', change, path))
70
def deleted(self, file_id):
71
self.calls.append(('deleted', file_id))
73
def missing(self, path):
74
self.calls.append(('missing', path))
76
def renamed(self, change, old_path, new_path):
77
self.calls.append(('renamed', change, old_path, new_path))
83
class TestCommit(TestCaseWithTransport):
85
def test_simple_commit(self):
86
"""Commit and check two versions of a single file."""
87
wt = self.make_branch_and_tree('.')
89
with open('hello', 'w') as f:
90
f.write('hello world')
92
rev1 = wt.commit(message='add hello')
94
with open('hello', 'w') as f:
96
rev2 = wt.commit(message='commit 2')
100
rev = b.repository.get_revision(rev1)
101
eq(rev.message, 'add hello')
103
tree1 = b.repository.revision_tree(rev1)
105
text = tree1.get_file_text('hello')
107
self.assertEqual(b'hello world', text)
109
tree2 = b.repository.revision_tree(rev2)
111
text = tree2.get_file_text('hello')
113
self.assertEqual(b'version 2', text)
115
def test_commit_lossy_native(self):
116
"""Attempt a lossy commit to a native branch."""
117
wt = self.make_branch_and_tree('.')
119
with open('hello', 'w') as f:
120
f.write('hello world')
122
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
123
self.assertEqual(b'revid', revid)
125
def test_commit_lossy_foreign(self):
126
"""Attempt a lossy commit to a foreign branch."""
127
test_foreign.register_dummy_foreign_for_test(self)
128
wt = self.make_branch_and_tree('.',
129
format=test_foreign.DummyForeignVcsDirFormat())
131
with open('hello', 'w') as f:
132
f.write('hello world')
134
revid = wt.commit(message='add hello', lossy=True,
135
timestamp=1302659388, timezone=0)
136
self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
138
def test_commit_bound_lossy_foreign(self):
139
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
140
test_foreign.register_dummy_foreign_for_test(self)
141
foreign_branch = self.make_branch('foreign',
142
format=test_foreign.DummyForeignVcsDirFormat())
143
wt = foreign_branch.create_checkout("local")
145
with open('local/hello', 'w') as f:
146
f.write('hello world')
148
revid = wt.commit(message='add hello', lossy=True,
149
timestamp=1302659388, timezone=0)
150
self.assertEqual(b'dummy-v1:1302659388-0-0', revid)
151
self.assertEqual(b'dummy-v1:1302659388-0-0',
152
foreign_branch.last_revision())
153
self.assertEqual(b'dummy-v1:1302659388-0-0',
154
wt.branch.last_revision())
156
def test_missing_commit(self):
157
"""Test a commit with a missing file"""
158
wt = self.make_branch_and_tree('.')
160
with open('hello', 'w') as f:
161
f.write('hello world')
162
wt.add(['hello'], [b'hello-id'])
163
wt.commit(message='add hello')
166
reporter = CapturingReporter()
167
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
169
[('missing', u'hello'), ('deleted', u'hello')],
172
tree = b.repository.revision_tree(b'rev2')
173
self.assertFalse(tree.has_id(b'hello-id'))
175
def test_partial_commit_move(self):
176
"""Test a partial commit where a file was renamed but not committed.
178
https://bugs.launchpad.net/bzr/+bug/83039
180
If not handled properly, commit will try to snapshot
181
dialog.py with olive/ as a parent, while
182
olive/ has not been snapshotted yet.
184
wt = self.make_branch_and_tree('.')
186
self.build_tree(['annotate/', 'annotate/foo.py',
187
'olive/', 'olive/dialog.py'
189
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
190
wt.commit(message='add files')
191
wt.rename_one("olive/dialog.py", "aaa")
192
self.build_tree_contents([('annotate/foo.py', b'modified\n')])
193
wt.commit('renamed hello', specific_files=["annotate"])
195
def test_pointless_commit(self):
196
"""Commit refuses unless there are changes or it's forced."""
197
wt = self.make_branch_and_tree('.')
199
with open('hello', 'w') as f:
202
wt.commit(message='add hello')
203
self.assertEqual(b.revno(), 1)
204
self.assertRaises(PointlessCommit,
207
allow_pointless=False)
208
self.assertEqual(b.revno(), 1)
210
def test_commit_empty(self):
211
"""Commiting an empty tree works."""
212
wt = self.make_branch_and_tree('.')
214
wt.commit(message='empty tree', allow_pointless=True)
215
self.assertRaises(PointlessCommit,
217
message='empty tree',
218
allow_pointless=False)
219
wt.commit(message='empty tree', allow_pointless=True)
220
self.assertEqual(b.revno(), 2)
222
def test_selective_delete(self):
223
"""Selective commit in tree with deletions"""
224
wt = self.make_branch_and_tree('.')
226
with open('hello', 'w') as f:
228
with open('buongia', 'w') as f:
230
wt.add(['hello', 'buongia'],
231
[b'hello-id', b'buongia-id'])
232
wt.commit(message='add files',
233
rev_id=b'test@rev-1')
236
with open('buongia', 'w') as f:
238
wt.commit(message='update text',
239
specific_files=['buongia'],
240
allow_pointless=False,
241
rev_id=b'test@rev-2')
243
wt.commit(message='remove hello',
244
specific_files=['hello'],
245
allow_pointless=False,
246
rev_id=b'test@rev-3')
248
eq = self.assertEqual
251
tree2 = b.repository.revision_tree(b'test@rev-2')
253
self.addCleanup(tree2.unlock)
254
self.assertTrue(tree2.has_filename('hello'))
255
self.assertEqual(tree2.get_file_text('hello'), b'hello')
256
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
258
tree3 = b.repository.revision_tree(b'test@rev-3')
260
self.addCleanup(tree3.unlock)
261
self.assertFalse(tree3.has_filename('hello'))
262
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
264
def test_commit_rename(self):
265
"""Test commit of a revision where a file is renamed."""
266
tree = self.make_branch_and_tree('.')
268
self.build_tree(['hello'], line_endings='binary')
269
tree.add(['hello'], [b'hello-id'])
270
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
272
tree.rename_one('hello', 'fruity')
273
tree.commit(message='renamed', rev_id=b'test@rev-2',
274
allow_pointless=False)
276
eq = self.assertEqual
277
tree1 = b.repository.revision_tree(b'test@rev-1')
279
self.addCleanup(tree1.unlock)
280
eq(tree1.id2path(b'hello-id'), 'hello')
281
eq(tree1.get_file_text('hello'), b'contents of hello\n')
282
self.assertFalse(tree1.has_filename('fruity'))
283
self.check_tree_shape(tree1, ['hello'])
284
eq(tree1.get_file_revision('hello'), b'test@rev-1')
286
tree2 = b.repository.revision_tree(b'test@rev-2')
288
self.addCleanup(tree2.unlock)
289
eq(tree2.id2path(b'hello-id'), 'fruity')
290
eq(tree2.get_file_text('fruity'), b'contents of hello\n')
291
self.check_tree_shape(tree2, ['fruity'])
292
eq(tree2.get_file_revision('fruity'), b'test@rev-2')
294
def test_reused_rev_id(self):
295
"""Test that a revision id cannot be reused in a branch"""
296
wt = self.make_branch_and_tree('.')
298
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
299
self.assertRaises(Exception,
302
rev_id=b'test@rev-1',
303
allow_pointless=True)
305
def test_commit_move(self):
306
"""Test commit of revisions with moved files and directories"""
307
eq = self.assertEqual
308
wt = self.make_branch_and_tree('.')
311
self.build_tree(['hello', 'a/', 'b/'])
312
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
313
wt.commit('initial', rev_id=r1, allow_pointless=False)
314
wt.move(['hello'], 'a')
316
wt.commit('two', rev_id=r2, allow_pointless=False)
319
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
325
wt.commit('three', rev_id=r3, allow_pointless=False)
328
self.check_tree_shape(wt,
329
['a/', 'a/hello', 'a/b/'])
330
self.check_tree_shape(b.repository.revision_tree(r3),
331
['a/', 'a/hello', 'a/b/'])
335
wt.move(['a/hello'], 'a/b')
337
wt.commit('four', rev_id=r4, allow_pointless=False)
340
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
344
inv = b.repository.get_inventory(r4)
345
eq(inv.get_entry(b'hello-id').revision, r4)
346
eq(inv.get_entry(b'a-id').revision, r1)
347
eq(inv.get_entry(b'b-id').revision, r3)
349
def test_removed_commit(self):
350
"""Commit with a removed file"""
351
wt = self.make_branch_and_tree('.')
353
with open('hello', 'w') as f:
354
f.write('hello world')
355
wt.add(['hello'], [b'hello-id'])
356
wt.commit(message='add hello')
358
wt.commit('removed hello', rev_id=b'rev2')
360
tree = b.repository.revision_tree(b'rev2')
361
self.assertFalse(tree.has_id(b'hello-id'))
363
def test_committed_ancestry(self):
364
"""Test commit appends revisions to ancestry."""
365
wt = self.make_branch_and_tree('.')
369
with open('hello', 'w') as f:
370
f.write((str(i) * 4) + '\n')
372
wt.add(['hello'], [b'hello-id'])
373
rev_id = b'test@rev-%d' % (i + 1)
374
rev_ids.append(rev_id)
375
wt.commit(message='rev %d' % (i + 1),
378
self.assertThat(rev_ids[:i + 1],
379
MatchesAncestry(b.repository, rev_ids[i]))
381
def test_commit_new_subdir_child_selective(self):
382
wt = self.make_branch_and_tree('.')
384
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
385
wt.add(['dir', 'dir/file1', 'dir/file2'],
386
[b'dirid', b'file1id', b'file2id'])
387
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id=b'1')
388
inv = b.repository.get_inventory(b'1')
389
self.assertEqual(b'1', inv.get_entry(b'dirid').revision)
390
self.assertEqual(b'1', inv.get_entry(b'file1id').revision)
391
# FIXME: This should raise a KeyError I think, rbc20051006
392
self.assertRaises(BzrError, inv.get_entry, b'file2id')
394
def test_strict_commit(self):
395
"""Try and commit with unknown files and strict = True, should fail."""
396
from ..errors import StrictCommitFailed
397
wt = self.make_branch_and_tree('.')
399
with open('hello', 'w') as f:
400
f.write('hello world')
402
with open('goodbye', 'w') as f:
403
f.write('goodbye cruel world!')
404
self.assertRaises(StrictCommitFailed, wt.commit,
405
message='add hello but not goodbye', strict=True)
407
def test_strict_commit_without_unknowns(self):
408
"""Try and commit with no unknown files and strict = True,
410
wt = self.make_branch_and_tree('.')
412
with open('hello', 'w') as f:
413
f.write('hello world')
415
wt.commit(message='add hello', strict=True)
417
def test_nonstrict_commit(self):
418
"""Try and commit with unknown files and strict = False, should work."""
419
wt = self.make_branch_and_tree('.')
421
with open('hello', 'w') as f:
422
f.write('hello world')
424
with open('goodbye', 'w') as f:
425
f.write('goodbye cruel world!')
426
wt.commit(message='add hello but not goodbye', strict=False)
428
def test_nonstrict_commit_without_unknowns(self):
429
"""Try and commit with no unknown files and strict = False,
431
wt = self.make_branch_and_tree('.')
433
with open('hello', 'w') as f:
434
f.write('hello world')
436
wt.commit(message='add hello', strict=False)
438
def test_signed_commit(self):
440
import breezy.commit as commit
441
oldstrategy = breezy.gpg.GPGStrategy
442
wt = self.make_branch_and_tree('.')
444
wt.commit("base", allow_pointless=True, rev_id=b'A')
445
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
447
from ..bzr.testament import Testament
448
# monkey patch gpg signing mechanism
449
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
450
conf = config.MemoryStack(b'''
451
create_signatures=always
453
commit.Commit(config_stack=conf).commit(
454
message="base", allow_pointless=True, rev_id=b'B',
458
return breezy.gpg.LoopbackGPGStrategy(None).sign(
459
text, breezy.gpg.MODE_CLEAR)
460
self.assertEqual(sign(Testament.from_revision(branch.repository,
461
b'B').as_short_text()),
462
branch.repository.get_signature_text(b'B'))
464
breezy.gpg.GPGStrategy = oldstrategy
466
def test_commit_failed_signature(self):
468
import breezy.commit as commit
469
oldstrategy = breezy.gpg.GPGStrategy
470
wt = self.make_branch_and_tree('.')
472
wt.commit("base", allow_pointless=True, rev_id=b'A')
473
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
475
# monkey patch gpg signing mechanism
476
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
477
conf = config.MemoryStack(b'''
478
create_signatures=always
480
self.assertRaises(breezy.gpg.SigningFailed,
481
commit.Commit(config_stack=conf).commit,
483
allow_pointless=True,
486
branch = Branch.open(self.get_url('.'))
487
self.assertEqual(branch.last_revision(), b'A')
488
self.assertFalse(branch.repository.has_revision(b'B'))
490
breezy.gpg.GPGStrategy = oldstrategy
492
def test_commit_invokes_hooks(self):
493
import breezy.commit as commit
494
wt = self.make_branch_and_tree('.')
498
def called(branch, rev_id):
499
calls.append('called')
500
breezy.ahook = called
502
conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
503
commit.Commit(config_stack=conf).commit(
504
message="base", allow_pointless=True, rev_id=b'A',
506
self.assertEqual(['called', 'called'], calls)
510
def test_commit_object_doesnt_set_nick(self):
511
# using the Commit object directly does not set the branch nick.
512
wt = self.make_branch_and_tree('.')
514
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
515
self.assertEqual(wt.branch.revno(), 1)
517
wt.branch.repository.get_revision(
518
wt.branch.last_revision()).properties)
520
def test_safe_master_lock(self):
522
master = BzrDirMetaFormat1().initialize('master')
523
master.create_repository()
524
master_branch = master.create_branch()
525
master.create_workingtree()
526
bound = master.sprout('bound')
527
wt = bound.open_workingtree()
528
wt.branch.set_bound_location(os.path.realpath('master'))
529
master_branch.lock_write()
531
self.assertRaises(LockContention, wt.commit, 'silly')
533
master_branch.unlock()
535
def test_commit_bound_merge(self):
536
# see bug #43959; commit of a merge in a bound branch fails to push
537
# the new commit into the master
538
master_branch = self.make_branch('master')
539
bound_tree = self.make_branch_and_tree('bound')
540
bound_tree.branch.bind(master_branch)
542
self.build_tree_contents(
543
[('bound/content_file', b'initial contents\n')])
544
bound_tree.add(['content_file'])
545
bound_tree.commit(message='woo!')
547
other_bzrdir = master_branch.controldir.sprout('other')
548
other_tree = other_bzrdir.open_workingtree()
550
# do a commit to the other branch changing the content file so
551
# that our commit after merging will have a merged revision in the
552
# content file history.
553
self.build_tree_contents(
554
[('other/content_file', b'change in other\n')])
555
other_tree.commit('change in other')
557
# do a merge into the bound branch from other, and then change the
558
# content file locally to force a new revision (rather than using the
559
# revision from other). This forces extra processing in commit.
560
bound_tree.merge_from_branch(other_tree.branch)
561
self.build_tree_contents(
562
[('bound/content_file', b'change in bound\n')])
564
# before #34959 was fixed, this failed with 'revision not present in
565
# weave' when trying to implicitly push from the bound branch to the master
566
bound_tree.commit(message='commit of merge in bound tree')
568
def test_commit_reporting_after_merge(self):
569
# when doing a commit of a merge, the reporter needs to still
570
# be called for each item that is added/removed/deleted.
571
this_tree = self.make_branch_and_tree('this')
572
# we need a bunch of files and dirs, to perform one action on each.
575
'this/dirtoreparent/',
578
'this/filetoreparent',
595
this_tree.commit('create_files')
596
other_dir = this_tree.controldir.sprout('other')
597
other_tree = other_dir.open_workingtree()
598
other_tree.lock_write()
599
# perform the needed actions on the files and dirs.
601
other_tree.rename_one('dirtorename', 'renameddir')
602
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
603
other_tree.rename_one('filetorename', 'renamedfile')
604
other_tree.rename_one(
605
'filetoreparent', 'renameddir/reparentedfile')
606
other_tree.remove(['dirtoremove', 'filetoremove'])
607
self.build_tree_contents([
609
('other/filetomodify', b'new content'),
610
('other/newfile', b'new file content')])
611
other_tree.add('newfile')
612
other_tree.add('newdir/')
613
other_tree.commit('modify all sample files and dirs.')
616
this_tree.merge_from_branch(other_tree.branch)
617
reporter = CapturingReporter()
618
this_tree.commit('do the commit', reporter=reporter)
620
('change', 'modified', 'filetomodify'),
621
('change', 'added', 'newdir'),
622
('change', 'added', 'newfile'),
623
('renamed', 'renamed', 'dirtorename', 'renameddir'),
624
('renamed', 'renamed', 'filetorename', 'renamedfile'),
625
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
626
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
627
('deleted', 'dirtoremove'),
628
('deleted', 'filetoremove'),
630
result = set(reporter.calls)
631
missing = expected - result
632
new = result - expected
633
self.assertEqual((set(), set()), (missing, new))
635
def test_commit_removals_respects_filespec(self):
636
"""Commit respects the specified_files for removals."""
637
tree = self.make_branch_and_tree('.')
638
self.build_tree(['a', 'b'])
640
tree.commit('added a, b')
641
tree.remove(['a', 'b'])
642
tree.commit('removed a', specific_files='a')
643
basis = tree.basis_tree()
644
with tree.lock_read():
645
self.assertFalse(basis.is_versioned('a'))
646
self.assertTrue(basis.is_versioned('b'))
648
def test_commit_saves_1ms_timestamp(self):
649
"""Passing in a timestamp is saved with 1ms resolution"""
650
tree = self.make_branch_and_tree('.')
651
self.build_tree(['a'])
653
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
656
rev = tree.branch.repository.get_revision(b'a1')
657
self.assertEqual(1153248633.419, rev.timestamp)
659
def test_commit_has_1ms_resolution(self):
660
"""Allowing commit to generate the timestamp also has 1ms resolution"""
661
tree = self.make_branch_and_tree('.')
662
self.build_tree(['a'])
664
tree.commit('added a', rev_id=b'a1')
666
rev = tree.branch.repository.get_revision(b'a1')
667
timestamp = rev.timestamp
668
timestamp_1ms = round(timestamp, 3)
669
self.assertEqual(timestamp_1ms, timestamp)
671
def assertBasisTreeKind(self, kind, tree, path):
672
basis = tree.basis_tree()
675
self.assertEqual(kind, basis.kind(path))
679
def test_commit_kind_changes(self):
680
self.requireFeature(SymlinkFeature)
681
tree = self.make_branch_and_tree('.')
682
os.symlink('target', 'name')
683
tree.add('name', b'a-file-id')
684
tree.commit('Added a symlink')
685
self.assertBasisTreeKind('symlink', tree, 'name')
688
self.build_tree(['name'])
689
tree.commit('Changed symlink to file')
690
self.assertBasisTreeKind('file', tree, 'name')
693
os.symlink('target', 'name')
694
tree.commit('file to symlink')
695
self.assertBasisTreeKind('symlink', tree, 'name')
699
tree.commit('symlink to directory')
700
self.assertBasisTreeKind('directory', tree, 'name')
703
os.symlink('target', 'name')
704
tree.commit('directory to symlink')
705
self.assertBasisTreeKind('symlink', tree, 'name')
707
# prepare for directory <-> file tests
710
tree.commit('symlink to directory')
711
self.assertBasisTreeKind('directory', tree, 'name')
714
self.build_tree(['name'])
715
tree.commit('Changed directory to file')
716
self.assertBasisTreeKind('file', tree, 'name')
720
tree.commit('file to directory')
721
self.assertBasisTreeKind('directory', tree, 'name')
723
def test_commit_unversioned_specified(self):
724
"""Commit should raise if specified files isn't in basis or worktree"""
725
tree = self.make_branch_and_tree('.')
726
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
727
'message', specific_files=['bogus'])
729
class Callback(object):
731
def __init__(self, message, testcase):
733
self.message = message
734
self.testcase = testcase
736
def __call__(self, commit_obj):
738
self.testcase.assertTrue(isinstance(commit_obj, Commit))
741
def test_commit_callback(self):
742
"""Commit should invoke a callback to get the message"""
744
tree = self.make_branch_and_tree('.')
747
except Exception as e:
748
self.assertTrue(isinstance(e, BzrError))
749
self.assertEqual('The message or message_callback keyword'
750
' parameter is required for commit().', str(e))
752
self.fail('exception not raised')
753
cb = self.Callback(u'commit 1', self)
754
tree.commit(message_callback=cb)
755
self.assertTrue(cb.called)
756
repository = tree.branch.repository
757
message = repository.get_revision(tree.last_revision()).message
758
self.assertEqual('commit 1', message)
760
def test_no_callback_pointless(self):
761
"""Callback should not be invoked for pointless commit"""
762
tree = self.make_branch_and_tree('.')
763
cb = self.Callback(u'commit 2', self)
764
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
765
allow_pointless=False)
766
self.assertFalse(cb.called)
768
def test_no_callback_netfailure(self):
769
"""Callback should not be invoked if connectivity fails"""
770
tree = self.make_branch_and_tree('.')
771
cb = self.Callback(u'commit 2', self)
772
repository = tree.branch.repository
773
# simulate network failure
775
def raise_(self, arg, arg2, arg3=None, arg4=None):
776
raise errors.NoSuchFile('foo')
777
repository.add_inventory = raise_
778
repository.add_inventory_by_delta = raise_
779
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
780
self.assertFalse(cb.called)
782
def test_selected_file_merge_commit(self):
783
"""Ensure the correct error is raised"""
784
tree = self.make_branch_and_tree('foo')
785
# pending merge would turn into a left parent
786
tree.commit('commit 1')
787
tree.add_parent_tree_id(b'example')
788
self.build_tree(['foo/bar', 'foo/baz'])
789
tree.add(['bar', 'baz'])
790
err = self.assertRaises(CannotCommitSelectedFileMerge,
791
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
792
self.assertEqual(['bar', 'baz'], err.files)
793
self.assertEqual('Selected-file commit of merges is not supported'
794
' yet: files bar, baz', str(err))
796
def test_commit_ordering(self):
797
"""Test of corner-case commit ordering error"""
798
tree = self.make_branch_and_tree('.')
799
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
800
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
802
self.build_tree(['a/c/d/'])
804
tree.rename_one('a/z/x', 'a/c/d/x')
805
tree.commit('test', specific_files=['a/z/y'])
807
def test_commit_no_author(self):
808
"""The default kwarg author in MutableTree.commit should not add
809
the 'author' revision property.
811
tree = self.make_branch_and_tree('foo')
812
rev_id = tree.commit('commit 1')
813
rev = tree.branch.repository.get_revision(rev_id)
814
self.assertFalse('author' in rev.properties)
815
self.assertFalse('authors' in rev.properties)
817
def test_commit_author(self):
818
"""Passing a non-empty authors kwarg to MutableTree.commit should add
819
the 'author' revision property.
821
tree = self.make_branch_and_tree('foo')
822
rev_id = tree.commit(
824
authors=['John Doe <jdoe@example.com>'])
825
rev = tree.branch.repository.get_revision(rev_id)
826
self.assertEqual('John Doe <jdoe@example.com>',
827
rev.properties['authors'])
828
self.assertFalse('author' in rev.properties)
830
def test_commit_empty_authors_list(self):
831
"""Passing an empty list to authors shouldn't add the property."""
832
tree = self.make_branch_and_tree('foo')
833
rev_id = tree.commit('commit 1', authors=[])
834
rev = tree.branch.repository.get_revision(rev_id)
835
self.assertFalse('author' in rev.properties)
836
self.assertFalse('authors' in rev.properties)
838
def test_multiple_authors(self):
839
tree = self.make_branch_and_tree('foo')
840
rev_id = tree.commit('commit 1',
841
authors=['John Doe <jdoe@example.com>',
842
'Jane Rey <jrey@example.com>'])
843
rev = tree.branch.repository.get_revision(rev_id)
844
self.assertEqual('John Doe <jdoe@example.com>\n'
845
'Jane Rey <jrey@example.com>', rev.properties['authors'])
846
self.assertFalse('author' in rev.properties)
848
def test_author_with_newline_rejected(self):
849
tree = self.make_branch_and_tree('foo')
850
self.assertRaises(AssertionError, tree.commit, 'commit 1',
851
authors=['John\nDoe <jdoe@example.com>'])
853
def test_commit_with_checkout_and_branch_sharing_repo(self):
854
repo = self.make_repository('repo', shared=True)
855
# make_branch_and_tree ignores shared repos
856
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
857
tree2 = branch.create_checkout('repo/tree2')
858
tree2.commit('message', rev_id=b'rev1')
859
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
862
class FilterExcludedTests(TestCase):
864
def test_add_file_not_excluded(self):
866
('fid', (None, 'newpath'),
867
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
868
('file', 'file'), (True, True))]
869
self.assertEqual(changes, list(
870
filter_excluded(changes, ['otherpath'])))
872
def test_add_file_excluded(self):
874
('fid', (None, 'newpath'),
875
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
876
('file', 'file'), (True, True))]
877
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
879
def test_delete_file_excluded(self):
881
('fid', ('somepath', None),
882
0, (False, None), ('pid', None), ('newpath', None),
883
('file', None), (True, None))]
884
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
886
def test_move_from_or_to_excluded(self):
888
('fid', ('oldpath', 'newpath'),
889
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
890
('file', 'file'), (True, True))]
891
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
892
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))