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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
21
from bzrlib import (
26
27
from bzrlib.branch import Branch
27
from bzrlib.bzrdir import BzrDirMetaFormat1
28
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
28
29
from bzrlib.commit import Commit, NullCommitReporter
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
30
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
45
37
# TODO: Test commit with some added, and added-but-missing files
47
class MustSignConfig(config.MemoryStack):
50
super(MustSignConfig, self).__init__('''
51
gpg_signing_command=cat -
52
create_signatures=always
39
class MustSignConfig(BranchConfig):
41
def signature_needed(self):
44
def gpg_signing_command(self):
48
class BranchWithHooks(BranchConfig):
50
def post_commit(self):
51
return "bzrlib.ahook bzrlib.ahook"
56
54
class CapturingReporter(NullCommitReporter):
82
77
"""Commit and check two versions of a single file."""
83
78
wt = self.make_branch_and_tree('.')
85
with file('hello', 'w') as f: f.write('hello world')
80
file('hello', 'w').write('hello world')
87
rev1 = wt.commit(message='add hello')
82
wt.commit(message='add hello')
88
83
file_id = wt.path2id('hello')
90
with file('hello', 'w') as f: f.write('version 2')
91
rev2 = wt.commit(message='commit 2')
85
file('hello', 'w').write('version 2')
86
wt.commit(message='commit 2')
88
eq = self.assertEquals
95
rev = b.repository.get_revision(rev1)
90
rh = b.revision_history()
91
rev = b.repository.get_revision(rh[0])
96
92
eq(rev.message, 'add hello')
98
tree1 = b.repository.revision_tree(rev1)
94
tree1 = b.repository.revision_tree(rh[0])
100
95
text = tree1.get_file_text(file_id)
102
self.assertEqual('hello world', text)
104
tree2 = b.repository.revision_tree(rev2)
106
text = tree2.get_file_text(file_id)
108
self.assertEqual('version 2', text)
110
def test_commit_lossy_native(self):
111
"""Attempt a lossy commit to a native branch."""
112
wt = self.make_branch_and_tree('.')
114
with file('hello', 'w') as f: f.write('hello world')
116
revid = wt.commit(message='add hello', rev_id='revid', lossy=True)
117
self.assertEqual('revid', revid)
119
def test_commit_lossy_foreign(self):
120
"""Attempt a lossy commit to a foreign branch."""
121
test_foreign.register_dummy_foreign_for_test(self)
122
wt = self.make_branch_and_tree('.',
123
format=test_foreign.DummyForeignVcsDirFormat())
125
with file('hello', 'w') as f: f.write('hello world')
127
revid = wt.commit(message='add hello', lossy=True,
128
timestamp=1302659388, timezone=0)
129
self.assertEqual('dummy-v1:1302659388.0-0-UNKNOWN', revid)
131
def test_commit_bound_lossy_foreign(self):
132
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
133
test_foreign.register_dummy_foreign_for_test(self)
134
foreign_branch = self.make_branch('foreign',
135
format=test_foreign.DummyForeignVcsDirFormat())
136
wt = foreign_branch.create_checkout("local")
138
with file('local/hello', 'w') as f: f.write('hello world')
140
revid = wt.commit(message='add hello', lossy=True,
141
timestamp=1302659388, timezone=0)
142
self.assertEqual('dummy-v1:1302659388.0-0-0', revid)
143
self.assertEqual('dummy-v1:1302659388.0-0-0',
144
foreign_branch.last_revision())
145
self.assertEqual('dummy-v1:1302659388.0-0-0',
146
wt.branch.last_revision())
148
def test_missing_commit(self):
149
"""Test a commit with a missing file"""
150
wt = self.make_branch_and_tree('.')
152
with file('hello', 'w') as f: f.write('hello world')
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"""
103
wt = self.make_branch_and_tree('.')
105
file('hello', 'w').write('hello world')
153
106
wt.add(['hello'], ['hello-id'])
154
107
wt.commit(message='add hello')
156
109
os.remove('hello')
157
reporter = CapturingReporter()
158
wt.commit('removed hello', rev_id='rev2', reporter=reporter)
160
[('missing', u'hello'), ('deleted', u'hello')],
110
wt.commit('removed hello', rev_id='rev2')
163
112
tree = b.repository.revision_tree('rev2')
164
113
self.assertFalse(tree.has_id('hello-id'))
166
def test_partial_commit_move(self):
167
"""Test a partial commit where a file was renamed but not committed.
169
https://bugs.launchpad.net/bzr/+bug/83039
171
If not handled properly, commit will try to snapshot
172
dialog.py with olive/ as a parent, while
173
olive/ has not been snapshotted yet.
175
wt = self.make_branch_and_tree('.')
177
self.build_tree(['annotate/', 'annotate/foo.py',
178
'olive/', 'olive/dialog.py'
180
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
181
wt.commit(message='add files')
182
wt.rename_one("olive/dialog.py", "aaa")
183
self.build_tree_contents([('annotate/foo.py', 'modified\n')])
184
wt.commit('renamed hello', specific_files=["annotate"])
186
115
def test_pointless_commit(self):
187
116
"""Commit refuses unless there are changes or it's forced."""
188
117
wt = self.make_branch_and_tree('.')
190
with file('hello', 'w') as f: f.write('hello')
119
file('hello', 'w').write('hello')
191
120
wt.add(['hello'])
192
121
wt.commit(message='add hello')
193
self.assertEqual(b.revno(), 1)
122
self.assertEquals(b.revno(), 1)
194
123
self.assertRaises(PointlessCommit,
197
126
allow_pointless=False)
198
self.assertEqual(b.revno(), 1)
127
self.assertEquals(b.revno(), 1)
200
129
def test_commit_empty(self):
201
130
"""Commiting an empty tree works."""
202
131
wt = self.make_branch_and_tree('.')
207
136
message='empty tree',
208
137
allow_pointless=False)
209
138
wt.commit(message='empty tree', allow_pointless=True)
210
self.assertEqual(b.revno(), 2)
139
self.assertEquals(b.revno(), 2)
212
141
def test_selective_delete(self):
213
142
"""Selective commit in tree with deletions"""
214
143
wt = self.make_branch_and_tree('.')
216
with file('hello', 'w') as f: f.write('hello')
217
with file('buongia', 'w') as f: f.write('buongia')
145
file('hello', 'w').write('hello')
146
file('buongia', 'w').write('buongia')
218
147
wt.add(['hello', 'buongia'],
219
148
['hello-id', 'buongia-id'])
220
149
wt.commit(message='add files',
221
150
rev_id='test@rev-1')
223
152
os.remove('hello')
224
with file('buongia', 'w') as f: f.write('new text')
153
file('buongia', 'w').write('new text')
225
154
wt.commit(message='update text',
226
155
specific_files=['buongia'],
227
156
allow_pointless=False,
259
184
tree.rename_one('hello', 'fruity')
260
185
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
262
eq = self.assertEqual
187
eq = self.assertEquals
263
188
tree1 = b.repository.revision_tree('test@rev-1')
265
self.addCleanup(tree1.unlock)
266
189
eq(tree1.id2path('hello-id'), 'hello')
267
190
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
268
191
self.assertFalse(tree1.has_filename('fruity'))
269
self.check_tree_shape(tree1, ['hello'])
270
eq(tree1.get_file_revision('hello-id'), 'test@rev-1')
192
self.check_inventory_shape(tree1.inventory, ['hello'])
193
ie = tree1.inventory['hello-id']
194
eq(ie.revision, 'test@rev-1')
272
196
tree2 = b.repository.revision_tree('test@rev-2')
274
self.addCleanup(tree2.unlock)
275
197
eq(tree2.id2path('hello-id'), 'fruity')
276
198
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
277
self.check_tree_shape(tree2, ['fruity'])
278
eq(tree2.get_file_revision('hello-id'), 'test@rev-2')
199
self.check_inventory_shape(tree2.inventory, ['fruity'])
200
ie = tree2.inventory['hello-id']
201
eq(ie.revision, 'test@rev-2')
280
203
def test_reused_rev_id(self):
281
204
"""Test that a revision id cannot be reused in a branch"""
447
372
wt = self.make_branch_and_tree('.')
448
373
branch = wt.branch
449
374
wt.commit("base", allow_pointless=True, rev_id='A')
450
self.assertFalse(branch.repository.has_signature_for_revision_id('A'))
375
self.failIf(branch.repository.has_signature_for_revision_id('A'))
377
from bzrlib.testament import Testament
452
378
# monkey patch gpg signing mechanism
453
379
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
454
conf = config.MemoryStack('''
455
gpg_signing_command=cat -
456
create_signatures=always
380
config = MustSignConfig(branch)
458
381
self.assertRaises(SigningFailed,
459
commit.Commit(config_stack=conf).commit,
382
commit.Commit(config=config).commit,
461
384
allow_pointless=True,
464
387
branch = Branch.open(self.get_url('.'))
465
self.assertEqual(branch.last_revision(), 'A')
466
self.assertFalse(branch.repository.has_revision('B'))
388
self.assertEqual(branch.revision_history(), ['A'])
389
self.failIf(branch.repository.has_revision('B'))
468
391
bzrlib.gpg.GPGStrategy = oldstrategy
589
517
this_tree.merge_from_branch(other_tree.branch)
590
518
reporter = CapturingReporter()
591
519
this_tree.commit('do the commit', reporter=reporter)
521
('change', 'unchanged', ''),
522
('change', 'unchanged', 'dirtoleave'),
523
('change', 'unchanged', 'filetoleave'),
593
524
('change', 'modified', 'filetomodify'),
594
525
('change', 'added', 'newdir'),
595
526
('change', 'added', 'newfile'),
596
527
('renamed', 'renamed', 'dirtorename', 'renameddir'),
597
('renamed', 'renamed', 'filetorename', 'renamedfile'),
598
528
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
599
529
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
530
('renamed', 'renamed', 'filetorename', 'renamedfile'),
600
531
('deleted', 'dirtoremove'),
601
532
('deleted', 'filetoremove'),
603
result = set(reporter.calls)
604
missing = expected - result
605
new = result - expected
606
self.assertEqual((set(), set()), (missing, new))
608
536
def test_commit_removals_respects_filespec(self):
609
537
"""Commit respects the specified_files for removals."""
747
676
cb = self.Callback(u'commit 2', self)
748
677
repository = tree.branch.repository
749
678
# simulate network failure
750
def raise_(self, arg, arg2, arg3=None, arg4=None):
679
def raise_(self, arg, arg2):
751
680
raise errors.NoSuchFile('foo')
752
681
repository.add_inventory = raise_
753
repository.add_inventory_by_delta = raise_
754
682
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
755
683
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 = controldir.ControlDir.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'))