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_strict_commit_without_unknowns(self):
280
"""Try and commit with no unknown files and strict = True,
282
from bzrlib.errors import StrictCommitFailed
283
b = Branch.initialize('.')
284
file('hello', 'w').write('hello world')
286
b.commit(message='add hello', strict=True)
288
def test_nonstrict_commit(self):
289
"""Try and commit with unknown files and strict = False, should work."""
290
b = Branch.initialize('.')
291
file('hello', 'w').write('hello world')
293
file('goodbye', 'w').write('goodbye cruel world!')
294
b.commit(message='add hello but not goodbye', strict=False)
296
def test_nonstrict_commit_without_unknowns(self):
297
"""Try and commit with no unknown files and strict = False,
299
b = Branch.initialize('.')
300
file('hello', 'w').write('hello world')
302
b.commit(message='add hello', strict=False)
304
def test_signed_commit(self):
306
import bzrlib.commit as commit
307
oldstrategy = bzrlib.gpg.GPGStrategy
308
branch = Branch.initialize('.')
309
branch.commit("base", allow_pointless=True, rev_id='A')
310
self.failIf(branch.revision_store.has_id('A', 'sig'))
312
from bzrlib.testament import Testament
313
# monkey patch gpg signing mechanism
314
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
315
commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
316
allow_pointless=True,
318
self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
319
branch.revision_store.get('B', 'sig').read())
321
bzrlib.gpg.GPGStrategy = oldstrategy
323
def test_commit_failed_signature(self):
325
import bzrlib.commit as commit
326
oldstrategy = bzrlib.gpg.GPGStrategy
327
branch = Branch.initialize('.')
328
branch.commit("base", allow_pointless=True, rev_id='A')
329
self.failIf(branch.revision_store.has_id('A', 'sig'))
331
from bzrlib.testament import Testament
332
# monkey patch gpg signing mechanism
333
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
334
config = MustSignConfig(branch)
335
self.assertRaises(SigningFailed,
336
commit.Commit(config=config).commit,
338
allow_pointless=True,
340
branch = Branch.open('.')
341
self.assertEqual(branch.revision_history(), ['A'])
342
self.failIf(branch.revision_store.has_id('B'))
344
bzrlib.gpg.GPGStrategy = oldstrategy
346
def test_commit_invokes_hooks(self):
347
import bzrlib.commit as commit
348
branch = Branch.initialize('.')
350
def called(branch, rev_id):
351
calls.append('called')
352
bzrlib.ahook = called
354
config = BranchWithHooks(branch)
355
commit.Commit(config=config).commit(
357
allow_pointless=True,
359
self.assertEqual(['called', 'called'], calls)