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')
112
166
tree = b.repository.revision_tree('rev2')
113
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"])
115
189
def test_pointless_commit(self):
116
190
"""Commit refuses unless there are changes or it's forced."""
117
191
wt = self.make_branch_and_tree('.')
187
265
eq = self.assertEquals
188
266
tree1 = b.repository.revision_tree('test@rev-1')
268
self.addCleanup(tree1.unlock)
189
269
eq(tree1.id2path('hello-id'), 'hello')
190
270
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
191
271
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')
272
self.check_tree_shape(tree1, ['hello'])
273
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
196
275
tree2 = b.repository.revision_tree('test@rev-2')
277
self.addCleanup(tree2.unlock)
197
278
eq(tree2.id2path('hello-id'), 'fruity')
198
279
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')
280
self.check_tree_shape(tree2, ['fruity'])
281
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
203
283
def test_reused_rev_id(self):
204
284
"""Test that a revision id cannot be reused in a branch"""
223
303
wt.move(['hello'], 'a')
224
304
r2 = 'test@rev-2'
225
305
wt.commit('two', rev_id=r2, allow_pointless=False)
226
self.check_inventory_shape(wt.read_working_inventory(),
227
['a', 'a/hello', 'b'])
308
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
229
312
wt.move(['b'], 'a')
230
313
r3 = 'test@rev-3'
231
314
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'])
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/'])
237
324
wt.move(['a/hello'], 'a/b')
238
325
r4 = 'test@rev-4'
239
326
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'])
329
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
243
inv = b.repository.get_revision_inventory(r4)
333
inv = b.repository.get_inventory(r4)
244
334
eq(inv['hello-id'].revision, r4)
245
335
eq(inv['a-id'].revision, r1)
246
336
eq(inv['b-id'].revision, r3)
248
338
def test_removed_commit(self):
249
339
"""Commit with a removed file"""
250
340
wt = self.make_branch_and_tree('.')
503
589
this_tree.merge_from_branch(other_tree.branch)
504
590
reporter = CapturingReporter()
505
591
this_tree.commit('do the commit', reporter=reporter)
507
('change', 'unchanged', ''),
508
('change', 'unchanged', 'dirtoleave'),
509
('change', 'unchanged', 'filetoleave'),
510
593
('change', 'modified', 'filetomodify'),
511
594
('change', 'added', 'newdir'),
512
595
('change', 'added', 'newfile'),
513
596
('renamed', 'renamed', 'dirtorename', 'renameddir'),
597
('renamed', 'renamed', 'filetorename', 'renamedfile'),
514
598
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
515
599
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
516
('renamed', 'renamed', 'filetorename', 'renamedfile'),
517
600
('deleted', 'dirtoremove'),
518
601
('deleted', 'filetoremove'),
603
result = set(reporter.calls)
604
missing = expected - result
605
new = result - expected
606
self.assertEqual((set(), set()), (missing, new))
522
608
def test_commit_removals_respects_filespec(self):
523
609
"""Commit respects the specified_files for removals."""
554
644
timestamp_1ms = round(timestamp, 3)
555
645
self.assertEqual(timestamp_1ms, timestamp)
647
def assertBasisTreeKind(self, kind, tree, file_id):
648
basis = tree.basis_tree()
651
self.assertEqual(kind, basis.kind(file_id))
557
655
def test_commit_kind_changes(self):
558
if not osutils.has_symlinks():
559
raise tests.TestSkipped('Test requires symlink support')
656
self.requireFeature(SymlinkFeature)
560
657
tree = self.make_branch_and_tree('.')
561
658
os.symlink('target', 'name')
562
659
tree.add('name', 'a-file-id')
563
660
tree.commit('Added a symlink')
564
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
661
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
566
663
os.unlink('name')
567
664
self.build_tree(['name'])
568
665
tree.commit('Changed symlink to file')
569
self.assertEqual('file', tree.basis_tree().kind('a-file-id'))
666
self.assertBasisTreeKind('file', tree, 'a-file-id')
571
668
os.unlink('name')
572
669
os.symlink('target', 'name')
573
670
tree.commit('file to symlink')
574
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
671
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
576
673
os.unlink('name')
578
675
tree.commit('symlink to directory')
579
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
676
self.assertBasisTreeKind('directory', tree, 'a-file-id')
582
679
os.symlink('target', 'name')
583
680
tree.commit('directory to symlink')
584
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
681
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
586
683
# prepare for directory <-> file tests
587
684
os.unlink('name')
589
686
tree.commit('symlink to directory')
590
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
687
self.assertBasisTreeKind('directory', tree, 'a-file-id')
593
690
self.build_tree(['name'])
594
691
tree.commit('Changed directory to file')
595
self.assertEqual('file', tree.basis_tree().kind('a-file-id'))
692
self.assertBasisTreeKind('file', tree, 'a-file-id')
597
694
os.unlink('name')
599
696
tree.commit('file to directory')
600
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
697
self.assertBasisTreeKind('directory', tree, 'a-file-id')
602
699
def test_commit_unversioned_specified(self):
603
700
"""Commit should raise if specified files isn't in basis or worktree"""
604
701
tree = self.make_branch_and_tree('.')
605
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
702
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
606
703
'message', specific_files=['bogus'])
608
705
class Callback(object):
610
707
def __init__(self, message, testcase):
611
708
self.called = False
612
709
self.message = message
650
747
cb = self.Callback(u'commit 2', self)
651
748
repository = tree.branch.repository
652
749
# simulate network failure
653
def raise_(self, arg, arg2):
750
def raise_(self, arg, arg2, arg3=None, arg4=None):
654
751
raise errors.NoSuchFile('foo')
655
752
repository.add_inventory = raise_
753
repository.add_inventory_by_delta = raise_
656
754
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
657
755
self.assertFalse(cb.called)
757
def test_selected_file_merge_commit(self):
758
"""Ensure the correct error is raised"""
759
tree = self.make_branch_and_tree('foo')
760
# pending merge would turn into a left parent
761
tree.commit('commit 1')
762
tree.add_parent_tree_id('example')
763
self.build_tree(['foo/bar', 'foo/baz'])
764
tree.add(['bar', 'baz'])
765
err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
766
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
767
self.assertEqual(['bar', 'baz'], err.files)
768
self.assertEqual('Selected-file commit of merges is not supported'
769
' yet: files bar, baz', str(err))
771
def test_commit_ordering(self):
772
"""Test of corner-case commit ordering error"""
773
tree = self.make_branch_and_tree('.')
774
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
775
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
777
self.build_tree(['a/c/d/'])
779
tree.rename_one('a/z/x', 'a/c/d/x')
780
tree.commit('test', specific_files=['a/z/y'])
782
def test_commit_no_author(self):
783
"""The default kwarg author in MutableTree.commit should not add
784
the 'author' revision property.
786
tree = self.make_branch_and_tree('foo')
787
rev_id = tree.commit('commit 1')
788
rev = tree.branch.repository.get_revision(rev_id)
789
self.assertFalse('author' in rev.properties)
790
self.assertFalse('authors' in rev.properties)
792
def test_commit_author(self):
793
"""Passing a non-empty author kwarg to MutableTree.commit should add
794
the 'author' revision property.
796
tree = self.make_branch_and_tree('foo')
797
rev_id = self.callDeprecated(['The parameter author was '
798
'deprecated in version 1.13. Use authors instead'],
799
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
800
rev = tree.branch.repository.get_revision(rev_id)
801
self.assertEqual('John Doe <jdoe@example.com>',
802
rev.properties['authors'])
803
self.assertFalse('author' in rev.properties)
805
def test_commit_empty_authors_list(self):
806
"""Passing an empty list to authors shouldn't add the property."""
807
tree = self.make_branch_and_tree('foo')
808
rev_id = tree.commit('commit 1', authors=[])
809
rev = tree.branch.repository.get_revision(rev_id)
810
self.assertFalse('author' in rev.properties)
811
self.assertFalse('authors' in rev.properties)
813
def test_multiple_authors(self):
814
tree = self.make_branch_and_tree('foo')
815
rev_id = tree.commit('commit 1',
816
authors=['John Doe <jdoe@example.com>',
817
'Jane Rey <jrey@example.com>'])
818
rev = tree.branch.repository.get_revision(rev_id)
819
self.assertEqual('John Doe <jdoe@example.com>\n'
820
'Jane Rey <jrey@example.com>', rev.properties['authors'])
821
self.assertFalse('author' in rev.properties)
823
def test_author_and_authors_incompatible(self):
824
tree = self.make_branch_and_tree('foo')
825
self.assertRaises(AssertionError, tree.commit, 'commit 1',
826
authors=['John Doe <jdoe@example.com>',
827
'Jane Rey <jrey@example.com>'],
828
author="Jack Me <jme@example.com>")
830
def test_author_with_newline_rejected(self):
831
tree = self.make_branch_and_tree('foo')
832
self.assertRaises(AssertionError, tree.commit, 'commit 1',
833
authors=['John\nDoe <jdoe@example.com>'])
835
def test_commit_with_checkout_and_branch_sharing_repo(self):
836
repo = self.make_repository('repo', shared=True)
837
# make_branch_and_tree ignores shared repos
838
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
839
tree2 = branch.create_checkout('repo/tree2')
840
tree2.commit('message', rev_id='rev1')
841
self.assertTrue(tree2.branch.repository.has_revision('rev1'))