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: f.write('hello world')
91
rev1 = wt.commit(message='add hello')
93
with open('hello', 'w') as f: f.write('version 2')
94
rev2 = wt.commit(message='commit 2')
98
rev = b.repository.get_revision(rev1)
99
eq(rev.message, 'add hello')
101
tree1 = b.repository.revision_tree(rev1)
103
text = tree1.get_file_text('hello')
105
self.assertEqual(b'hello world', text)
107
tree2 = b.repository.revision_tree(rev2)
109
text = tree2.get_file_text('hello')
111
self.assertEqual(b'version 2', text)
113
def test_commit_lossy_native(self):
114
"""Attempt a lossy commit to a native branch."""
115
wt = self.make_branch_and_tree('.')
117
with open('hello', 'w') as f: f.write('hello world')
119
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
120
self.assertEqual('revid', revid)
122
def test_commit_lossy_foreign(self):
123
"""Attempt a lossy commit to a foreign branch."""
124
test_foreign.register_dummy_foreign_for_test(self)
125
wt = self.make_branch_and_tree('.',
126
format=test_foreign.DummyForeignVcsDirFormat())
128
with open('hello', 'w') as f: f.write('hello world')
130
revid = wt.commit(message='add hello', lossy=True,
131
timestamp=1302659388, timezone=0)
132
self.assertEqual('dummy-v1:1302659388.0-0-UNKNOWN', revid)
134
def test_commit_bound_lossy_foreign(self):
135
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
136
test_foreign.register_dummy_foreign_for_test(self)
137
foreign_branch = self.make_branch('foreign',
138
format=test_foreign.DummyForeignVcsDirFormat())
139
wt = foreign_branch.create_checkout("local")
141
with open('local/hello', 'w') as f: f.write('hello world')
143
revid = wt.commit(message='add hello', lossy=True,
144
timestamp=1302659388, timezone=0)
145
self.assertEqual('dummy-v1:1302659388.0-0-0', revid)
146
self.assertEqual('dummy-v1:1302659388.0-0-0',
147
foreign_branch.last_revision())
148
self.assertEqual('dummy-v1:1302659388.0-0-0',
149
wt.branch.last_revision())
151
def test_missing_commit(self):
152
"""Test a commit with a missing file"""
153
wt = self.make_branch_and_tree('.')
155
with open('hello', 'w') as f: f.write('hello world')
156
wt.add(['hello'], [b'hello-id'])
157
wt.commit(message='add hello')
160
reporter = CapturingReporter()
161
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
163
[('missing', u'hello'), ('deleted', u'hello')],
166
tree = b.repository.revision_tree(b'rev2')
167
self.assertFalse(tree.has_id(b'hello-id'))
169
def test_partial_commit_move(self):
170
"""Test a partial commit where a file was renamed but not committed.
172
https://bugs.launchpad.net/bzr/+bug/83039
174
If not handled properly, commit will try to snapshot
175
dialog.py with olive/ as a parent, while
176
olive/ has not been snapshotted yet.
178
wt = self.make_branch_and_tree('.')
180
self.build_tree(['annotate/', 'annotate/foo.py',
181
'olive/', 'olive/dialog.py'
183
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
184
wt.commit(message='add files')
185
wt.rename_one("olive/dialog.py", "aaa")
186
self.build_tree_contents([('annotate/foo.py', b'modified\n')])
187
wt.commit('renamed hello', specific_files=["annotate"])
189
def test_pointless_commit(self):
190
"""Commit refuses unless there are changes or it's forced."""
191
wt = self.make_branch_and_tree('.')
193
with open('hello', 'w') as f: f.write('hello')
195
wt.commit(message='add hello')
196
self.assertEqual(b.revno(), 1)
197
self.assertRaises(PointlessCommit,
200
allow_pointless=False)
201
self.assertEqual(b.revno(), 1)
203
def test_commit_empty(self):
204
"""Commiting an empty tree works."""
205
wt = self.make_branch_and_tree('.')
207
wt.commit(message='empty tree', allow_pointless=True)
208
self.assertRaises(PointlessCommit,
210
message='empty tree',
211
allow_pointless=False)
212
wt.commit(message='empty tree', allow_pointless=True)
213
self.assertEqual(b.revno(), 2)
215
def test_selective_delete(self):
216
"""Selective commit in tree with deletions"""
217
wt = self.make_branch_and_tree('.')
219
with open('hello', 'w') as f: f.write('hello')
220
with open('buongia', 'w') as f: f.write('buongia')
221
wt.add(['hello', 'buongia'],
222
[b'hello-id', b'buongia-id'])
223
wt.commit(message='add files',
224
rev_id=b'test@rev-1')
227
with open('buongia', 'w') as f: f.write('new text')
228
wt.commit(message='update text',
229
specific_files=['buongia'],
230
allow_pointless=False,
231
rev_id=b'test@rev-2')
233
wt.commit(message='remove hello',
234
specific_files=['hello'],
235
allow_pointless=False,
236
rev_id=b'test@rev-3')
238
eq = self.assertEqual
241
tree2 = b.repository.revision_tree(b'test@rev-2')
243
self.addCleanup(tree2.unlock)
244
self.assertTrue(tree2.has_filename('hello'))
245
self.assertEqual(tree2.get_file_text('hello'), b'hello')
246
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
248
tree3 = b.repository.revision_tree(b'test@rev-3')
250
self.addCleanup(tree3.unlock)
251
self.assertFalse(tree3.has_filename('hello'))
252
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
254
def test_commit_rename(self):
255
"""Test commit of a revision where a file is renamed."""
256
tree = self.make_branch_and_tree('.')
258
self.build_tree(['hello'], line_endings='binary')
259
tree.add(['hello'], [b'hello-id'])
260
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
262
tree.rename_one('hello', 'fruity')
263
tree.commit(message='renamed', rev_id=b'test@rev-2', allow_pointless=False)
265
eq = self.assertEqual
266
tree1 = b.repository.revision_tree(b'test@rev-1')
268
self.addCleanup(tree1.unlock)
269
eq(tree1.id2path(b'hello-id'), 'hello')
270
eq(tree1.get_file_text('hello'), b'contents of hello\n')
271
self.assertFalse(tree1.has_filename('fruity'))
272
self.check_tree_shape(tree1, ['hello'])
273
eq(tree1.get_file_revision('hello'), b'test@rev-1')
275
tree2 = b.repository.revision_tree(b'test@rev-2')
277
self.addCleanup(tree2.unlock)
278
eq(tree2.id2path(b'hello-id'), 'fruity')
279
eq(tree2.get_file_text('fruity'), b'contents of hello\n')
280
self.check_tree_shape(tree2, ['fruity'])
281
eq(tree2.get_file_revision('fruity'), b'test@rev-2')
283
def test_reused_rev_id(self):
284
"""Test that a revision id cannot be reused in a branch"""
285
wt = self.make_branch_and_tree('.')
287
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
288
self.assertRaises(Exception,
291
rev_id=b'test@rev-1',
292
allow_pointless=True)
294
def test_commit_move(self):
295
"""Test commit of revisions with moved files and directories"""
296
eq = self.assertEqual
297
wt = self.make_branch_and_tree('.')
300
self.build_tree(['hello', 'a/', 'b/'])
301
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
302
wt.commit('initial', rev_id=r1, allow_pointless=False)
303
wt.move(['hello'], 'a')
305
wt.commit('two', rev_id=r2, allow_pointless=False)
308
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
314
wt.commit('three', rev_id=r3, allow_pointless=False)
317
self.check_tree_shape(wt,
318
['a/', 'a/hello', 'a/b/'])
319
self.check_tree_shape(b.repository.revision_tree(r3),
320
['a/', 'a/hello', 'a/b/'])
324
wt.move(['a/hello'], 'a/b')
326
wt.commit('four', rev_id=r4, allow_pointless=False)
329
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
333
inv = b.repository.get_inventory(r4)
334
eq(inv.get_entry(b'hello-id').revision, r4)
335
eq(inv.get_entry(b'a-id').revision, r1)
336
eq(inv.get_entry(b'b-id').revision, r3)
338
def test_removed_commit(self):
339
"""Commit with a removed file"""
340
wt = self.make_branch_and_tree('.')
342
with open('hello', 'w') as f: f.write('hello world')
343
wt.add(['hello'], [b'hello-id'])
344
wt.commit(message='add hello')
346
wt.commit('removed hello', rev_id=b'rev2')
348
tree = b.repository.revision_tree(b'rev2')
349
self.assertFalse(tree.has_id(b'hello-id'))
351
def test_committed_ancestry(self):
352
"""Test commit appends revisions to ancestry."""
353
wt = self.make_branch_and_tree('.')
357
with open('hello', 'w') as f: f.write((str(i) * 4) + '\n')
359
wt.add(['hello'], [b'hello-id'])
360
rev_id = 'test@rev-%d' % (i+1)
361
rev_ids.append(rev_id)
362
wt.commit(message='rev %d' % (i+1),
365
self.assertThat(rev_ids[:i+1],
366
MatchesAncestry(b.repository, rev_ids[i]))
368
def test_commit_new_subdir_child_selective(self):
369
wt = self.make_branch_and_tree('.')
371
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
372
wt.add(['dir', 'dir/file1', 'dir/file2'],
373
[b'dirid', b'file1id', b'file2id'])
374
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id=b'1')
375
inv = b.repository.get_inventory(b'1')
376
self.assertEqual(b'1', inv.get_entry(b'dirid').revision)
377
self.assertEqual(b'1', inv.get_entry(b'file1id').revision)
378
# FIXME: This should raise a KeyError I think, rbc20051006
379
self.assertRaises(BzrError, inv.get_entry, b'file2id')
381
def test_strict_commit(self):
382
"""Try and commit with unknown files and strict = True, should fail."""
383
from ..errors import StrictCommitFailed
384
wt = self.make_branch_and_tree('.')
386
with open('hello', 'w') as f: f.write('hello world')
388
with open('goodbye', 'w') as f: f.write('goodbye cruel world!')
389
self.assertRaises(StrictCommitFailed, wt.commit,
390
message='add hello but not goodbye', strict=True)
392
def test_strict_commit_without_unknowns(self):
393
"""Try and commit with no unknown files and strict = True,
395
wt = self.make_branch_and_tree('.')
397
with open('hello', 'w') as f: f.write('hello world')
399
wt.commit(message='add hello', strict=True)
401
def test_nonstrict_commit(self):
402
"""Try and commit with unknown files and strict = False, should work."""
403
wt = self.make_branch_and_tree('.')
405
with open('hello', 'w') as f: f.write('hello world')
407
with open('goodbye', 'w') as f: f.write('goodbye cruel world!')
408
wt.commit(message='add hello but not goodbye', strict=False)
410
def test_nonstrict_commit_without_unknowns(self):
411
"""Try and commit with no unknown files and strict = False,
413
wt = self.make_branch_and_tree('.')
415
with open('hello', 'w') as f: f.write('hello world')
417
wt.commit(message='add hello', strict=False)
419
def test_signed_commit(self):
421
import breezy.commit as commit
422
oldstrategy = breezy.gpg.GPGStrategy
423
wt = self.make_branch_and_tree('.')
425
wt.commit("base", allow_pointless=True, rev_id=b'A')
426
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
428
from ..testament import Testament
429
# monkey patch gpg signing mechanism
430
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
431
conf = config.MemoryStack(b'''
432
create_signatures=always
434
commit.Commit(config_stack=conf).commit(
435
message="base", allow_pointless=True, rev_id=b'B',
438
return breezy.gpg.LoopbackGPGStrategy(None).sign(
439
text, breezy.gpg.MODE_CLEAR)
440
self.assertEqual(sign(Testament.from_revision(branch.repository,
441
'B').as_short_text()),
442
branch.repository.get_signature_text('B'))
444
breezy.gpg.GPGStrategy = oldstrategy
446
def test_commit_failed_signature(self):
448
import breezy.commit as commit
449
oldstrategy = breezy.gpg.GPGStrategy
450
wt = self.make_branch_and_tree('.')
452
wt.commit("base", allow_pointless=True, rev_id=b'A')
453
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
455
# monkey patch gpg signing mechanism
456
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
457
conf = config.MemoryStack(b'''
458
create_signatures=always
460
self.assertRaises(breezy.gpg.SigningFailed,
461
commit.Commit(config_stack=conf).commit,
463
allow_pointless=True,
466
branch = Branch.open(self.get_url('.'))
467
self.assertEqual(branch.last_revision(), b'A')
468
self.assertFalse(branch.repository.has_revision(b'B'))
470
breezy.gpg.GPGStrategy = oldstrategy
472
def test_commit_invokes_hooks(self):
473
import breezy.commit as commit
474
wt = self.make_branch_and_tree('.')
477
def called(branch, rev_id):
478
calls.append('called')
479
breezy.ahook = called
481
conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
482
commit.Commit(config_stack=conf).commit(
483
message = "base", allow_pointless=True, rev_id=b'A',
485
self.assertEqual(['called', 'called'], calls)
489
def test_commit_object_doesnt_set_nick(self):
490
# using the Commit object directly does not set the branch nick.
491
wt = self.make_branch_and_tree('.')
493
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
494
self.assertEqual(wt.branch.revno(), 1)
496
wt.branch.repository.get_revision(
497
wt.branch.last_revision()).properties)
499
def test_safe_master_lock(self):
501
master = BzrDirMetaFormat1().initialize('master')
502
master.create_repository()
503
master_branch = master.create_branch()
504
master.create_workingtree()
505
bound = master.sprout('bound')
506
wt = bound.open_workingtree()
507
wt.branch.set_bound_location(os.path.realpath('master'))
508
master_branch.lock_write()
510
self.assertRaises(LockContention, wt.commit, 'silly')
512
master_branch.unlock()
514
def test_commit_bound_merge(self):
515
# see bug #43959; commit of a merge in a bound branch fails to push
516
# the new commit into the master
517
master_branch = self.make_branch('master')
518
bound_tree = self.make_branch_and_tree('bound')
519
bound_tree.branch.bind(master_branch)
521
self.build_tree_contents([('bound/content_file', b'initial contents\n')])
522
bound_tree.add(['content_file'])
523
bound_tree.commit(message='woo!')
525
other_bzrdir = master_branch.controldir.sprout('other')
526
other_tree = other_bzrdir.open_workingtree()
528
# do a commit to the other branch changing the content file so
529
# that our commit after merging will have a merged revision in the
530
# content file history.
531
self.build_tree_contents([('other/content_file', b'change in other\n')])
532
other_tree.commit('change in other')
534
# do a merge into the bound branch from other, and then change the
535
# content file locally to force a new revision (rather than using the
536
# revision from other). This forces extra processing in commit.
537
bound_tree.merge_from_branch(other_tree.branch)
538
self.build_tree_contents([('bound/content_file', b'change in bound\n')])
540
# before #34959 was fixed, this failed with 'revision not present in
541
# weave' when trying to implicitly push from the bound branch to the master
542
bound_tree.commit(message='commit of merge in bound tree')
544
def test_commit_reporting_after_merge(self):
545
# when doing a commit of a merge, the reporter needs to still
546
# be called for each item that is added/removed/deleted.
547
this_tree = self.make_branch_and_tree('this')
548
# we need a bunch of files and dirs, to perform one action on each.
551
'this/dirtoreparent/',
554
'this/filetoreparent',
571
this_tree.commit('create_files')
572
other_dir = this_tree.controldir.sprout('other')
573
other_tree = other_dir.open_workingtree()
574
other_tree.lock_write()
575
# perform the needed actions on the files and dirs.
577
other_tree.rename_one('dirtorename', 'renameddir')
578
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
579
other_tree.rename_one('filetorename', 'renamedfile')
580
other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
581
other_tree.remove(['dirtoremove', 'filetoremove'])
582
self.build_tree_contents([
584
('other/filetomodify', b'new content'),
585
('other/newfile', b'new file content')])
586
other_tree.add('newfile')
587
other_tree.add('newdir/')
588
other_tree.commit('modify all sample files and dirs.')
591
this_tree.merge_from_branch(other_tree.branch)
592
reporter = CapturingReporter()
593
this_tree.commit('do the commit', reporter=reporter)
595
('change', 'modified', 'filetomodify'),
596
('change', 'added', 'newdir'),
597
('change', 'added', 'newfile'),
598
('renamed', 'renamed', 'dirtorename', 'renameddir'),
599
('renamed', 'renamed', 'filetorename', 'renamedfile'),
600
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
601
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
602
('deleted', 'dirtoremove'),
603
('deleted', 'filetoremove'),
605
result = set(reporter.calls)
606
missing = expected - result
607
new = result - expected
608
self.assertEqual((set(), set()), (missing, new))
610
def test_commit_removals_respects_filespec(self):
611
"""Commit respects the specified_files for removals."""
612
tree = self.make_branch_and_tree('.')
613
self.build_tree(['a', 'b'])
615
tree.commit('added a, b')
616
tree.remove(['a', 'b'])
617
tree.commit('removed a', specific_files='a')
618
basis = tree.basis_tree()
619
with tree.lock_read():
620
self.assertFalse(basis.is_versioned('a'))
621
self.assertTrue(basis.is_versioned('b'))
623
def test_commit_saves_1ms_timestamp(self):
624
"""Passing in a timestamp is saved with 1ms resolution"""
625
tree = self.make_branch_and_tree('.')
626
self.build_tree(['a'])
628
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
631
rev = tree.branch.repository.get_revision(b'a1')
632
self.assertEqual(1153248633.419, rev.timestamp)
634
def test_commit_has_1ms_resolution(self):
635
"""Allowing commit to generate the timestamp also has 1ms resolution"""
636
tree = self.make_branch_and_tree('.')
637
self.build_tree(['a'])
639
tree.commit('added a', rev_id=b'a1')
641
rev = tree.branch.repository.get_revision(b'a1')
642
timestamp = rev.timestamp
643
timestamp_1ms = round(timestamp, 3)
644
self.assertEqual(timestamp_1ms, timestamp)
646
def assertBasisTreeKind(self, kind, tree, path):
647
basis = tree.basis_tree()
650
self.assertEqual(kind, basis.kind(path))
654
def test_commit_kind_changes(self):
655
self.requireFeature(SymlinkFeature)
656
tree = self.make_branch_and_tree('.')
657
os.symlink('target', 'name')
658
tree.add('name', b'a-file-id')
659
tree.commit('Added a symlink')
660
self.assertBasisTreeKind('symlink', tree, 'name')
663
self.build_tree(['name'])
664
tree.commit('Changed symlink to file')
665
self.assertBasisTreeKind('file', tree, 'name')
668
os.symlink('target', 'name')
669
tree.commit('file to symlink')
670
self.assertBasisTreeKind('symlink', tree, 'name')
674
tree.commit('symlink to directory')
675
self.assertBasisTreeKind('directory', tree, 'name')
678
os.symlink('target', 'name')
679
tree.commit('directory to symlink')
680
self.assertBasisTreeKind('symlink', tree, 'name')
682
# prepare for directory <-> file tests
685
tree.commit('symlink to directory')
686
self.assertBasisTreeKind('directory', tree, 'name')
689
self.build_tree(['name'])
690
tree.commit('Changed directory to file')
691
self.assertBasisTreeKind('file', tree, 'name')
695
tree.commit('file to directory')
696
self.assertBasisTreeKind('directory', tree, 'name')
698
def test_commit_unversioned_specified(self):
699
"""Commit should raise if specified files isn't in basis or worktree"""
700
tree = self.make_branch_and_tree('.')
701
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
702
'message', specific_files=['bogus'])
704
class Callback(object):
706
def __init__(self, message, testcase):
708
self.message = message
709
self.testcase = testcase
711
def __call__(self, commit_obj):
713
self.testcase.assertTrue(isinstance(commit_obj, Commit))
716
def test_commit_callback(self):
717
"""Commit should invoke a callback to get the message"""
719
tree = self.make_branch_and_tree('.')
722
except Exception as e:
723
self.assertTrue(isinstance(e, BzrError))
724
self.assertEqual('The message or message_callback keyword'
725
' parameter is required for commit().', str(e))
727
self.fail('exception not raised')
728
cb = self.Callback(u'commit 1', self)
729
tree.commit(message_callback=cb)
730
self.assertTrue(cb.called)
731
repository = tree.branch.repository
732
message = repository.get_revision(tree.last_revision()).message
733
self.assertEqual('commit 1', message)
735
def test_no_callback_pointless(self):
736
"""Callback should not be invoked for pointless commit"""
737
tree = self.make_branch_and_tree('.')
738
cb = self.Callback(u'commit 2', self)
739
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
740
allow_pointless=False)
741
self.assertFalse(cb.called)
743
def test_no_callback_netfailure(self):
744
"""Callback should not be invoked if connectivity fails"""
745
tree = self.make_branch_and_tree('.')
746
cb = self.Callback(u'commit 2', self)
747
repository = tree.branch.repository
748
# simulate network failure
749
def raise_(self, arg, arg2, arg3=None, arg4=None):
750
raise errors.NoSuchFile('foo')
751
repository.add_inventory = raise_
752
repository.add_inventory_by_delta = raise_
753
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
754
self.assertFalse(cb.called)
756
def test_selected_file_merge_commit(self):
757
"""Ensure the correct error is raised"""
758
tree = self.make_branch_and_tree('foo')
759
# pending merge would turn into a left parent
760
tree.commit('commit 1')
761
tree.add_parent_tree_id(b'example')
762
self.build_tree(['foo/bar', 'foo/baz'])
763
tree.add(['bar', 'baz'])
764
err = self.assertRaises(CannotCommitSelectedFileMerge,
765
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
766
self.assertEqual(['bar', 'baz'], err.files)
767
self.assertEqual('Selected-file commit of merges is not supported'
768
' yet: files bar, baz', str(err))
770
def test_commit_ordering(self):
771
"""Test of corner-case commit ordering error"""
772
tree = self.make_branch_and_tree('.')
773
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
774
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
776
self.build_tree(['a/c/d/'])
778
tree.rename_one('a/z/x', 'a/c/d/x')
779
tree.commit('test', specific_files=['a/z/y'])
781
def test_commit_no_author(self):
782
"""The default kwarg author in MutableTree.commit should not add
783
the 'author' revision property.
785
tree = self.make_branch_and_tree('foo')
786
rev_id = tree.commit('commit 1')
787
rev = tree.branch.repository.get_revision(rev_id)
788
self.assertFalse('author' in rev.properties)
789
self.assertFalse('authors' in rev.properties)
791
def test_commit_author(self):
792
"""Passing a non-empty authors kwarg to MutableTree.commit should add
793
the 'author' revision property.
795
tree = self.make_branch_and_tree('foo')
796
rev_id = tree.commit(
798
authors=['John Doe <jdoe@example.com>'])
799
rev = tree.branch.repository.get_revision(rev_id)
800
self.assertEqual('John Doe <jdoe@example.com>',
801
rev.properties['authors'])
802
self.assertFalse('author' in rev.properties)
804
def test_commit_empty_authors_list(self):
805
"""Passing an empty list to authors shouldn't add the property."""
806
tree = self.make_branch_and_tree('foo')
807
rev_id = tree.commit('commit 1', authors=[])
808
rev = tree.branch.repository.get_revision(rev_id)
809
self.assertFalse('author' in rev.properties)
810
self.assertFalse('authors' in rev.properties)
812
def test_multiple_authors(self):
813
tree = self.make_branch_and_tree('foo')
814
rev_id = tree.commit('commit 1',
815
authors=['John Doe <jdoe@example.com>',
816
'Jane Rey <jrey@example.com>'])
817
rev = tree.branch.repository.get_revision(rev_id)
818
self.assertEqual('John Doe <jdoe@example.com>\n'
819
'Jane Rey <jrey@example.com>', rev.properties['authors'])
820
self.assertFalse('author' in rev.properties)
822
def test_author_with_newline_rejected(self):
823
tree = self.make_branch_and_tree('foo')
824
self.assertRaises(AssertionError, tree.commit, 'commit 1',
825
authors=['John\nDoe <jdoe@example.com>'])
827
def test_commit_with_checkout_and_branch_sharing_repo(self):
828
repo = self.make_repository('repo', shared=True)
829
# make_branch_and_tree ignores shared repos
830
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
831
tree2 = branch.create_checkout('repo/tree2')
832
tree2.commit('message', rev_id=b'rev1')
833
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
836
class FilterExcludedTests(TestCase):
838
def test_add_file_not_excluded(self):
840
('fid', (None, 'newpath'),
841
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
842
('file', 'file'), (True, True))]
843
self.assertEqual(changes, list(filter_excluded(changes, ['otherpath'])))
845
def test_add_file_excluded(self):
847
('fid', (None, 'newpath'),
848
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
849
('file', 'file'), (True, True))]
850
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
852
def test_delete_file_excluded(self):
854
('fid', ('somepath', None),
855
0, (False, None), ('pid', None), ('newpath', None),
856
('file', None), (True, None))]
857
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
859
def test_move_from_or_to_excluded(self):
861
('fid', ('oldpath', 'newpath'),
862
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
863
('file', 'file'), (True, True))]
864
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
865
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))