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
80
91
file('hello', 'w').write('hello world')
82
wt.commit(message='add hello')
93
rev1 = wt.commit(message='add hello')
83
94
file_id = wt.path2id('hello')
85
96
file('hello', 'w').write('version 2')
86
wt.commit(message='commit 2')
97
rev2 = wt.commit(message='commit 2')
88
99
eq = self.assertEquals
90
rh = b.revision_history()
91
rev = b.repository.get_revision(rh[0])
101
rev = b.repository.get_revision(rev1)
92
102
eq(rev.message, 'add hello')
94
tree1 = b.repository.revision_tree(rh[0])
104
tree1 = b.repository.revision_tree(rev1)
95
106
text = tree1.get_file_text(file_id)
96
eq(text, 'hello world')
98
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"""
108
self.assertEqual('hello world', text)
110
tree2 = b.repository.revision_tree(rev2)
112
text = tree2.get_file_text(file_id)
114
self.assertEqual('version 2', text)
116
def test_commit_lossy_native(self):
117
"""Attempt a lossy commit to a native branch."""
118
wt = self.make_branch_and_tree('.')
120
file('hello', 'w').write('hello world')
122
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
123
self.assertEquals('revid', revid)
125
def test_commit_lossy_foreign(self):
126
"""Attempt a lossy commit to a foreign branch."""
127
test_foreign.register_dummy_foreign_for_test(self)
128
wt = self.make_branch_and_tree('.',
129
format=test_foreign.DummyForeignVcsDirFormat())
131
file('hello', 'w').write('hello world')
133
revid = wt.commit(message='add hello', lossy=True,
134
timestamp=1302659388, timezone=0)
135
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
137
def test_commit_bound_lossy_foreign(self):
138
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
139
test_foreign.register_dummy_foreign_for_test(self)
140
foreign_branch = self.make_branch('foreign',
141
format=test_foreign.DummyForeignVcsDirFormat())
142
wt = foreign_branch.create_checkout("local")
144
file('local/hello', 'w').write('hello world')
146
revid = wt.commit(message='add hello', lossy=True,
147
timestamp=1302659388, timezone=0)
148
self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
149
self.assertEquals('dummy-v1:1302659388.0-0-0',
150
foreign_branch.last_revision())
151
self.assertEquals('dummy-v1:1302659388.0-0-0',
152
wt.branch.last_revision())
154
def test_missing_commit(self):
155
"""Test a commit with a missing file"""
103
156
wt = self.make_branch_and_tree('.')
105
158
file('hello', 'w').write('hello world')
107
160
wt.commit(message='add hello')
109
162
os.remove('hello')
110
wt.commit('removed hello', rev_id='rev2')
163
reporter = CapturingReporter()
164
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
166
[('missing', u'hello'), ('deleted', u'hello')],
112
169
tree = b.repository.revision_tree('rev2')
113
170
self.assertFalse(tree.has_id('hello-id'))
172
def test_partial_commit_move(self):
173
"""Test a partial commit where a file was renamed but not committed.
175
https://bugs.launchpad.net/bzr/+bug/83039
177
If not handled properly, commit will try to snapshot
178
dialog.py with olive/ as a parent, while
179
olive/ has not been snapshotted yet.
181
wt = self.make_branch_and_tree('.')
183
self.build_tree(['annotate/', 'annotate/foo.py',
184
'olive/', 'olive/dialog.py'
186
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
187
wt.commit(message='add files')
188
wt.rename_one("olive/dialog.py", "aaa")
189
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
190
wt.commit('renamed hello', specific_files=["annotate"])
115
192
def test_pointless_commit(self):
116
193
"""Commit refuses unless there are changes or it's forced."""
117
194
wt = self.make_branch_and_tree('.')
187
268
eq = self.assertEquals
188
269
tree1 = b.repository.revision_tree('test@rev-1')
271
self.addCleanup(tree1.unlock)
189
272
eq(tree1.id2path('hello-id'), 'hello')
190
273
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
191
274
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')
275
self.check_tree_shape(tree1, ['hello'])
276
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
196
278
tree2 = b.repository.revision_tree('test@rev-2')
280
self.addCleanup(tree2.unlock)
197
281
eq(tree2.id2path('hello-id'), 'fruity')
198
282
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')
283
self.check_tree_shape(tree2, ['fruity'])
284
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
203
286
def test_reused_rev_id(self):
204
287
"""Test that a revision id cannot be reused in a branch"""
223
306
wt.move(['hello'], 'a')
224
307
r2 = 'test@rev-2'
225
308
wt.commit('two', rev_id=r2, allow_pointless=False)
226
self.check_inventory_shape(wt.read_working_inventory(),
227
['a', 'a/hello', 'b'])
311
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
229
315
wt.move(['b'], 'a')
230
316
r3 = 'test@rev-3'
231
317
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'])
320
self.check_tree_shape(wt,
321
['a/', 'a/hello', 'a/b/'])
322
self.check_tree_shape(b.repository.revision_tree(r3),
323
['a/', 'a/hello', 'a/b/'])
237
327
wt.move(['a/hello'], 'a/b')
238
328
r4 = 'test@rev-4'
239
329
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'])
332
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
243
inv = b.repository.get_revision_inventory(r4)
336
inv = b.repository.get_inventory(r4)
244
337
eq(inv['hello-id'].revision, r4)
245
338
eq(inv['a-id'].revision, r1)
246
339
eq(inv['b-id'].revision, r3)
248
341
def test_removed_commit(self):
249
342
"""Commit with a removed file"""
250
343
wt = self.make_branch_and_tree('.')
503
590
this_tree.merge_from_branch(other_tree.branch)
504
591
reporter = CapturingReporter()
505
592
this_tree.commit('do the commit', reporter=reporter)
507
('change', 'unchanged', ''),
508
('change', 'unchanged', 'dirtoleave'),
509
('change', 'unchanged', 'filetoleave'),
510
594
('change', 'modified', 'filetomodify'),
511
595
('change', 'added', 'newdir'),
512
596
('change', 'added', 'newfile'),
513
597
('renamed', 'renamed', 'dirtorename', 'renameddir'),
598
('renamed', 'renamed', 'filetorename', 'renamedfile'),
514
599
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
515
600
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
516
('renamed', 'renamed', 'filetorename', 'renamedfile'),
517
601
('deleted', 'dirtoremove'),
518
602
('deleted', 'filetoremove'),
604
result = set(reporter.calls)
605
missing = expected - result
606
new = result - expected
607
self.assertEqual((set(), set()), (missing, new))
522
609
def test_commit_removals_respects_filespec(self):
523
610
"""Commit respects the specified_files for removals."""
554
645
timestamp_1ms = round(timestamp, 3)
555
646
self.assertEqual(timestamp_1ms, timestamp)
648
def assertBasisTreeKind(self, kind, tree, file_id):
649
basis = tree.basis_tree()
652
self.assertEqual(kind, basis.kind(file_id))
557
656
def test_commit_kind_changes(self):
558
if not osutils.has_symlinks():
559
raise tests.TestSkipped('Test requires symlink support')
657
self.requireFeature(SymlinkFeature)
560
658
tree = self.make_branch_and_tree('.')
561
659
os.symlink('target', 'name')
562
660
tree.add('name', 'a-file-id')
563
661
tree.commit('Added a symlink')
564
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
662
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
566
664
os.unlink('name')
567
665
self.build_tree(['name'])
568
666
tree.commit('Changed symlink to file')
569
self.assertEqual('file', tree.basis_tree().kind('a-file-id'))
667
self.assertBasisTreeKind('file', tree, 'a-file-id')
571
669
os.unlink('name')
572
670
os.symlink('target', 'name')
573
671
tree.commit('file to symlink')
574
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
672
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
576
674
os.unlink('name')
578
676
tree.commit('symlink to directory')
579
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
677
self.assertBasisTreeKind('directory', tree, 'a-file-id')
582
680
os.symlink('target', 'name')
583
681
tree.commit('directory to symlink')
584
self.assertEqual('symlink', tree.basis_tree().kind('a-file-id'))
682
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
586
684
# prepare for directory <-> file tests
587
685
os.unlink('name')
589
687
tree.commit('symlink to directory')
590
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
688
self.assertBasisTreeKind('directory', tree, 'a-file-id')
593
691
self.build_tree(['name'])
594
692
tree.commit('Changed directory to file')
595
self.assertEqual('file', tree.basis_tree().kind('a-file-id'))
693
self.assertBasisTreeKind('file', tree, 'a-file-id')
597
695
os.unlink('name')
599
697
tree.commit('file to directory')
600
self.assertEqual('directory', tree.basis_tree().kind('a-file-id'))
698
self.assertBasisTreeKind('directory', tree, 'a-file-id')
602
700
def test_commit_unversioned_specified(self):
603
701
"""Commit should raise if specified files isn't in basis or worktree"""
604
702
tree = self.make_branch_and_tree('.')
605
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
703
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
606
704
'message', specific_files=['bogus'])
608
706
class Callback(object):
610
708
def __init__(self, message, testcase):
611
709
self.called = False
612
710
self.message = message
650
748
cb = self.Callback(u'commit 2', self)
651
749
repository = tree.branch.repository
652
750
# simulate network failure
653
def raise_(self, arg, arg2):
751
def raise_(self, arg, arg2, arg3=None, arg4=None):
654
752
raise errors.NoSuchFile('foo')
655
753
repository.add_inventory = raise_
754
repository.add_inventory_by_delta = raise_
656
755
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
657
756
self.assertFalse(cb.called)
758
def test_selected_file_merge_commit(self):
759
"""Ensure the correct error is raised"""
760
tree = self.make_branch_and_tree('foo')
761
# pending merge would turn into a left parent
762
tree.commit('commit 1')
763
tree.add_parent_tree_id('example')
764
self.build_tree(['foo/bar', 'foo/baz'])
765
tree.add(['bar', 'baz'])
766
err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
767
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
768
self.assertEqual(['bar', 'baz'], err.files)
769
self.assertEqual('Selected-file commit of merges is not supported'
770
' yet: files bar, baz', str(err))
772
def test_commit_ordering(self):
773
"""Test of corner-case commit ordering error"""
774
tree = self.make_branch_and_tree('.')
775
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
776
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
778
self.build_tree(['a/c/d/'])
780
tree.rename_one('a/z/x', 'a/c/d/x')
781
tree.commit('test', specific_files=['a/z/y'])
783
def test_commit_no_author(self):
784
"""The default kwarg author in MutableTree.commit should not add
785
the 'author' revision property.
787
tree = self.make_branch_and_tree('foo')
788
rev_id = tree.commit('commit 1')
789
rev = tree.branch.repository.get_revision(rev_id)
790
self.assertFalse('author' in rev.properties)
791
self.assertFalse('authors' in rev.properties)
793
def test_commit_author(self):
794
"""Passing a non-empty author kwarg to MutableTree.commit should add
795
the 'author' revision property.
797
tree = self.make_branch_and_tree('foo')
798
rev_id = self.callDeprecated(['The parameter author was '
799
'deprecated in version 1.13. Use authors instead'],
800
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
801
rev = tree.branch.repository.get_revision(rev_id)
802
self.assertEqual('John Doe <jdoe@example.com>',
803
rev.properties['authors'])
804
self.assertFalse('author' in rev.properties)
806
def test_commit_empty_authors_list(self):
807
"""Passing an empty list to authors shouldn't add the property."""
808
tree = self.make_branch_and_tree('foo')
809
rev_id = tree.commit('commit 1', authors=[])
810
rev = tree.branch.repository.get_revision(rev_id)
811
self.assertFalse('author' in rev.properties)
812
self.assertFalse('authors' in rev.properties)
814
def test_multiple_authors(self):
815
tree = self.make_branch_and_tree('foo')
816
rev_id = tree.commit('commit 1',
817
authors=['John Doe <jdoe@example.com>',
818
'Jane Rey <jrey@example.com>'])
819
rev = tree.branch.repository.get_revision(rev_id)
820
self.assertEqual('John Doe <jdoe@example.com>\n'
821
'Jane Rey <jrey@example.com>', rev.properties['authors'])
822
self.assertFalse('author' in rev.properties)
824
def test_author_and_authors_incompatible(self):
825
tree = self.make_branch_and_tree('foo')
826
self.assertRaises(AssertionError, tree.commit, 'commit 1',
827
authors=['John Doe <jdoe@example.com>',
828
'Jane Rey <jrey@example.com>'],
829
author="Jack Me <jme@example.com>")
831
def test_author_with_newline_rejected(self):
832
tree = self.make_branch_and_tree('foo')
833
self.assertRaises(AssertionError, tree.commit, 'commit 1',
834
authors=['John\nDoe <jdoe@example.com>'])
836
def test_commit_with_checkout_and_branch_sharing_repo(self):
837
repo = self.make_repository('repo', shared=True)
838
# make_branch_and_tree ignores shared repos
839
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
840
tree2 = branch.create_checkout('repo/tree2')
841
tree2.commit('message', rev_id='rev1')
842
self.assertTrue(tree2.branch.repository.has_revision('rev1'))