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
from bzrlib.tests import TestCaseWithTransport
22
26
from bzrlib.branch import Branch
23
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
24
from bzrlib.workingtree import WorkingTree
27
from bzrlib.bzrdir import BzrDirMetaFormat1
25
28
from bzrlib.commit import Commit, NullCommitReporter
26
from bzrlib.config import BranchConfig
27
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
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
31
45
# TODO: Test commit with some added, and added-but-missing files
33
class MustSignConfig(BranchConfig):
35
def signature_needed(self):
38
def gpg_signing_command(self):
42
class BranchWithHooks(BranchConfig):
44
def post_commit(self):
45
return "bzrlib.ahook bzrlib.ahook"
47
class MustSignConfig(config.Stack):
49
def __init__(self, branch):
50
store = config.IniFileStore()
51
store._load_from_string('''
52
gpg_signing_command=cat -
53
create_signatures=always
55
super(MustSignConfig, self).__init__([store.get_sections])
56
# FIXME: Strictly speaking we should fallback to the no-name section in
57
# branch.conf but no tests need that so far -- vila 2011-12-14
60
class BranchWithHooks(config.Stack):
62
def __init__(self, branch):
63
store = config.IniFileStore()
64
store._load_from_string('post_commit=bzrlib.ahook bzrlib.ahook')
65
super(BranchWithHooks, self).__init__([store.get_sections])
66
# FIXME: Strictly speaking we should fallback to the no-name section in
67
# branch.conf but no tests need that so far -- vila 2011-12-14
48
70
class CapturingReporter(NullCommitReporter):
74
99
file('hello', 'w').write('hello world')
76
wt.commit(message='add hello')
101
rev1 = wt.commit(message='add hello')
77
102
file_id = wt.path2id('hello')
79
104
file('hello', 'w').write('version 2')
80
wt.commit(message='commit 2')
105
rev2 = wt.commit(message='commit 2')
82
107
eq = self.assertEquals
84
rh = b.revision_history()
85
rev = b.repository.get_revision(rh[0])
109
rev = b.repository.get_revision(rev1)
86
110
eq(rev.message, 'add hello')
88
tree1 = b.repository.revision_tree(rh[0])
112
tree1 = b.repository.revision_tree(rev1)
89
114
text = tree1.get_file_text(file_id)
90
eq(text, 'hello world')
92
tree2 = b.repository.revision_tree(rh[1])
93
eq(tree2.get_file_text(file_id), 'version 2')
95
def test_delete_commit(self):
96
"""Test a commit with a deleted file"""
116
self.assertEqual('hello world', text)
118
tree2 = b.repository.revision_tree(rev2)
120
text = tree2.get_file_text(file_id)
122
self.assertEqual('version 2', text)
124
def test_commit_lossy_native(self):
125
"""Attempt a lossy commit to a native branch."""
126
wt = self.make_branch_and_tree('.')
128
file('hello', 'w').write('hello world')
130
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
131
self.assertEquals('revid', revid)
133
def test_commit_lossy_foreign(self):
134
"""Attempt a lossy commit to a foreign branch."""
135
test_foreign.register_dummy_foreign_for_test(self)
136
wt = self.make_branch_and_tree('.',
137
format=test_foreign.DummyForeignVcsDirFormat())
139
file('hello', 'w').write('hello world')
141
revid = wt.commit(message='add hello', lossy=True,
142
timestamp=1302659388, timezone=0)
143
self.assertEquals('dummy-v1:1302659388.0-0-UNKNOWN', revid)
145
def test_commit_bound_lossy_foreign(self):
146
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
147
test_foreign.register_dummy_foreign_for_test(self)
148
foreign_branch = self.make_branch('foreign',
149
format=test_foreign.DummyForeignVcsDirFormat())
150
wt = foreign_branch.create_checkout("local")
152
file('local/hello', 'w').write('hello world')
154
revid = wt.commit(message='add hello', lossy=True,
155
timestamp=1302659388, timezone=0)
156
self.assertEquals('dummy-v1:1302659388.0-0-0', revid)
157
self.assertEquals('dummy-v1:1302659388.0-0-0',
158
foreign_branch.last_revision())
159
self.assertEquals('dummy-v1:1302659388.0-0-0',
160
wt.branch.last_revision())
162
def test_missing_commit(self):
163
"""Test a commit with a missing file"""
97
164
wt = self.make_branch_and_tree('.')
99
166
file('hello', 'w').write('hello world')
217
314
wt.move(['hello'], 'a')
218
315
r2 = 'test@rev-2'
219
316
wt.commit('two', rev_id=r2, allow_pointless=False)
220
self.check_inventory_shape(wt.read_working_inventory(),
221
['a', 'a/hello', 'b'])
319
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
223
323
wt.move(['b'], 'a')
224
324
r3 = 'test@rev-3'
225
325
wt.commit('three', rev_id=r3, allow_pointless=False)
226
self.check_inventory_shape(wt.read_working_inventory(),
227
['a', 'a/hello', 'a/b'])
228
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
229
['a', 'a/hello', 'a/b'])
328
self.check_tree_shape(wt,
329
['a/', 'a/hello', 'a/b/'])
330
self.check_tree_shape(b.repository.revision_tree(r3),
331
['a/', 'a/hello', 'a/b/'])
231
335
wt.move(['a/hello'], 'a/b')
232
336
r4 = 'test@rev-4'
233
337
wt.commit('four', rev_id=r4, allow_pointless=False)
234
self.check_inventory_shape(wt.read_working_inventory(),
235
['a', 'a/b/hello', 'a/b'])
340
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
237
inv = b.repository.get_revision_inventory(r4)
344
inv = b.repository.get_inventory(r4)
238
345
eq(inv['hello-id'].revision, r4)
239
346
eq(inv['a-id'].revision, r1)
240
347
eq(inv['b-id'].revision, r3)
242
349
def test_removed_commit(self):
243
350
"""Commit with a removed file"""
244
351
wt = self.make_branch_and_tree('.')
490
594
other_tree.commit('modify all sample files and dirs.')
492
596
other_tree.unlock()
493
self.merge(other_tree.branch, this_tree)
597
this_tree.merge_from_branch(other_tree.branch)
494
598
reporter = CapturingReporter()
495
599
this_tree.commit('do the commit', reporter=reporter)
497
('change', 'unchanged', 'dirtoleave'),
498
('change', 'unchanged', 'filetoleave'),
499
601
('change', 'modified', 'filetomodify'),
500
602
('change', 'added', 'newdir'),
501
603
('change', 'added', 'newfile'),
502
604
('renamed', 'renamed', 'dirtorename', 'renameddir'),
605
('renamed', 'renamed', 'filetorename', 'renamedfile'),
503
606
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
504
607
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
505
('renamed', 'renamed', 'filetorename', 'renamedfile'),
506
608
('deleted', 'dirtoremove'),
507
609
('deleted', 'filetoremove'),
611
result = set(reporter.calls)
612
missing = expected - result
613
new = result - expected
614
self.assertEqual((set(), set()), (missing, new))
511
616
def test_commit_removals_respects_filespec(self):
512
617
"""Commit respects the specified_files for removals."""
542
651
timestamp = rev.timestamp
543
652
timestamp_1ms = round(timestamp, 3)
544
653
self.assertEqual(timestamp_1ms, timestamp)
655
def assertBasisTreeKind(self, kind, tree, file_id):
656
basis = tree.basis_tree()
659
self.assertEqual(kind, basis.kind(file_id))
663
def test_commit_kind_changes(self):
664
self.requireFeature(SymlinkFeature)
665
tree = self.make_branch_and_tree('.')
666
os.symlink('target', 'name')
667
tree.add('name', 'a-file-id')
668
tree.commit('Added a symlink')
669
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
672
self.build_tree(['name'])
673
tree.commit('Changed symlink to file')
674
self.assertBasisTreeKind('file', tree, 'a-file-id')
677
os.symlink('target', 'name')
678
tree.commit('file to symlink')
679
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
683
tree.commit('symlink to directory')
684
self.assertBasisTreeKind('directory', tree, 'a-file-id')
687
os.symlink('target', 'name')
688
tree.commit('directory to symlink')
689
self.assertBasisTreeKind('symlink', tree, 'a-file-id')
691
# prepare for directory <-> file tests
694
tree.commit('symlink to directory')
695
self.assertBasisTreeKind('directory', tree, 'a-file-id')
698
self.build_tree(['name'])
699
tree.commit('Changed directory to file')
700
self.assertBasisTreeKind('file', tree, 'a-file-id')
704
tree.commit('file to directory')
705
self.assertBasisTreeKind('directory', tree, 'a-file-id')
707
def test_commit_unversioned_specified(self):
708
"""Commit should raise if specified files isn't in basis or worktree"""
709
tree = self.make_branch_and_tree('.')
710
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
711
'message', specific_files=['bogus'])
713
class Callback(object):
715
def __init__(self, message, testcase):
717
self.message = message
718
self.testcase = testcase
720
def __call__(self, commit_obj):
722
self.testcase.assertTrue(isinstance(commit_obj, Commit))
725
def test_commit_callback(self):
726
"""Commit should invoke a callback to get the message"""
728
tree = self.make_branch_and_tree('.')
732
self.assertTrue(isinstance(e, BzrError))
733
self.assertEqual('The message or message_callback keyword'
734
' parameter is required for commit().', str(e))
736
self.fail('exception not raised')
737
cb = self.Callback(u'commit 1', self)
738
tree.commit(message_callback=cb)
739
self.assertTrue(cb.called)
740
repository = tree.branch.repository
741
message = repository.get_revision(tree.last_revision()).message
742
self.assertEqual('commit 1', message)
744
def test_no_callback_pointless(self):
745
"""Callback should not be invoked for pointless commit"""
746
tree = self.make_branch_and_tree('.')
747
cb = self.Callback(u'commit 2', self)
748
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
749
allow_pointless=False)
750
self.assertFalse(cb.called)
752
def test_no_callback_netfailure(self):
753
"""Callback should not be invoked if connectivity fails"""
754
tree = self.make_branch_and_tree('.')
755
cb = self.Callback(u'commit 2', self)
756
repository = tree.branch.repository
757
# simulate network failure
758
def raise_(self, arg, arg2, arg3=None, arg4=None):
759
raise errors.NoSuchFile('foo')
760
repository.add_inventory = raise_
761
repository.add_inventory_by_delta = raise_
762
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
763
self.assertFalse(cb.called)
765
def test_selected_file_merge_commit(self):
766
"""Ensure the correct error is raised"""
767
tree = self.make_branch_and_tree('foo')
768
# pending merge would turn into a left parent
769
tree.commit('commit 1')
770
tree.add_parent_tree_id('example')
771
self.build_tree(['foo/bar', 'foo/baz'])
772
tree.add(['bar', 'baz'])
773
err = self.assertRaises(errors.CannotCommitSelectedFileMerge,
774
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
775
self.assertEqual(['bar', 'baz'], err.files)
776
self.assertEqual('Selected-file commit of merges is not supported'
777
' yet: files bar, baz', str(err))
779
def test_commit_ordering(self):
780
"""Test of corner-case commit ordering error"""
781
tree = self.make_branch_and_tree('.')
782
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
783
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
785
self.build_tree(['a/c/d/'])
787
tree.rename_one('a/z/x', 'a/c/d/x')
788
tree.commit('test', specific_files=['a/z/y'])
790
def test_commit_no_author(self):
791
"""The default kwarg author in MutableTree.commit should not add
792
the 'author' revision property.
794
tree = self.make_branch_and_tree('foo')
795
rev_id = tree.commit('commit 1')
796
rev = tree.branch.repository.get_revision(rev_id)
797
self.assertFalse('author' in rev.properties)
798
self.assertFalse('authors' in rev.properties)
800
def test_commit_author(self):
801
"""Passing a non-empty author kwarg to MutableTree.commit should add
802
the 'author' revision property.
804
tree = self.make_branch_and_tree('foo')
805
rev_id = self.callDeprecated(['The parameter author was '
806
'deprecated in version 1.13. Use authors instead'],
807
tree.commit, 'commit 1', author='John Doe <jdoe@example.com>')
808
rev = tree.branch.repository.get_revision(rev_id)
809
self.assertEqual('John Doe <jdoe@example.com>',
810
rev.properties['authors'])
811
self.assertFalse('author' in rev.properties)
813
def test_commit_empty_authors_list(self):
814
"""Passing an empty list to authors shouldn't add the property."""
815
tree = self.make_branch_and_tree('foo')
816
rev_id = tree.commit('commit 1', authors=[])
817
rev = tree.branch.repository.get_revision(rev_id)
818
self.assertFalse('author' in rev.properties)
819
self.assertFalse('authors' in rev.properties)
821
def test_multiple_authors(self):
822
tree = self.make_branch_and_tree('foo')
823
rev_id = tree.commit('commit 1',
824
authors=['John Doe <jdoe@example.com>',
825
'Jane Rey <jrey@example.com>'])
826
rev = tree.branch.repository.get_revision(rev_id)
827
self.assertEqual('John Doe <jdoe@example.com>\n'
828
'Jane Rey <jrey@example.com>', rev.properties['authors'])
829
self.assertFalse('author' in rev.properties)
831
def test_author_and_authors_incompatible(self):
832
tree = self.make_branch_and_tree('foo')
833
self.assertRaises(AssertionError, tree.commit, 'commit 1',
834
authors=['John Doe <jdoe@example.com>',
835
'Jane Rey <jrey@example.com>'],
836
author="Jack Me <jme@example.com>")
838
def test_author_with_newline_rejected(self):
839
tree = self.make_branch_and_tree('foo')
840
self.assertRaises(AssertionError, tree.commit, 'commit 1',
841
authors=['John\nDoe <jdoe@example.com>'])
843
def test_commit_with_checkout_and_branch_sharing_repo(self):
844
repo = self.make_repository('repo', shared=True)
845
# make_branch_and_tree ignores shared repos
846
branch = bzrdir.BzrDir.create_branch_convenience('repo/branch')
847
tree2 = branch.create_checkout('repo/tree2')
848
tree2.commit('message', rev_id='rev1')
849
self.assertTrue(tree2.branch.repository.has_revision('rev1'))