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.tests import TestCaseWithTransport
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(TestCaseWithTransport):
48
def test_simple_commit(self):
49
"""Commit and check two versions of a single file."""
50
wt = self.make_branch_and_tree('.')
52
file('hello', 'w').write('hello world')
54
wt.commit(message='add hello')
55
file_id = wt.path2id('hello')
57
file('hello', 'w').write('version 2')
58
wt.commit(message='commit 2')
60
eq = self.assertEquals
62
rh = b.revision_history()
63
rev = b.repository.get_revision(rh[0])
64
eq(rev.message, 'add hello')
66
tree1 = b.repository.revision_tree(rh[0])
67
text = tree1.get_file_text(file_id)
68
eq(text, 'hello world')
70
tree2 = b.repository.revision_tree(rh[1])
71
eq(tree2.get_file_text(file_id), 'version 2')
73
def test_delete_commit(self):
74
"""Test a commit with a deleted file"""
75
wt = self.make_branch_and_tree('.')
77
file('hello', 'w').write('hello world')
78
wt.add(['hello'], ['hello-id'])
79
wt.commit(message='add hello')
82
wt.commit('removed hello', rev_id='rev2')
84
tree = b.repository.revision_tree('rev2')
85
self.assertFalse(tree.has_id('hello-id'))
87
def test_pointless_commit(self):
88
"""Commit refuses unless there are changes or it's forced."""
89
wt = self.make_branch_and_tree('.')
91
file('hello', 'w').write('hello')
93
wt.commit(message='add hello')
94
self.assertEquals(b.revno(), 1)
95
self.assertRaises(PointlessCommit,
98
allow_pointless=False)
99
self.assertEquals(b.revno(), 1)
101
def test_commit_empty(self):
102
"""Commiting an empty tree works."""
103
wt = self.make_branch_and_tree('.')
105
wt.commit(message='empty tree', allow_pointless=True)
106
self.assertRaises(PointlessCommit,
108
message='empty tree',
109
allow_pointless=False)
110
wt.commit(message='empty tree', allow_pointless=True)
111
self.assertEquals(b.revno(), 2)
113
def test_selective_delete(self):
114
"""Selective commit in tree with deletions"""
115
wt = self.make_branch_and_tree('.')
117
file('hello', 'w').write('hello')
118
file('buongia', 'w').write('buongia')
119
wt.add(['hello', 'buongia'],
120
['hello-id', 'buongia-id'])
121
wt.commit(message='add files',
125
file('buongia', 'w').write('new text')
126
wt.commit(message='update text',
127
specific_files=['buongia'],
128
allow_pointless=False,
131
wt.commit(message='remove hello',
132
specific_files=['hello'],
133
allow_pointless=False,
136
eq = self.assertEquals
139
tree2 = b.repository.revision_tree('test@rev-2')
140
self.assertTrue(tree2.has_filename('hello'))
141
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
142
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
144
tree3 = b.repository.revision_tree('test@rev-3')
145
self.assertFalse(tree3.has_filename('hello'))
146
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
tree = self.make_branch_and_tree('.')
152
self.build_tree(['hello'], line_endings='binary')
153
tree.add(['hello'], ['hello-id'])
154
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
156
tree.rename_one('hello', 'fruity')
157
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
159
eq = self.assertEquals
160
tree1 = b.repository.revision_tree('test@rev-1')
161
eq(tree1.id2path('hello-id'), 'hello')
162
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
163
self.assertFalse(tree1.has_filename('fruity'))
164
self.check_inventory_shape(tree1.inventory, ['hello'])
165
ie = tree1.inventory['hello-id']
166
eq(ie.revision, 'test@rev-1')
168
tree2 = b.repository.revision_tree('test@rev-2')
169
eq(tree2.id2path('hello-id'), 'fruity')
170
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
171
self.check_inventory_shape(tree2.inventory, ['fruity'])
172
ie = tree2.inventory['hello-id']
173
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
wt = self.make_branch_and_tree('.')
179
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
180
self.assertRaises(Exception,
181
b.working_tree().commit,
184
allow_pointless=True)
186
def test_commit_move(self):
187
"""Test commit of revisions with moved files and directories"""
188
eq = self.assertEquals
189
wt = self.make_branch_and_tree('.')
192
self.build_tree(['hello', 'a/', 'b/'])
193
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
194
wt.commit('initial', rev_id=r1, allow_pointless=False)
195
wt.move(['hello'], 'a')
197
wt.commit('two', rev_id=r2, allow_pointless=False)
198
self.check_inventory_shape(b.working_tree().read_working_inventory(),
199
['a', 'a/hello', 'b'])
203
wt.commit('three', rev_id=r3, allow_pointless=False)
204
self.check_inventory_shape(wt.read_working_inventory(),
205
['a', 'a/hello', 'a/b'])
206
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
207
['a', 'a/hello', 'a/b'])
209
wt.move(['a/hello'], 'a/b')
211
wt.commit('four', rev_id=r4, allow_pointless=False)
212
self.check_inventory_shape(wt.read_working_inventory(),
213
['a', 'a/b/hello', 'a/b'])
215
inv = b.repository.get_revision_inventory(r4)
216
eq(inv['hello-id'].revision, r4)
217
eq(inv['a-id'].revision, r1)
218
eq(inv['b-id'].revision, r3)
220
def test_removed_commit(self):
221
"""Commit with a removed file"""
222
wt = self.make_branch_and_tree('.')
224
file('hello', 'w').write('hello world')
225
wt.add(['hello'], ['hello-id'])
226
wt.commit(message='add hello')
228
wt.commit('removed hello', rev_id='rev2')
230
tree = b.repository.revision_tree('rev2')
231
self.assertFalse(tree.has_id('hello-id'))
233
def test_committed_ancestry(self):
234
"""Test commit appends revisions to ancestry."""
235
wt = self.make_branch_and_tree('.')
239
file('hello', 'w').write((str(i) * 4) + '\n')
241
wt.add(['hello'], ['hello-id'])
242
rev_id = 'test@rev-%d' % (i+1)
243
rev_ids.append(rev_id)
244
wt.commit(message='rev %d' % (i+1),
246
eq = self.assertEquals
247
eq(b.revision_history(), rev_ids)
249
anc = b.repository.get_ancestry(rev_ids[i])
250
eq(anc, [None] + rev_ids[:i+1])
252
def test_commit_new_subdir_child_selective(self):
253
wt = self.make_branch_and_tree('.')
255
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
256
wt.add(['dir', 'dir/file1', 'dir/file2'],
257
['dirid', 'file1id', 'file2id'])
258
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
259
inv = b.repository.get_inventory('1')
260
self.assertEqual('1', inv['dirid'].revision)
261
self.assertEqual('1', inv['file1id'].revision)
262
# FIXME: This should raise a KeyError I think, rbc20051006
263
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
265
def test_strict_commit(self):
266
"""Try and commit with unknown files and strict = True, should fail."""
267
from bzrlib.errors import StrictCommitFailed
268
wt = self.make_branch_and_tree('.')
270
file('hello', 'w').write('hello world')
272
file('goodbye', 'w').write('goodbye cruel world!')
273
self.assertRaises(StrictCommitFailed, b.working_tree().commit,
274
message='add hello but not goodbye', strict=True)
276
def test_strict_commit_without_unknowns(self):
277
"""Try and commit with no unknown files and strict = True,
279
from bzrlib.errors import StrictCommitFailed
280
wt = self.make_branch_and_tree('.')
282
file('hello', 'w').write('hello world')
284
wt.commit(message='add hello', strict=True)
286
def test_nonstrict_commit(self):
287
"""Try and commit with unknown files and strict = False, should work."""
288
wt = self.make_branch_and_tree('.')
290
file('hello', 'w').write('hello world')
292
file('goodbye', 'w').write('goodbye cruel world!')
293
wt.commit(message='add hello but not goodbye', strict=False)
295
def test_nonstrict_commit_without_unknowns(self):
296
"""Try and commit with no unknown files and strict = False,
298
wt = self.make_branch_and_tree('.')
300
file('hello', 'w').write('hello world')
302
wt.commit(message='add hello', strict=False)
304
def test_signed_commit(self):
306
import bzrlib.commit as commit
307
oldstrategy = bzrlib.gpg.GPGStrategy
308
wt = self.make_branch_and_tree('.')
310
wt.commit("base", allow_pointless=True, rev_id='A')
311
self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
313
from bzrlib.testament import Testament
314
# monkey patch gpg signing mechanism
315
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
316
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
317
allow_pointless=True,
320
self.assertEqual(Testament.from_revision(branch.repository,
321
'B').as_short_text(),
322
branch.repository.revision_store.get('B',
325
bzrlib.gpg.GPGStrategy = oldstrategy
327
def test_commit_failed_signature(self):
329
import bzrlib.commit as commit
330
oldstrategy = bzrlib.gpg.GPGStrategy
331
wt = self.make_branch_and_tree('.')
333
wt.commit("base", allow_pointless=True, rev_id='A')
334
self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
336
from bzrlib.testament import Testament
337
# monkey patch gpg signing mechanism
338
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
339
config = MustSignConfig(branch)
340
self.assertRaises(SigningFailed,
341
commit.Commit(config=config).commit,
343
allow_pointless=True,
345
branch = Branch.open(self.get_url('.'))
346
self.assertEqual(branch.revision_history(), ['A'])
347
self.failIf(branch.repository.revision_store.has_id('B'))
349
bzrlib.gpg.GPGStrategy = oldstrategy
351
def test_commit_invokes_hooks(self):
352
import bzrlib.commit as commit
353
wt = self.make_branch_and_tree('.')
356
def called(branch, rev_id):
357
calls.append('called')
358
bzrlib.ahook = called
360
config = BranchWithHooks(branch)
361
commit.Commit(config=config).commit(
363
allow_pointless=True,
364
rev_id='A', working_tree = wt)
365
self.assertEqual(['called', 'called'], calls)