51
86
"""Commit and check two versions of a single file."""
52
87
wt = self.make_branch_and_tree('.')
54
file('hello', 'w').write('hello world')
89
with open('hello', 'w') as f: f.write('hello world')
56
wt.commit(message='add hello')
57
file_id = wt.path2id('hello')
59
file('hello', 'w').write('version 2')
60
wt.commit(message='commit 2')
62
eq = self.assertEquals
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')
64
rh = b.revision_history()
65
rev = b.repository.get_revision(rh[0])
98
rev = b.repository.get_revision(rev1)
66
99
eq(rev.message, 'add hello')
68
tree1 = b.repository.revision_tree(rh[0])
69
text = tree1.get_file_text(file_id)
70
eq(text, 'hello world')
72
tree2 = b.repository.revision_tree(rh[1])
73
eq(tree2.get_file_text(file_id), 'version 2')
75
def test_delete_commit(self):
76
"""Test a commit with a deleted file"""
77
wt = self.make_branch_and_tree('.')
79
file('hello', 'w').write('hello world')
101
tree1 = b.repository.revision_tree(rev1)
103
text = tree1.get_file_text('hello')
105
self.assertEqual('hello world', text)
107
tree2 = b.repository.revision_tree(rev2)
109
text = tree2.get_file_text('hello')
111
self.assertEqual('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='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')
80
156
wt.add(['hello'], ['hello-id'])
81
157
wt.commit(message='add hello')
83
159
os.remove('hello')
84
wt.commit('removed hello', rev_id='rev2')
160
reporter = CapturingReporter()
161
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
163
[('missing', u'hello'), ('deleted', u'hello')],
86
166
tree = b.repository.revision_tree('rev2')
87
167
self.assertFalse(tree.has_id('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', 'modified\n')])
187
wt.commit('renamed hello', specific_files=["annotate"])
89
189
def test_pointless_commit(self):
90
190
"""Commit refuses unless there are changes or it's forced."""
91
191
wt = self.make_branch_and_tree('.')
93
file('hello', 'w').write('hello')
193
with open('hello', 'w') as f: f.write('hello')
95
195
wt.commit(message='add hello')
96
self.assertEquals(b.revno(), 1)
196
self.assertEqual(b.revno(), 1)
97
197
self.assertRaises(PointlessCommit,
100
200
allow_pointless=False)
101
self.assertEquals(b.revno(), 1)
201
self.assertEqual(b.revno(), 1)
103
203
def test_commit_empty(self):
104
204
"""Commiting an empty tree works."""
105
205
wt = self.make_branch_and_tree('.')
300
413
wt = self.make_branch_and_tree('.')
302
file('hello', 'w').write('hello world')
415
with open('hello', 'w') as f: f.write('hello world')
304
417
wt.commit(message='add hello', strict=False)
306
419
def test_signed_commit(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
421
import breezy.commit as commit
422
oldstrategy = breezy.gpg.GPGStrategy
310
423
wt = self.make_branch_and_tree('.')
311
424
branch = wt.branch
312
425
wt.commit("base", allow_pointless=True, rev_id='A')
313
self.failIf(branch.repository.has_signature_for_revision_id('A'))
426
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
315
from bzrlib.testament import Testament
428
from ..testament import Testament
316
429
# monkey patch gpg signing mechanism
317
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
318
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
319
allow_pointless=True,
322
self.assertEqual(Testament.from_revision(branch.repository,
323
'B').as_short_text(),
430
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
431
conf = config.MemoryStack('''
432
create_signatures=always
434
commit.Commit(config_stack=conf).commit(
435
message="base", allow_pointless=True, rev_id='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()),
324
442
branch.repository.get_signature_text('B'))
326
bzrlib.gpg.GPGStrategy = oldstrategy
444
breezy.gpg.GPGStrategy = oldstrategy
328
446
def test_commit_failed_signature(self):
330
import bzrlib.commit as commit
331
oldstrategy = bzrlib.gpg.GPGStrategy
448
import breezy.commit as commit
449
oldstrategy = breezy.gpg.GPGStrategy
332
450
wt = self.make_branch_and_tree('.')
333
451
branch = wt.branch
334
452
wt.commit("base", allow_pointless=True, rev_id='A')
335
self.failIf(branch.repository.has_signature_for_revision_id('A'))
453
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
337
from bzrlib.testament import Testament
338
455
# monkey patch gpg signing mechanism
339
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
340
config = MustSignConfig(branch)
341
self.assertRaises(SigningFailed,
342
commit.Commit(config=config).commit,
456
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
457
conf = config.MemoryStack('''
458
create_signatures=always
460
self.assertRaises(breezy.gpg.SigningFailed,
461
commit.Commit(config_stack=conf).commit,
344
463
allow_pointless=True,
347
466
branch = Branch.open(self.get_url('.'))
348
self.assertEqual(branch.revision_history(), ['A'])
349
self.failIf(branch.repository.has_revision('B'))
467
self.assertEqual(branch.last_revision(), 'A')
468
self.assertFalse(branch.repository.has_revision('B'))
351
bzrlib.gpg.GPGStrategy = oldstrategy
470
breezy.gpg.GPGStrategy = oldstrategy
353
472
def test_commit_invokes_hooks(self):
354
import bzrlib.commit as commit
473
import breezy.commit as commit
355
474
wt = self.make_branch_and_tree('.')
356
475
branch = wt.branch
358
477
def called(branch, rev_id):
359
478
calls.append('called')
360
bzrlib.ahook = called
479
breezy.ahook = called
362
config = BranchWithHooks(branch)
363
commit.Commit(config=config).commit(
365
allow_pointless=True,
366
rev_id='A', working_tree = wt)
481
conf = config.MemoryStack('post_commit=breezy.ahook breezy.ahook')
482
commit.Commit(config_stack=conf).commit(
483
message = "base", allow_pointless=True, rev_id='A',
367
485
self.assertEqual(['called', 'called'], calls)
371
489
def test_commit_object_doesnt_set_nick(self):
372
490
# using the Commit object directly does not set the branch nick.
373
491
wt = self.make_branch_and_tree('.')
375
493
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
376
self.assertEquals(wt.branch.revno(), 1)
494
self.assertEqual(wt.branch.revno(), 1)
377
495
self.assertEqual({},
378
496
wt.branch.repository.get_revision(
379
497
wt.branch.last_revision()).properties)
392
510
self.assertRaises(LockContention, wt.commit, 'silly')
394
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', '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', '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', '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', 'new content'),
585
('other/newfile', '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('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='a1')
641
rev = tree.branch.repository.get_revision('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', '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('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='rev1')
833
self.assertTrue(tree2.branch.repository.has_revision('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'])))