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')
91
rev1 = wt.commit(message='add hello')
57
92
file_id = wt.path2id('hello')
59
file('hello', 'w').write('version 2')
60
wt.commit(message='commit 2')
94
with open('hello', 'w') as f: f.write('version 2')
95
rev2 = wt.commit(message='commit 2')
62
eq = self.assertEquals
64
rh = b.revision_history()
65
rev = b.repository.get_revision(rh[0])
99
rev = b.repository.get_revision(rev1)
66
100
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')
102
tree1 = b.repository.revision_tree(rev1)
104
text = tree1.get_file_text('hello')
106
self.assertEqual('hello world', text)
108
tree2 = b.repository.revision_tree(rev2)
110
text = tree2.get_file_text('hello')
112
self.assertEqual('version 2', text)
114
def test_commit_lossy_native(self):
115
"""Attempt a lossy commit to a native branch."""
116
wt = self.make_branch_and_tree('.')
118
with open('hello', 'w') as f: f.write('hello world')
120
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
121
self.assertEqual('revid', revid)
123
def test_commit_lossy_foreign(self):
124
"""Attempt a lossy commit to a foreign branch."""
125
test_foreign.register_dummy_foreign_for_test(self)
126
wt = self.make_branch_and_tree('.',
127
format=test_foreign.DummyForeignVcsDirFormat())
129
with open('hello', 'w') as f: f.write('hello world')
131
revid = wt.commit(message='add hello', lossy=True,
132
timestamp=1302659388, timezone=0)
133
self.assertEqual('dummy-v1:1302659388.0-0-UNKNOWN', revid)
135
def test_commit_bound_lossy_foreign(self):
136
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
137
test_foreign.register_dummy_foreign_for_test(self)
138
foreign_branch = self.make_branch('foreign',
139
format=test_foreign.DummyForeignVcsDirFormat())
140
wt = foreign_branch.create_checkout("local")
142
with open('local/hello', 'w') as f: f.write('hello world')
144
revid = wt.commit(message='add hello', lossy=True,
145
timestamp=1302659388, timezone=0)
146
self.assertEqual('dummy-v1:1302659388.0-0-0', revid)
147
self.assertEqual('dummy-v1:1302659388.0-0-0',
148
foreign_branch.last_revision())
149
self.assertEqual('dummy-v1:1302659388.0-0-0',
150
wt.branch.last_revision())
152
def test_missing_commit(self):
153
"""Test a commit with a missing file"""
154
wt = self.make_branch_and_tree('.')
156
with open('hello', 'w') as f: f.write('hello world')
80
157
wt.add(['hello'], ['hello-id'])
81
158
wt.commit(message='add hello')
83
160
os.remove('hello')
84
wt.commit('removed hello', rev_id='rev2')
161
reporter = CapturingReporter()
162
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
164
[('missing', u'hello'), ('deleted', u'hello')],
86
167
tree = b.repository.revision_tree('rev2')
87
168
self.assertFalse(tree.has_id('hello-id'))
170
def test_partial_commit_move(self):
171
"""Test a partial commit where a file was renamed but not committed.
173
https://bugs.launchpad.net/bzr/+bug/83039
175
If not handled properly, commit will try to snapshot
176
dialog.py with olive/ as a parent, while
177
olive/ has not been snapshotted yet.
179
wt = self.make_branch_and_tree('.')
181
self.build_tree(['annotate/', 'annotate/foo.py',
182
'olive/', 'olive/dialog.py'
184
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
185
wt.commit(message='add files')
186
wt.rename_one("olive/dialog.py", "aaa")
187
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
188
wt.commit('renamed hello', specific_files=["annotate"])
89
190
def test_pointless_commit(self):
90
191
"""Commit refuses unless there are changes or it's forced."""
91
192
wt = self.make_branch_and_tree('.')
93
file('hello', 'w').write('hello')
194
with open('hello', 'w') as f: f.write('hello')
95
196
wt.commit(message='add hello')
96
self.assertEquals(b.revno(), 1)
197
self.assertEqual(b.revno(), 1)
97
198
self.assertRaises(PointlessCommit,
100
201
allow_pointless=False)
101
self.assertEquals(b.revno(), 1)
202
self.assertEqual(b.revno(), 1)
103
204
def test_commit_empty(self):
104
205
"""Commiting an empty tree works."""
105
206
wt = self.make_branch_and_tree('.')
300
414
wt = self.make_branch_and_tree('.')
302
file('hello', 'w').write('hello world')
416
with open('hello', 'w') as f: f.write('hello world')
304
418
wt.commit(message='add hello', strict=False)
306
420
def test_signed_commit(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
422
import breezy.commit as commit
423
oldstrategy = breezy.gpg.GPGStrategy
310
424
wt = self.make_branch_and_tree('.')
311
425
branch = wt.branch
312
426
wt.commit("base", allow_pointless=True, rev_id='A')
313
self.failIf(branch.repository.has_signature_for_revision_id('A'))
427
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
315
from bzrlib.testament import Testament
429
from ..testament import Testament
316
430
# 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(),
431
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
432
conf = config.MemoryStack('''
433
create_signatures=always
435
commit.Commit(config_stack=conf).commit(
436
message="base", allow_pointless=True, rev_id='B',
439
return breezy.gpg.LoopbackGPGStrategy(None).sign(text)
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()
621
self.assertIs(None, basis.path2id('a'))
622
self.assertFalse(basis.path2id('b') is None)
626
def test_commit_saves_1ms_timestamp(self):
627
"""Passing in a timestamp is saved with 1ms resolution"""
628
tree = self.make_branch_and_tree('.')
629
self.build_tree(['a'])
631
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
634
rev = tree.branch.repository.get_revision('a1')
635
self.assertEqual(1153248633.419, rev.timestamp)
637
def test_commit_has_1ms_resolution(self):
638
"""Allowing commit to generate the timestamp also has 1ms resolution"""
639
tree = self.make_branch_and_tree('.')
640
self.build_tree(['a'])
642
tree.commit('added a', rev_id='a1')
644
rev = tree.branch.repository.get_revision('a1')
645
timestamp = rev.timestamp
646
timestamp_1ms = round(timestamp, 3)
647
self.assertEqual(timestamp_1ms, timestamp)
649
def assertBasisTreeKind(self, kind, tree, path):
650
basis = tree.basis_tree()
653
self.assertEqual(kind, basis.kind(path))
657
def test_commit_kind_changes(self):
658
self.requireFeature(SymlinkFeature)
659
tree = self.make_branch_and_tree('.')
660
os.symlink('target', 'name')
661
tree.add('name', 'a-file-id')
662
tree.commit('Added a symlink')
663
self.assertBasisTreeKind('symlink', tree, 'name')
666
self.build_tree(['name'])
667
tree.commit('Changed symlink to file')
668
self.assertBasisTreeKind('file', tree, 'name')
671
os.symlink('target', 'name')
672
tree.commit('file to symlink')
673
self.assertBasisTreeKind('symlink', tree, 'name')
677
tree.commit('symlink to directory')
678
self.assertBasisTreeKind('directory', tree, 'name')
681
os.symlink('target', 'name')
682
tree.commit('directory to symlink')
683
self.assertBasisTreeKind('symlink', tree, 'name')
685
# prepare for directory <-> file tests
688
tree.commit('symlink to directory')
689
self.assertBasisTreeKind('directory', tree, 'name')
692
self.build_tree(['name'])
693
tree.commit('Changed directory to file')
694
self.assertBasisTreeKind('file', tree, 'name')
698
tree.commit('file to directory')
699
self.assertBasisTreeKind('directory', tree, 'name')
701
def test_commit_unversioned_specified(self):
702
"""Commit should raise if specified files isn't in basis or worktree"""
703
tree = self.make_branch_and_tree('.')
704
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
705
'message', specific_files=['bogus'])
707
class Callback(object):
709
def __init__(self, message, testcase):
711
self.message = message
712
self.testcase = testcase
714
def __call__(self, commit_obj):
716
self.testcase.assertTrue(isinstance(commit_obj, Commit))
719
def test_commit_callback(self):
720
"""Commit should invoke a callback to get the message"""
722
tree = self.make_branch_and_tree('.')
725
except Exception as e:
726
self.assertTrue(isinstance(e, BzrError))
727
self.assertEqual('The message or message_callback keyword'
728
' parameter is required for commit().', str(e))
730
self.fail('exception not raised')
731
cb = self.Callback(u'commit 1', self)
732
tree.commit(message_callback=cb)
733
self.assertTrue(cb.called)
734
repository = tree.branch.repository
735
message = repository.get_revision(tree.last_revision()).message
736
self.assertEqual('commit 1', message)
738
def test_no_callback_pointless(self):
739
"""Callback should not be invoked for pointless commit"""
740
tree = self.make_branch_and_tree('.')
741
cb = self.Callback(u'commit 2', self)
742
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
743
allow_pointless=False)
744
self.assertFalse(cb.called)
746
def test_no_callback_netfailure(self):
747
"""Callback should not be invoked if connectivity fails"""
748
tree = self.make_branch_and_tree('.')
749
cb = self.Callback(u'commit 2', self)
750
repository = tree.branch.repository
751
# simulate network failure
752
def raise_(self, arg, arg2, arg3=None, arg4=None):
753
raise errors.NoSuchFile('foo')
754
repository.add_inventory = raise_
755
repository.add_inventory_by_delta = raise_
756
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
757
self.assertFalse(cb.called)
759
def test_selected_file_merge_commit(self):
760
"""Ensure the correct error is raised"""
761
tree = self.make_branch_and_tree('foo')
762
# pending merge would turn into a left parent
763
tree.commit('commit 1')
764
tree.add_parent_tree_id('example')
765
self.build_tree(['foo/bar', 'foo/baz'])
766
tree.add(['bar', 'baz'])
767
err = self.assertRaises(CannotCommitSelectedFileMerge,
768
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
769
self.assertEqual(['bar', 'baz'], err.files)
770
self.assertEqual('Selected-file commit of merges is not supported'
771
' yet: files bar, baz', str(err))
773
def test_commit_ordering(self):
774
"""Test of corner-case commit ordering error"""
775
tree = self.make_branch_and_tree('.')
776
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
777
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
779
self.build_tree(['a/c/d/'])
781
tree.rename_one('a/z/x', 'a/c/d/x')
782
tree.commit('test', specific_files=['a/z/y'])
784
def test_commit_no_author(self):
785
"""The default kwarg author in MutableTree.commit should not add
786
the 'author' revision property.
788
tree = self.make_branch_and_tree('foo')
789
rev_id = tree.commit('commit 1')
790
rev = tree.branch.repository.get_revision(rev_id)
791
self.assertFalse('author' in rev.properties)
792
self.assertFalse('authors' in rev.properties)
794
def test_commit_author(self):
795
"""Passing a non-empty authors kwarg to MutableTree.commit should add
796
the 'author' revision property.
798
tree = self.make_branch_and_tree('foo')
799
rev_id = tree.commit(
801
authors=['John Doe <jdoe@example.com>'])
802
rev = tree.branch.repository.get_revision(rev_id)
803
self.assertEqual('John Doe <jdoe@example.com>',
804
rev.properties['authors'])
805
self.assertFalse('author' in rev.properties)
807
def test_commit_empty_authors_list(self):
808
"""Passing an empty list to authors shouldn't add the property."""
809
tree = self.make_branch_and_tree('foo')
810
rev_id = tree.commit('commit 1', authors=[])
811
rev = tree.branch.repository.get_revision(rev_id)
812
self.assertFalse('author' in rev.properties)
813
self.assertFalse('authors' in rev.properties)
815
def test_multiple_authors(self):
816
tree = self.make_branch_and_tree('foo')
817
rev_id = tree.commit('commit 1',
818
authors=['John Doe <jdoe@example.com>',
819
'Jane Rey <jrey@example.com>'])
820
rev = tree.branch.repository.get_revision(rev_id)
821
self.assertEqual('John Doe <jdoe@example.com>\n'
822
'Jane Rey <jrey@example.com>', rev.properties['authors'])
823
self.assertFalse('author' in rev.properties)
825
def test_author_with_newline_rejected(self):
826
tree = self.make_branch_and_tree('foo')
827
self.assertRaises(AssertionError, tree.commit, 'commit 1',
828
authors=['John\nDoe <jdoe@example.com>'])
830
def test_commit_with_checkout_and_branch_sharing_repo(self):
831
repo = self.make_repository('repo', shared=True)
832
# make_branch_and_tree ignores shared repos
833
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
834
tree2 = branch.create_checkout('repo/tree2')
835
tree2.commit('message', rev_id='rev1')
836
self.assertTrue(tree2.branch.repository.has_revision('rev1'))
839
class FilterExcludedTests(TestCase):
841
def test_add_file_not_excluded(self):
843
('fid', (None, 'newpath'),
844
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
845
('file', 'file'), (True, True))]
846
self.assertEqual(changes, list(filter_excluded(changes, ['otherpath'])))
848
def test_add_file_excluded(self):
850
('fid', (None, 'newpath'),
851
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
852
('file', 'file'), (True, True))]
853
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
855
def test_delete_file_excluded(self):
857
('fid', ('somepath', None),
858
0, (False, None), ('pid', None), ('newpath', None),
859
('file', None), (True, None))]
860
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
862
def test_move_from_or_to_excluded(self):
864
('fid', ('oldpath', 'newpath'),
865
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
866
('file', 'file'), (True, True))]
867
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
868
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))