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
19
from io import BytesIO
28
from ..branch import Branch
29
from ..bzr.bzrdir import BzrDirMetaFormat1
30
from ..commit import (
31
CannotCommitSelectedFileMerge,
37
from ..errors import (
41
from ..bzr.inventorytree import InventoryTreeChange
44
TestCaseWithTransport,
47
from .features import (
50
from .matchers import MatchesAncestry
53
# TODO: Test commit with some added, and added-but-missing files
55
class MustSignConfig(config.MemoryStack):
58
super(MustSignConfig, self).__init__(b'''
59
create_signatures=always
63
class CapturingReporter(NullCommitReporter):
64
"""This reporter captures the calls made to it for evaluation later."""
67
# a list of the calls this received
70
def snapshot_change(self, change, path):
71
self.calls.append(('change', change, path))
73
def deleted(self, file_id):
74
self.calls.append(('deleted', file_id))
76
def missing(self, path):
77
self.calls.append(('missing', path))
79
def renamed(self, change, old_path, new_path):
80
self.calls.append(('renamed', change, old_path, new_path))
86
class TestCommit(TestCaseWithTransport):
88
def test_simple_commit(self):
89
"""Commit and check two versions of a single file."""
90
wt = self.make_branch_and_tree('.')
92
with open('hello', 'w') as f:
93
f.write('hello world')
95
rev1 = wt.commit(message='add hello')
97
with open('hello', 'w') as f:
99
rev2 = wt.commit(message='commit 2')
101
eq = self.assertEqual
103
rev = b.repository.get_revision(rev1)
104
eq(rev.message, 'add hello')
106
tree1 = b.repository.revision_tree(rev1)
108
text = tree1.get_file_text('hello')
110
self.assertEqual(b'hello world', text)
112
tree2 = b.repository.revision_tree(rev2)
114
text = tree2.get_file_text('hello')
116
self.assertEqual(b'version 2', text)
118
def test_commit_lossy_native(self):
119
"""Attempt a lossy commit to a native branch."""
120
wt = self.make_branch_and_tree('.')
122
with open('hello', 'w') as f:
123
f.write('hello world')
125
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
126
self.assertEqual(b'revid', revid)
128
def test_commit_lossy_foreign(self):
129
"""Attempt a lossy commit to a foreign branch."""
130
test_foreign.register_dummy_foreign_for_test(self)
131
wt = self.make_branch_and_tree('.',
132
format=test_foreign.DummyForeignVcsDirFormat())
134
with open('hello', 'w') as f:
135
f.write('hello world')
137
revid = wt.commit(message='add hello', lossy=True,
138
timestamp=1302659388, timezone=0)
139
self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
141
def test_commit_bound_lossy_foreign(self):
142
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
143
test_foreign.register_dummy_foreign_for_test(self)
144
foreign_branch = self.make_branch('foreign',
145
format=test_foreign.DummyForeignVcsDirFormat())
146
wt = foreign_branch.create_checkout("local")
148
with open('local/hello', 'w') as f:
149
f.write('hello world')
151
revid = wt.commit(message='add hello', lossy=True,
152
timestamp=1302659388, timezone=0)
153
self.assertEqual(b'dummy-v1:1302659388-0-0', revid)
154
self.assertEqual(b'dummy-v1:1302659388-0-0',
155
foreign_branch.last_revision())
156
self.assertEqual(b'dummy-v1:1302659388-0-0',
157
wt.branch.last_revision())
159
def test_missing_commit(self):
160
"""Test a commit with a missing file"""
161
wt = self.make_branch_and_tree('.')
163
with open('hello', 'w') as f:
164
f.write('hello world')
165
wt.add(['hello'], [b'hello-id'])
166
wt.commit(message='add hello')
169
reporter = CapturingReporter()
170
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
172
[('missing', u'hello'), ('deleted', u'hello')],
175
tree = b.repository.revision_tree(b'rev2')
176
self.assertFalse(tree.has_filename('hello'))
178
def test_partial_commit_move(self):
179
"""Test a partial commit where a file was renamed but not committed.
181
https://bugs.launchpad.net/bzr/+bug/83039
183
If not handled properly, commit will try to snapshot
184
dialog.py with olive/ as a parent, while
185
olive/ has not been snapshotted yet.
187
wt = self.make_branch_and_tree('.')
189
self.build_tree(['annotate/', 'annotate/foo.py',
190
'olive/', 'olive/dialog.py'
192
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
193
wt.commit(message='add files')
194
wt.rename_one("olive/dialog.py", "aaa")
195
self.build_tree_contents([('annotate/foo.py', b'modified\n')])
196
wt.commit('renamed hello', specific_files=["annotate"])
198
def test_pointless_commit(self):
199
"""Commit refuses unless there are changes or it's forced."""
200
wt = self.make_branch_and_tree('.')
202
with open('hello', 'w') as f:
205
wt.commit(message='add hello')
206
self.assertEqual(b.revno(), 1)
207
self.assertRaises(PointlessCommit,
210
allow_pointless=False)
211
self.assertEqual(b.revno(), 1)
213
def test_commit_empty(self):
214
"""Commiting an empty tree works."""
215
wt = self.make_branch_and_tree('.')
217
wt.commit(message='empty tree', allow_pointless=True)
218
self.assertRaises(PointlessCommit,
220
message='empty tree',
221
allow_pointless=False)
222
wt.commit(message='empty tree', allow_pointless=True)
223
self.assertEqual(b.revno(), 2)
225
def test_selective_delete(self):
226
"""Selective commit in tree with deletions"""
227
wt = self.make_branch_and_tree('.')
229
with open('hello', 'w') as f:
231
with open('buongia', 'w') as f:
233
wt.add(['hello', 'buongia'],
234
[b'hello-id', b'buongia-id'])
235
wt.commit(message='add files',
236
rev_id=b'test@rev-1')
239
with open('buongia', 'w') as f:
241
wt.commit(message='update text',
242
specific_files=['buongia'],
243
allow_pointless=False,
244
rev_id=b'test@rev-2')
246
wt.commit(message='remove hello',
247
specific_files=['hello'],
248
allow_pointless=False,
249
rev_id=b'test@rev-3')
251
eq = self.assertEqual
254
tree2 = b.repository.revision_tree(b'test@rev-2')
256
self.addCleanup(tree2.unlock)
257
self.assertTrue(tree2.has_filename('hello'))
258
self.assertEqual(tree2.get_file_text('hello'), b'hello')
259
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
261
tree3 = b.repository.revision_tree(b'test@rev-3')
263
self.addCleanup(tree3.unlock)
264
self.assertFalse(tree3.has_filename('hello'))
265
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
267
def test_commit_rename(self):
268
"""Test commit of a revision where a file is renamed."""
269
tree = self.make_branch_and_tree('.')
271
self.build_tree(['hello'], line_endings='binary')
272
tree.add(['hello'], [b'hello-id'])
273
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
275
tree.rename_one('hello', 'fruity')
276
tree.commit(message='renamed', rev_id=b'test@rev-2',
277
allow_pointless=False)
279
eq = self.assertEqual
280
tree1 = b.repository.revision_tree(b'test@rev-1')
282
self.addCleanup(tree1.unlock)
283
eq(tree1.id2path(b'hello-id'), 'hello')
284
eq(tree1.get_file_text('hello'), b'contents of hello\n')
285
self.assertFalse(tree1.has_filename('fruity'))
286
self.check_tree_shape(tree1, ['hello'])
287
eq(tree1.get_file_revision('hello'), b'test@rev-1')
289
tree2 = b.repository.revision_tree(b'test@rev-2')
291
self.addCleanup(tree2.unlock)
292
eq(tree2.id2path(b'hello-id'), 'fruity')
293
eq(tree2.get_file_text('fruity'), b'contents of hello\n')
294
self.check_tree_shape(tree2, ['fruity'])
295
eq(tree2.get_file_revision('fruity'), b'test@rev-2')
297
def test_reused_rev_id(self):
298
"""Test that a revision id cannot be reused in a branch"""
299
wt = self.make_branch_and_tree('.')
301
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
302
self.assertRaises(Exception,
305
rev_id=b'test@rev-1',
306
allow_pointless=True)
308
def test_commit_move(self):
309
"""Test commit of revisions with moved files and directories"""
310
eq = self.assertEqual
311
wt = self.make_branch_and_tree('.')
314
self.build_tree(['hello', 'a/', 'b/'])
315
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
316
wt.commit('initial', rev_id=r1, allow_pointless=False)
317
wt.move(['hello'], 'a')
319
wt.commit('two', rev_id=r2, allow_pointless=False)
322
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
328
wt.commit('three', rev_id=r3, allow_pointless=False)
331
self.check_tree_shape(wt,
332
['a/', 'a/hello', 'a/b/'])
333
self.check_tree_shape(b.repository.revision_tree(r3),
334
['a/', 'a/hello', 'a/b/'])
338
wt.move(['a/hello'], 'a/b')
340
wt.commit('four', rev_id=r4, allow_pointless=False)
343
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
347
inv = b.repository.get_inventory(r4)
348
eq(inv.get_entry(b'hello-id').revision, r4)
349
eq(inv.get_entry(b'a-id').revision, r1)
350
eq(inv.get_entry(b'b-id').revision, r3)
352
def test_removed_commit(self):
353
"""Commit with a removed file"""
354
wt = self.make_branch_and_tree('.')
356
with open('hello', 'w') as f:
357
f.write('hello world')
358
wt.add(['hello'], [b'hello-id'])
359
wt.commit(message='add hello')
361
wt.commit('removed hello', rev_id=b'rev2')
363
tree = b.repository.revision_tree(b'rev2')
364
self.assertFalse(tree.has_filename('hello'))
366
def test_committed_ancestry(self):
367
"""Test commit appends revisions to ancestry."""
368
wt = self.make_branch_and_tree('.')
372
with open('hello', 'w') as f:
373
f.write((str(i) * 4) + '\n')
375
wt.add(['hello'], [b'hello-id'])
376
rev_id = b'test@rev-%d' % (i + 1)
377
rev_ids.append(rev_id)
378
wt.commit(message='rev %d' % (i + 1),
381
self.assertThat(rev_ids[:i + 1],
382
MatchesAncestry(b.repository, rev_ids[i]))
384
def test_commit_new_subdir_child_selective(self):
385
wt = self.make_branch_and_tree('.')
387
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
388
wt.add(['dir', 'dir/file1', 'dir/file2'],
389
[b'dirid', b'file1id', b'file2id'])
390
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id=b'1')
391
inv = b.repository.get_inventory(b'1')
392
self.assertEqual(b'1', inv.get_entry(b'dirid').revision)
393
self.assertEqual(b'1', inv.get_entry(b'file1id').revision)
394
# FIXME: This should raise a KeyError I think, rbc20051006
395
self.assertRaises(BzrError, inv.get_entry, b'file2id')
397
def test_strict_commit(self):
398
"""Try and commit with unknown files and strict = True, should fail."""
399
from ..errors import StrictCommitFailed
400
wt = self.make_branch_and_tree('.')
402
with open('hello', 'w') as f:
403
f.write('hello world')
405
with open('goodbye', 'w') as f:
406
f.write('goodbye cruel world!')
407
self.assertRaises(StrictCommitFailed, wt.commit,
408
message='add hello but not goodbye', strict=True)
410
def test_strict_commit_without_unknowns(self):
411
"""Try and commit with no unknown files and strict = True,
413
wt = self.make_branch_and_tree('.')
415
with open('hello', 'w') as f:
416
f.write('hello world')
418
wt.commit(message='add hello', strict=True)
420
def test_nonstrict_commit(self):
421
"""Try and commit with unknown files and strict = False, should work."""
422
wt = self.make_branch_and_tree('.')
424
with open('hello', 'w') as f:
425
f.write('hello world')
427
with open('goodbye', 'w') as f:
428
f.write('goodbye cruel world!')
429
wt.commit(message='add hello but not goodbye', strict=False)
431
def test_nonstrict_commit_without_unknowns(self):
432
"""Try and commit with no unknown files and strict = False,
434
wt = self.make_branch_and_tree('.')
436
with open('hello', 'w') as f:
437
f.write('hello world')
439
wt.commit(message='add hello', strict=False)
441
def test_signed_commit(self):
443
import breezy.commit as commit
444
oldstrategy = breezy.gpg.GPGStrategy
445
wt = self.make_branch_and_tree('.')
447
wt.commit("base", allow_pointless=True, rev_id=b'A')
448
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
450
from ..bzr.testament import Testament
451
# monkey patch gpg signing mechanism
452
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
453
conf = config.MemoryStack(b'''
454
create_signatures=always
456
commit.Commit(config_stack=conf).commit(
457
message="base", allow_pointless=True, rev_id=b'B',
461
return breezy.gpg.LoopbackGPGStrategy(None).sign(
462
text, breezy.gpg.MODE_CLEAR)
463
self.assertEqual(sign(Testament.from_revision(branch.repository,
464
b'B').as_short_text()),
465
branch.repository.get_signature_text(b'B'))
467
breezy.gpg.GPGStrategy = oldstrategy
469
def test_commit_failed_signature(self):
471
import breezy.commit as commit
472
oldstrategy = breezy.gpg.GPGStrategy
473
wt = self.make_branch_and_tree('.')
475
wt.commit("base", allow_pointless=True, rev_id=b'A')
476
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
478
# monkey patch gpg signing mechanism
479
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
480
conf = config.MemoryStack(b'''
481
create_signatures=always
483
self.assertRaises(breezy.gpg.SigningFailed,
484
commit.Commit(config_stack=conf).commit,
486
allow_pointless=True,
489
branch = Branch.open(self.get_url('.'))
490
self.assertEqual(branch.last_revision(), b'A')
491
self.assertFalse(branch.repository.has_revision(b'B'))
493
breezy.gpg.GPGStrategy = oldstrategy
495
def test_commit_invokes_hooks(self):
496
import breezy.commit as commit
497
wt = self.make_branch_and_tree('.')
501
def called(branch, rev_id):
502
calls.append('called')
503
breezy.ahook = called
505
conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
506
commit.Commit(config_stack=conf).commit(
507
message="base", allow_pointless=True, rev_id=b'A',
509
self.assertEqual(['called', 'called'], calls)
513
def test_commit_object_doesnt_set_nick(self):
514
# using the Commit object directly does not set the branch nick.
515
wt = self.make_branch_and_tree('.')
517
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
518
self.assertEqual(wt.branch.revno(), 1)
520
wt.branch.repository.get_revision(
521
wt.branch.last_revision()).properties)
523
def test_safe_master_lock(self):
525
master = BzrDirMetaFormat1().initialize('master')
526
master.create_repository()
527
master_branch = master.create_branch()
528
master.create_workingtree()
529
bound = master.sprout('bound')
530
wt = bound.open_workingtree()
531
wt.branch.set_bound_location(os.path.realpath('master'))
532
with master_branch.lock_write():
533
self.assertRaises(LockContention, wt.commit, 'silly')
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_unsupported_symlink_commit(self):
680
self.requireFeature(SymlinkFeature)
681
tree = self.make_branch_and_tree('.')
682
self.build_tree(['hello'])
684
tree.commit('added hello', rev_id=b'hello_id')
685
os.symlink('hello', 'foo')
687
tree.commit('added foo', rev_id=b'foo_id')
689
trace.push_log_file(log)
690
os_symlink = getattr(os, 'symlink', None)
693
# At this point as bzr thinks symlinks are not supported
694
# we should get a warning about symlink foo and bzr should
695
# not think its removed.
697
self.build_tree(['world'])
699
tree.commit('added world', rev_id=b'world_id')
702
os.symlink = os_symlink
703
self.assertContainsRe(
705
b'Ignoring "foo" as symlinks are not '
706
b'supported on this filesystem\\.')
708
def test_commit_kind_changes(self):
709
self.requireFeature(SymlinkFeature)
710
tree = self.make_branch_and_tree('.')
711
os.symlink('target', 'name')
712
tree.add('name', b'a-file-id')
713
tree.commit('Added a symlink')
714
self.assertBasisTreeKind('symlink', tree, 'name')
717
self.build_tree(['name'])
718
tree.commit('Changed symlink to file')
719
self.assertBasisTreeKind('file', tree, 'name')
722
os.symlink('target', 'name')
723
tree.commit('file to symlink')
724
self.assertBasisTreeKind('symlink', tree, 'name')
728
tree.commit('symlink to directory')
729
self.assertBasisTreeKind('directory', tree, 'name')
732
os.symlink('target', 'name')
733
tree.commit('directory to symlink')
734
self.assertBasisTreeKind('symlink', tree, 'name')
736
# prepare for directory <-> file tests
739
tree.commit('symlink to directory')
740
self.assertBasisTreeKind('directory', tree, 'name')
743
self.build_tree(['name'])
744
tree.commit('Changed directory to file')
745
self.assertBasisTreeKind('file', tree, 'name')
749
tree.commit('file to directory')
750
self.assertBasisTreeKind('directory', tree, 'name')
752
def test_commit_unversioned_specified(self):
753
"""Commit should raise if specified files isn't in basis or worktree"""
754
tree = self.make_branch_and_tree('.')
755
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
756
'message', specific_files=['bogus'])
758
class Callback(object):
760
def __init__(self, message, testcase):
762
self.message = message
763
self.testcase = testcase
765
def __call__(self, commit_obj):
767
self.testcase.assertTrue(isinstance(commit_obj, Commit))
770
def test_commit_callback(self):
771
"""Commit should invoke a callback to get the message"""
773
tree = self.make_branch_and_tree('.')
776
except Exception as e:
777
self.assertTrue(isinstance(e, BzrError))
778
self.assertEqual('The message or message_callback keyword'
779
' parameter is required for commit().', str(e))
781
self.fail('exception not raised')
782
cb = self.Callback(u'commit 1', self)
783
tree.commit(message_callback=cb)
784
self.assertTrue(cb.called)
785
repository = tree.branch.repository
786
message = repository.get_revision(tree.last_revision()).message
787
self.assertEqual('commit 1', message)
789
def test_no_callback_pointless(self):
790
"""Callback should not be invoked for pointless commit"""
791
tree = self.make_branch_and_tree('.')
792
cb = self.Callback(u'commit 2', self)
793
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
794
allow_pointless=False)
795
self.assertFalse(cb.called)
797
def test_no_callback_netfailure(self):
798
"""Callback should not be invoked if connectivity fails"""
799
tree = self.make_branch_and_tree('.')
800
cb = self.Callback(u'commit 2', self)
801
repository = tree.branch.repository
802
# simulate network failure
804
def raise_(self, arg, arg2, arg3=None, arg4=None):
805
raise errors.NoSuchFile('foo')
806
repository.add_inventory = raise_
807
repository.add_inventory_by_delta = raise_
808
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
809
self.assertFalse(cb.called)
811
def test_selected_file_merge_commit(self):
812
"""Ensure the correct error is raised"""
813
tree = self.make_branch_and_tree('foo')
814
# pending merge would turn into a left parent
815
tree.commit('commit 1')
816
tree.add_parent_tree_id(b'example')
817
self.build_tree(['foo/bar', 'foo/baz'])
818
tree.add(['bar', 'baz'])
819
err = self.assertRaises(CannotCommitSelectedFileMerge,
820
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
821
self.assertEqual(['bar', 'baz'], err.files)
822
self.assertEqual('Selected-file commit of merges is not supported'
823
' yet: files bar, baz', str(err))
825
def test_commit_ordering(self):
826
"""Test of corner-case commit ordering error"""
827
tree = self.make_branch_and_tree('.')
828
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
829
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
831
self.build_tree(['a/c/d/'])
833
tree.rename_one('a/z/x', 'a/c/d/x')
834
tree.commit('test', specific_files=['a/z/y'])
836
def test_commit_no_author(self):
837
"""The default kwarg author in MutableTree.commit should not add
838
the 'author' revision property.
840
tree = self.make_branch_and_tree('foo')
841
rev_id = tree.commit('commit 1')
842
rev = tree.branch.repository.get_revision(rev_id)
843
self.assertFalse('author' in rev.properties)
844
self.assertFalse('authors' in rev.properties)
846
def test_commit_author(self):
847
"""Passing a non-empty authors kwarg to MutableTree.commit should add
848
the 'author' revision property.
850
tree = self.make_branch_and_tree('foo')
851
rev_id = tree.commit(
853
authors=['John Doe <jdoe@example.com>'])
854
rev = tree.branch.repository.get_revision(rev_id)
855
self.assertEqual('John Doe <jdoe@example.com>',
856
rev.properties['authors'])
857
self.assertFalse('author' in rev.properties)
859
def test_commit_empty_authors_list(self):
860
"""Passing an empty list to authors shouldn't add the property."""
861
tree = self.make_branch_and_tree('foo')
862
rev_id = tree.commit('commit 1', authors=[])
863
rev = tree.branch.repository.get_revision(rev_id)
864
self.assertFalse('author' in rev.properties)
865
self.assertFalse('authors' in rev.properties)
867
def test_multiple_authors(self):
868
tree = self.make_branch_and_tree('foo')
869
rev_id = tree.commit('commit 1',
870
authors=['John Doe <jdoe@example.com>',
871
'Jane Rey <jrey@example.com>'])
872
rev = tree.branch.repository.get_revision(rev_id)
873
self.assertEqual('John Doe <jdoe@example.com>\n'
874
'Jane Rey <jrey@example.com>', rev.properties['authors'])
875
self.assertFalse('author' in rev.properties)
877
def test_author_with_newline_rejected(self):
878
tree = self.make_branch_and_tree('foo')
879
self.assertRaises(AssertionError, tree.commit, 'commit 1',
880
authors=['John\nDoe <jdoe@example.com>'])
882
def test_commit_with_checkout_and_branch_sharing_repo(self):
883
repo = self.make_repository('repo', shared=True)
884
# make_branch_and_tree ignores shared repos
885
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
886
tree2 = branch.create_checkout('repo/tree2')
887
tree2.commit('message', rev_id=b'rev1')
888
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
891
class FilterExcludedTests(TestCase):
893
def test_add_file_not_excluded(self):
896
'fid', (None, 'newpath'),
897
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
898
('file', 'file'), (True, True))]
899
self.assertEqual(changes, list(
900
filter_excluded(changes, ['otherpath'])))
902
def test_add_file_excluded(self):
905
'fid', (None, 'newpath'),
906
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
907
('file', 'file'), (True, True))]
908
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
910
def test_delete_file_excluded(self):
913
'fid', ('somepath', None),
914
0, (False, None), ('pid', None), ('newpath', None),
915
('file', None), (True, None))]
916
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
918
def test_move_from_or_to_excluded(self):
921
'fid', ('oldpath', 'newpath'),
922
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
923
('file', 'file'), (True, True))]
924
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
925
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))