1
# Copyright (C) 2005 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
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
21
from bzrlib.selftest import TestCaseInTempDir
22
from bzrlib.branch import Branch
23
from bzrlib.workingtree import WorkingTree
24
from bzrlib.commit import Commit
25
from bzrlib.config import BranchConfig
26
from bzrlib.errors import PointlessCommit, BzrError, SigningFailed
29
# TODO: Test commit with some added, and added-but-missing files
31
class MustSignConfig(BranchConfig):
33
def signature_needed(self):
36
def gpg_signing_command(self):
40
class BranchWithHooks(BranchConfig):
42
def post_commit(self):
43
return "bzrlib.ahook bzrlib.ahook"
46
class TestCommit(TestCaseInTempDir):
48
def test_simple_commit(self):
49
"""Commit and check two versions of a single file."""
50
b = Branch.initialize('.')
51
file('hello', 'w').write('hello world')
53
b.commit(message='add hello')
54
file_id = b.working_tree().path2id('hello')
56
file('hello', 'w').write('version 2')
57
b.commit(message='commit 2')
59
eq = self.assertEquals
61
rh = b.revision_history()
62
rev = b.get_revision(rh[0])
63
eq(rev.message, 'add hello')
65
tree1 = b.revision_tree(rh[0])
66
text = tree1.get_file_text(file_id)
67
eq(text, 'hello world')
69
tree2 = b.revision_tree(rh[1])
70
eq(tree2.get_file_text(file_id), 'version 2')
72
def test_delete_commit(self):
73
"""Test a commit with a deleted file"""
74
b = Branch.initialize('.')
75
file('hello', 'w').write('hello world')
76
b.add(['hello'], ['hello-id'])
77
b.commit(message='add hello')
80
b.commit('removed hello', rev_id='rev2')
82
tree = b.revision_tree('rev2')
83
self.assertFalse(tree.has_id('hello-id'))
86
def test_pointless_commit(self):
87
"""Commit refuses unless there are changes or it's forced."""
88
b = Branch.initialize('.')
89
file('hello', 'w').write('hello')
91
b.commit(message='add hello')
92
self.assertEquals(b.revno(), 1)
93
self.assertRaises(PointlessCommit,
96
allow_pointless=False)
97
self.assertEquals(b.revno(), 1)
101
def test_commit_empty(self):
102
"""Commiting an empty tree works."""
103
b = Branch.initialize('.')
104
b.commit(message='empty tree', allow_pointless=True)
105
self.assertRaises(PointlessCommit,
107
message='empty tree',
108
allow_pointless=False)
109
b.commit(message='empty tree', allow_pointless=True)
110
self.assertEquals(b.revno(), 2)
113
def test_selective_delete(self):
114
"""Selective commit in tree with deletions"""
115
b = Branch.initialize('.')
116
file('hello', 'w').write('hello')
117
file('buongia', 'w').write('buongia')
118
b.add(['hello', 'buongia'],
119
['hello-id', 'buongia-id'])
120
b.commit(message='add files',
124
file('buongia', 'w').write('new text')
125
b.commit(message='update text',
126
specific_files=['buongia'],
127
allow_pointless=False,
130
b.commit(message='remove hello',
131
specific_files=['hello'],
132
allow_pointless=False,
135
eq = self.assertEquals
138
tree2 = b.revision_tree('test@rev-2')
139
self.assertTrue(tree2.has_filename('hello'))
140
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
141
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
143
tree3 = b.revision_tree('test@rev-3')
144
self.assertFalse(tree3.has_filename('hello'))
145
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
148
def test_commit_rename(self):
149
"""Test commit of a revision where a file is renamed."""
150
b = Branch.initialize('.')
151
self.build_tree(['hello'])
152
b.add(['hello'], ['hello-id'])
153
b.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
155
b.rename_one('hello', 'fruity')
156
b.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
158
eq = self.assertEquals
159
tree1 = b.revision_tree('test@rev-1')
160
eq(tree1.id2path('hello-id'), 'hello')
161
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
162
self.assertFalse(tree1.has_filename('fruity'))
163
self.check_inventory_shape(tree1.inventory, ['hello'])
164
ie = tree1.inventory['hello-id']
165
eq(ie.revision, 'test@rev-1')
167
tree2 = b.revision_tree('test@rev-2')
168
eq(tree2.id2path('hello-id'), 'fruity')
169
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
170
self.check_inventory_shape(tree2.inventory, ['fruity'])
171
ie = tree2.inventory['hello-id']
172
eq(ie.revision, 'test@rev-2')
175
def test_reused_rev_id(self):
176
"""Test that a revision id cannot be reused in a branch"""
177
b = Branch.initialize('.')
178
b.commit('initial', rev_id='test@rev-1', allow_pointless=True)
179
self.assertRaises(Exception,
183
allow_pointless=True)
187
def test_commit_move(self):
188
"""Test commit of revisions with moved files and directories"""
189
eq = self.assertEquals
190
b = Branch.initialize('.')
192
self.build_tree(['hello', 'a/', 'b/'])
193
b.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
194
b.commit('initial', rev_id=r1, allow_pointless=False)
196
b.move(['hello'], 'a')
198
b.commit('two', rev_id=r2, allow_pointless=False)
199
self.check_inventory_shape(b.inventory,
200
['a', 'a/hello', 'b'])
204
b.commit('three', rev_id=r3, allow_pointless=False)
205
self.check_inventory_shape(b.inventory,
206
['a', 'a/hello', 'a/b'])
207
self.check_inventory_shape(b.get_revision_inventory(r3),
208
['a', 'a/hello', 'a/b'])
210
b.move([os.sep.join(['a', 'hello'])],
211
os.sep.join(['a', 'b']))
213
b.commit('four', rev_id=r4, allow_pointless=False)
214
self.check_inventory_shape(b.inventory,
215
['a', 'a/b/hello', 'a/b'])
217
inv = b.get_revision_inventory(r4)
218
eq(inv['hello-id'].revision, r4)
219
eq(inv['a-id'].revision, r1)
220
eq(inv['b-id'].revision, r3)
223
def test_removed_commit(self):
224
"""Commit with a removed file"""
225
b = Branch.initialize('.')
226
wt = b.working_tree()
227
file('hello', 'w').write('hello world')
228
b.add(['hello'], ['hello-id'])
229
b.commit(message='add hello')
231
wt = b.working_tree() # FIXME: kludge for aliasing of working inventory
233
b.commit('removed hello', rev_id='rev2')
235
tree = b.revision_tree('rev2')
236
self.assertFalse(tree.has_id('hello-id'))
239
def test_committed_ancestry(self):
240
"""Test commit appends revisions to ancestry."""
241
b = Branch.initialize('.')
244
file('hello', 'w').write((str(i) * 4) + '\n')
246
b.add(['hello'], ['hello-id'])
247
rev_id = 'test@rev-%d' % (i+1)
248
rev_ids.append(rev_id)
249
b.commit(message='rev %d' % (i+1),
251
eq = self.assertEquals
252
eq(b.revision_history(), rev_ids)
254
anc = b.get_ancestry(rev_ids[i])
255
eq(anc, [None] + rev_ids[:i+1])
257
def test_commit_new_subdir_child_selective(self):
258
b = Branch.initialize('.')
259
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
260
b.add(['dir', 'dir/file1', 'dir/file2'],
261
['dirid', 'file1id', 'file2id'])
262
b.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
263
inv = b.get_inventory('1')
264
self.assertEqual('1', inv['dirid'].revision)
265
self.assertEqual('1', inv['file1id'].revision)
266
# FIXME: This should raise a KeyError I think, rbc20051006
267
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
269
def test_strict_commit(self):
270
"""Try and commit with unknown files and strict = True, should fail."""
271
from bzrlib.errors import StrictCommitFailed
272
b = Branch.initialize('.')
273
file('hello', 'w').write('hello world')
275
file('goodbye', 'w').write('goodbye cruel world!')
276
self.assertRaises(StrictCommitFailed, b.commit,
277
message='add hello but not goodbye', strict=True)
279
def test_nonstrict_commit(self):
280
"""Try and commit with unknown files and strict = False, should work."""
281
b = Branch.initialize('.')
282
file('hello', 'w').write('hello world')
284
file('goodbye', 'w').write('goodbye cruel world!')
285
b.commit(message='add hello but not goodbye', strict=False)
287
def test_signed_commit(self):
289
import bzrlib.commit as commit
290
oldstrategy = bzrlib.gpg.GPGStrategy
291
branch = Branch.initialize('.')
292
branch.commit("base", allow_pointless=True, rev_id='A')
293
self.failIf(branch.revision_store.has_id('A', 'sig'))
295
from bzrlib.testament import Testament
296
# monkey patch gpg signing mechanism
297
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
298
commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
299
allow_pointless=True,
301
self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
302
branch.revision_store.get('B', 'sig').read())
304
bzrlib.gpg.GPGStrategy = oldstrategy
306
def test_commit_failed_signature(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
310
branch = Branch.initialize('.')
311
branch.commit("base", allow_pointless=True, rev_id='A')
312
self.failIf(branch.revision_store.has_id('A', 'sig'))
314
from bzrlib.testament import Testament
315
# monkey patch gpg signing mechanism
316
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
317
config = MustSignConfig(branch)
318
self.assertRaises(SigningFailed,
319
commit.Commit(config=config).commit,
321
allow_pointless=True,
323
branch = Branch.open('.')
324
self.assertEqual(branch.revision_history(), ['A'])
325
self.failIf(branch.revision_store.has_id('B'))
327
bzrlib.gpg.GPGStrategy = oldstrategy
329
def test_commit_invokes_hooks(self):
330
import bzrlib.commit as commit
331
branch = Branch.initialize('.')
333
def called(branch, rev_id):
334
calls.append('called')
335
bzrlib.ahook = called
337
config = BranchWithHooks(branch)
338
commit.Commit(config=config).commit(
340
allow_pointless=True,
342
self.assertEqual(['called', 'called'], calls)