13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21
21
from bzrlib import (
27
25
from bzrlib.branch import Branch
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
26
from bzrlib.bzrdir import BzrDirMetaFormat1
29
27
from bzrlib.commit import Commit, NullCommitReporter
30
28
from bzrlib.config import BranchConfig
31
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
33
from bzrlib.tests import TestCaseWithTransport
34
from bzrlib.workingtree import WorkingTree
29
from bzrlib.errors import (
35
from bzrlib.tests import (
36
TestCaseWithTransport,
39
from bzrlib.tests.features import (
42
from bzrlib.tests.matchers import MatchesAncestry
37
45
# TODO: Test commit with some added, and added-but-missing files
92
103
eq(rev.message, 'add hello')
94
105
tree1 = b.repository.revision_tree(rh[0])
95
107
text = tree1.get_file_text(file_id)
96
eq(text, 'hello world')
109
self.assertEqual('hello world', text)
98
111
tree2 = b.repository.revision_tree(rh[1])
99
eq(tree2.get_file_text(file_id), 'version 2')
101
def test_delete_commit(self):
102
"""Test a commit with a deleted file"""
113
text = tree2.get_file_text(file_id)
115
self.assertEqual('version 2', text)
117
def test_commit_lossy_native(self):
118
"""Attempt a lossy commit to a native branch."""
119
wt = self.make_branch_and_tree('.')
121
file('hello', 'w').write('hello world')
123
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
124
self.assertEquals('revid', revid)
126
def test_commit_lossy_foreign(self):
127
"""Attempt a lossy commit to a foreign branch."""
128
test_foreign.register_dummy_foreign_for_test(self)
129
wt = self.make_branch_and_tree('.',
130
format=test_foreign.DummyForeignVcsDirFormat())
132
file('hello', 'w').write('hello world')
134
revid = wt.commit(message='add hello', lossy=True,
135
timestamp=1302659388, timezone=0)
136
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
138
def test_commit_bound_lossy_foreign(self):
139
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
140
test_foreign.register_dummy_foreign_for_test(self)
141
foreign_branch = self.make_branch('foreign',
142
format=test_foreign.DummyForeignVcsDirFormat())
143
wt = foreign_branch.create_checkout("local")
145
file('local/hello', 'w').write('hello world')
147
revid = wt.commit(message='add hello', lossy=True,
148
timestamp=1302659388, timezone=0)
149
self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
150
self.assertEquals('dummy-v1:1302659388.0-0-0',
151
foreign_branch.last_revision())
152
self.assertEquals('dummy-v1:1302659388.0-0-0',
153
wt.branch.last_revision())
155
def test_missing_commit(self):
156
"""Test a commit with a missing file"""
103
157
wt = self.make_branch_and_tree('.')
105
159
file('hello', 'w').write('hello world')
107
161
wt.commit(message='add hello')
109
163
os.remove('hello')
110
wt.commit('removed hello', rev_id='rev2')
164
reporter = CapturingReporter()
165
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
167
[('missing', u'hello'), ('deleted', u'hello')],
112
170
tree = b.repository.revision_tree('rev2')
113
171
self.assertFalse(tree.has_id('hello-id'))
173
def test_partial_commit_move(self):
174
"""Test a partial commit where a file was renamed but not committed.
176
https://bugs.launchpad.net/bzr/+bug/83039
178
If not handled properly, commit will try to snapshot
179
dialog.py with olive/ as a parent, while
180
olive/ has not been snapshotted yet.
182
wt = self.make_branch_and_tree('.')
184
self.build_tree(['annotate/', 'annotate/foo.py',
185
'olive/', 'olive/dialog.py'
187
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
188
wt.commit(message='add files')
189
wt.rename_one("olive/dialog.py", "aaa")
190
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
191
wt.commit('renamed hello', specific_files=["annotate"])
115
193
def test_pointless_commit(self):
116
194
"""Commit refuses unless there are changes or it's forced."""
117
195
wt = self.make_branch_and_tree('.')
187
269
eq = self.assertEquals
188
270
tree1 = b.repository.revision_tree('test@rev-1')
272
self.addCleanup(tree1.unlock)
189
273
eq(tree1.id2path('hello-id'), 'hello')
190
274
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
191
275
self.assertFalse(tree1.has_filename('fruity'))
192
self.check_inventory_shape(tree1.inventory, ['hello'])
193
ie = tree1.inventory['hello-id']
194
eq(ie.revision, 'test@rev-1')
276
self.check_tree_shape(tree1, ['hello'])
277
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
196
279
tree2 = b.repository.revision_tree('test@rev-2')
281
self.addCleanup(tree2.unlock)
197
282
eq(tree2.id2path('hello-id'), 'fruity')
198
283
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
199
self.check_inventory_shape(tree2.inventory, ['fruity'])
200
ie = tree2.inventory['hello-id']
201
eq(ie.revision, 'test@rev-2')
284
self.check_tree_shape(tree2, ['fruity'])
285
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
203
287
def test_reused_rev_id(self):
204
288
"""Test that a revision id cannot be reused in a branch"""
223
307
wt.move(['hello'], 'a')
224
308
r2 = 'test@rev-2'
225
309
wt.commit('two', rev_id=r2, allow_pointless=False)
226
self.check_inventory_shape(wt.read_working_inventory(),
227
['a', 'a/hello', 'b'])
312
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
229
316
wt.move(['b'], 'a')
230
317
r3 = 'test@rev-3'
231
318
wt.commit('three', rev_id=r3, allow_pointless=False)
232
self.check_inventory_shape(wt.read_working_inventory(),
233
['a', 'a/hello', 'a/b'])
234
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
235
['a', 'a/hello', 'a/b'])
321
self.check_tree_shape(wt,
322
['a/', 'a/hello', 'a/b/'])
323
self.check_tree_shape(b.repository.revision_tree(r3),
324
['a/', 'a/hello', 'a/b/'])
237
328
wt.move(['a/hello'], 'a/b')
238
329
r4 = 'test@rev-4'
239
330
wt.commit('four', rev_id=r4, allow_pointless=False)
240
self.check_inventory_shape(wt.read_working_inventory(),
241
['a', 'a/b/hello', 'a/b'])
333
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
243
inv = b.repository.get_revision_inventory(r4)
337
inv = b.repository.get_inventory(r4)
244
338
eq(inv['hello-id'].revision, r4)
245
339
eq(inv['a-id'].revision, r1)
246
340
eq(inv['b-id'].revision, r3)
248
342
def test_removed_commit(self):
249
343
"""Commit with a removed file"""
250
344
wt = self.make_branch_and_tree('.')
503
593
this_tree.merge_from_branch(other_tree.branch)
504
594
reporter = CapturingReporter()
505
595
this_tree.commit('do the commit', reporter=reporter)
507
('change', 'unchanged', ''),
508
('change', 'unchanged', 'dirtoleave'),
509
('change', 'unchanged', 'filetoleave'),
510
597
('change', 'modified', 'filetomodify'),
511
598
('change', 'added', 'newdir'),
512
599
('change', 'added', 'newfile'),
513
600
('renamed', 'renamed', 'dirtorename', 'renameddir'),
601
('renamed', 'renamed', 'filetorename', 'renamedfile'),
514
602
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
515
603
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
516
('renamed', 'renamed', 'filetorename', 'renamedfile'),
517
604
('deleted', 'dirtoremove'),
518
605
('deleted', 'filetoremove'),
607
result = set(reporter.calls)
608
missing = expected - result
609
new = result - expected
610
self.assertEqual((set(), set()), (missing, new))
522
612
def test_commit_removals_respects_filespec(self):
523
613
"""Commit respects the specified_files for removals."""
554
648
timestamp_1ms = round(timestamp, 3)
555
649
self.assertEqual(timestamp_1ms, timestamp)
651
def assertBasisTreeKind(self, kind, tree, file_id):
652
basis = tree.basis_tree()
655
self.assertEqual(kind, basis.kind(file_id))
557
659
def test_commit_kind_changes(self):
558
if not osutils.has_symlinks():
559
raise tests.TestSkipped('Test requires symlink support')
660
self.requireFeature(SymlinkFeature)
560
661
tree = self.make_branch_and_tree('.')
561
662
os.symlink('target', 'name')
562
663
tree.add('name', 'a-file-id')
563
664
tree.commit('Added a symlink')
564
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
665
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
566
667
os.unlink('name')
567
668
self.build_tree(['name'])
568
669
tree.commit('Changed symlink to file')
569
self.assertEqual('file', tree.basis_tree().kind('a-file-id'))
670
self.assertBasisTreeKind('file', tree, 'a-file-id')
571
672
os.unlink('name')
572
673
os.symlink('target', 'name')
573
674
tree.commit('file to symlink')
574
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
675
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
576
677
os.unlink('name')
578
679
tree.commit('symlink to directory')
579
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
680
self.assertBasisTreeKind('directory', tree, 'a-file-id')
582
683
os.symlink('target', 'name')
583
684
tree.commit('directory to symlink')
584
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
685
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
586
687
# prepare for directory <-> file tests
587
688
os.unlink('name')
589
690
tree.commit('symlink to directory')
590
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
691
self.assertBasisTreeKind('directory', tree, 'a-file-id')
593
694
self.build_tree(['name'])
594
695
tree.commit('Changed directory to file')
595
self.assertEqual('file', tree.basis_tree().kind('a-file-id'))
696
self.assertBasisTreeKind('file', tree, 'a-file-id')
597
698
os.unlink('name')
599
700
tree.commit('file to directory')
600
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
701
self.assertBasisTreeKind('directory', tree, 'a-file-id')
602
703
def test_commit_unversioned_specified(self):
603
704
"""Commit should raise if specified files isn't in basis or worktree"""
604
705
tree = self.make_branch_and_tree('.')
605
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
706
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
606
707
'message', specific_files=['bogus'])
608
709
class Callback(object):
610
711
def __init__(self, message, testcase):
611
712
self.called = False
612
713
self.message = message
650
751
cb = self.Callback(u'commit 2', self)
651
752
repository = tree.branch.repository
652
753
# simulate network failure
653
def raise_(self, arg, arg2):
754
def raise_(self, arg, arg2, arg3=None, arg4=None):
654
755
raise errors.NoSuchFile('foo')
655
756
repository.add_inventory = raise_
757
repository.add_inventory_by_delta = raise_
656
758
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
657
759
self.assertFalse(cb.called)
761
def test_selected_file_merge_commit(self):
762
"""Ensure the correct error is raised"""
763
tree = self.make_branch_and_tree('foo')
764
# pending merge would turn into a left parent
765
tree.commit('commit 1')
766
tree.add_parent_tree_id('example')
767
self.build_tree(['foo/bar', 'foo/baz'])
768
tree.add(['bar', 'baz'])
769
err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
770
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
771
self.assertEqual(['bar', 'baz'], err.files)
772
self.assertEqual('Selected-file commit of merges is not supported'
773
' yet: files bar, baz', str(err))
775
def test_commit_ordering(self):
776
"""Test of corner-case commit ordering error"""
777
tree = self.make_branch_and_tree('.')
778
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
779
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
781
self.build_tree(['a/c/d/'])
783
tree.rename_one('a/z/x', 'a/c/d/x')
784
tree.commit('test', specific_files=['a/z/y'])
786
def test_commit_no_author(self):
787
"""The default kwarg author in MutableTree.commit should not add
788
the 'author' revision property.
790
tree = self.make_branch_and_tree('foo')
791
rev_id = tree.commit('commit 1')
792
rev = tree.branch.repository.get_revision(rev_id)
793
self.assertFalse('author' in rev.properties)
794
self.assertFalse('authors' in rev.properties)
796
def test_commit_author(self):
797
"""Passing a non-empty author kwarg to MutableTree.commit should add
798
the 'author' revision property.
800
tree = self.make_branch_and_tree('foo')
801
rev_id = self.callDeprecated(['The parameter author was '
802
'deprecated in version 1.13. Use authors instead'],
803
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
804
rev = tree.branch.repository.get_revision(rev_id)
805
self.assertEqual('John Doe <jdoe@example.com>',
806
rev.properties['authors'])
807
self.assertFalse('author' in rev.properties)
809
def test_commit_empty_authors_list(self):
810
"""Passing an empty list to authors shouldn't add the property."""
811
tree = self.make_branch_and_tree('foo')
812
rev_id = tree.commit('commit 1', authors=[])
813
rev = tree.branch.repository.get_revision(rev_id)
814
self.assertFalse('author' in rev.properties)
815
self.assertFalse('authors' in rev.properties)
817
def test_multiple_authors(self):
818
tree = self.make_branch_and_tree('foo')
819
rev_id = tree.commit('commit 1',
820
authors=['John Doe <jdoe@example.com>',
821
'Jane Rey <jrey@example.com>'])
822
rev = tree.branch.repository.get_revision(rev_id)
823
self.assertEqual('John Doe <jdoe@example.com>\n'
824
'Jane Rey <jrey@example.com>', rev.properties['authors'])
825
self.assertFalse('author' in rev.properties)
827
def test_author_and_authors_incompatible(self):
828
tree = self.make_branch_and_tree('foo')
829
self.assertRaises(AssertionError, tree.commit, 'commit 1',
830
authors=['John Doe <jdoe@example.com>',
831
'Jane Rey <jrey@example.com>'],
832
author="Jack Me <jme@example.com>")
834
def test_author_with_newline_rejected(self):
835
tree = self.make_branch_and_tree('foo')
836
self.assertRaises(AssertionError, tree.commit, 'commit 1',
837
authors=['John\nDoe <jdoe@example.com>'])
839
def test_commit_with_checkout_and_branch_sharing_repo(self):
840
repo = self.make_repository('repo', shared=True)
841
# make_branch_and_tree ignores shared repos
842
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
843
tree2 = branch.create_checkout('repo/tree2')
844
tree2.commit('message', rev_id='rev1')
845
self.assertTrue(tree2.branch.repository.has_revision('rev1'))