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.bzrdir import BzrDir, BzrDirMetaFormat1
24
from bzrlib.workingtree import WorkingTree
25
from bzrlib.commit import Commit
26
from bzrlib.config import BranchConfig
27
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
31
# 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"
48
class TestCommit(TestCaseWithTransport):
50
def test_simple_commit(self):
51
"""Commit and check two versions of a single file."""
52
wt = self.make_branch_and_tree('.')
54
file('hello', 'w').write('hello world')
56
wt.commit(message='add hello')
57
file_id = wt.path2id('hello')
59
file('hello', 'w').write('version 2')
60
wt.commit(message='commit 2')
62
eq = self.assertEquals
64
rh = b.revision_history()
65
rev = b.repository.get_revision(rh[0])
66
eq(rev.message, 'add hello')
68
tree1 = b.repository.revision_tree(rh[0])
69
text = tree1.get_file_text(file_id)
70
eq(text, 'hello world')
72
tree2 = b.repository.revision_tree(rh[1])
73
eq(tree2.get_file_text(file_id), 'version 2')
75
def test_delete_commit(self):
76
"""Test a commit with a deleted file"""
77
wt = self.make_branch_and_tree('.')
79
file('hello', 'w').write('hello world')
80
wt.add(['hello'], ['hello-id'])
81
wt.commit(message='add hello')
84
wt.commit('removed hello', rev_id='rev2')
86
tree = b.repository.revision_tree('rev2')
87
self.assertFalse(tree.has_id('hello-id'))
89
def test_pointless_commit(self):
90
"""Commit refuses unless there are changes or it's forced."""
91
wt = self.make_branch_and_tree('.')
93
file('hello', 'w').write('hello')
95
wt.commit(message='add hello')
96
self.assertEquals(b.revno(), 1)
97
self.assertRaises(PointlessCommit,
100
allow_pointless=False)
101
self.assertEquals(b.revno(), 1)
103
def test_commit_empty(self):
104
"""Commiting an empty tree works."""
105
wt = self.make_branch_and_tree('.')
107
wt.commit(message='empty tree', allow_pointless=True)
108
self.assertRaises(PointlessCommit,
110
message='empty tree',
111
allow_pointless=False)
112
wt.commit(message='empty tree', allow_pointless=True)
113
self.assertEquals(b.revno(), 2)
115
def test_selective_delete(self):
116
"""Selective commit in tree with deletions"""
117
wt = self.make_branch_and_tree('.')
119
file('hello', 'w').write('hello')
120
file('buongia', 'w').write('buongia')
121
wt.add(['hello', 'buongia'],
122
['hello-id', 'buongia-id'])
123
wt.commit(message='add files',
127
file('buongia', 'w').write('new text')
128
wt.commit(message='update text',
129
specific_files=['buongia'],
130
allow_pointless=False,
133
wt.commit(message='remove hello',
134
specific_files=['hello'],
135
allow_pointless=False,
138
eq = self.assertEquals
141
tree2 = b.repository.revision_tree('test@rev-2')
142
self.assertTrue(tree2.has_filename('hello'))
143
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
144
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
146
tree3 = b.repository.revision_tree('test@rev-3')
147
self.assertFalse(tree3.has_filename('hello'))
148
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
150
def test_commit_rename(self):
151
"""Test commit of a revision where a file is renamed."""
152
tree = self.make_branch_and_tree('.')
154
self.build_tree(['hello'], line_endings='binary')
155
tree.add(['hello'], ['hello-id'])
156
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
158
tree.rename_one('hello', 'fruity')
159
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
161
eq = self.assertEquals
162
tree1 = b.repository.revision_tree('test@rev-1')
163
eq(tree1.id2path('hello-id'), 'hello')
164
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
165
self.assertFalse(tree1.has_filename('fruity'))
166
self.check_inventory_shape(tree1.inventory, ['hello'])
167
ie = tree1.inventory['hello-id']
168
eq(ie.revision, 'test@rev-1')
170
tree2 = b.repository.revision_tree('test@rev-2')
171
eq(tree2.id2path('hello-id'), 'fruity')
172
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
173
self.check_inventory_shape(tree2.inventory, ['fruity'])
174
ie = tree2.inventory['hello-id']
175
eq(ie.revision, 'test@rev-2')
177
def test_reused_rev_id(self):
178
"""Test that a revision id cannot be reused in a branch"""
179
wt = self.make_branch_and_tree('.')
181
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
182
self.assertRaises(Exception,
186
allow_pointless=True)
188
def test_commit_move(self):
189
"""Test commit of revisions with moved files and directories"""
190
eq = self.assertEquals
191
wt = self.make_branch_and_tree('.')
194
self.build_tree(['hello', 'a/', 'b/'])
195
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
196
wt.commit('initial', rev_id=r1, allow_pointless=False)
197
wt.move(['hello'], 'a')
199
wt.commit('two', rev_id=r2, allow_pointless=False)
200
self.check_inventory_shape(wt.read_working_inventory(),
201
['a', 'a/hello', 'b'])
205
wt.commit('three', rev_id=r3, allow_pointless=False)
206
self.check_inventory_shape(wt.read_working_inventory(),
207
['a', 'a/hello', 'a/b'])
208
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
209
['a', 'a/hello', 'a/b'])
211
wt.move(['a/hello'], 'a/b')
213
wt.commit('four', rev_id=r4, allow_pointless=False)
214
self.check_inventory_shape(wt.read_working_inventory(),
215
['a', 'a/b/hello', 'a/b'])
217
inv = b.repository.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)
222
def test_removed_commit(self):
223
"""Commit with a removed file"""
224
wt = self.make_branch_and_tree('.')
226
file('hello', 'w').write('hello world')
227
wt.add(['hello'], ['hello-id'])
228
wt.commit(message='add hello')
230
wt.commit('removed hello', rev_id='rev2')
232
tree = b.repository.revision_tree('rev2')
233
self.assertFalse(tree.has_id('hello-id'))
235
def test_committed_ancestry(self):
236
"""Test commit appends revisions to ancestry."""
237
wt = self.make_branch_and_tree('.')
241
file('hello', 'w').write((str(i) * 4) + '\n')
243
wt.add(['hello'], ['hello-id'])
244
rev_id = 'test@rev-%d' % (i+1)
245
rev_ids.append(rev_id)
246
wt.commit(message='rev %d' % (i+1),
248
eq = self.assertEquals
249
eq(b.revision_history(), rev_ids)
251
anc = b.repository.get_ancestry(rev_ids[i])
252
eq(anc, [None] + rev_ids[:i+1])
254
def test_commit_new_subdir_child_selective(self):
255
wt = self.make_branch_and_tree('.')
257
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
258
wt.add(['dir', 'dir/file1', 'dir/file2'],
259
['dirid', 'file1id', 'file2id'])
260
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
261
inv = b.repository.get_inventory('1')
262
self.assertEqual('1', inv['dirid'].revision)
263
self.assertEqual('1', inv['file1id'].revision)
264
# FIXME: This should raise a KeyError I think, rbc20051006
265
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
267
def test_strict_commit(self):
268
"""Try and commit with unknown files and strict = True, should fail."""
269
from bzrlib.errors import StrictCommitFailed
270
wt = self.make_branch_and_tree('.')
272
file('hello', 'w').write('hello world')
274
file('goodbye', 'w').write('goodbye cruel world!')
275
self.assertRaises(StrictCommitFailed, wt.commit,
276
message='add hello but not goodbye', strict=True)
278
def test_strict_commit_without_unknowns(self):
279
"""Try and commit with no unknown files and strict = True,
281
from bzrlib.errors import StrictCommitFailed
282
wt = self.make_branch_and_tree('.')
284
file('hello', 'w').write('hello world')
286
wt.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
wt = self.make_branch_and_tree('.')
292
file('hello', 'w').write('hello world')
294
file('goodbye', 'w').write('goodbye cruel world!')
295
wt.commit(message='add hello but not goodbye', strict=False)
297
def test_nonstrict_commit_without_unknowns(self):
298
"""Try and commit with no unknown files and strict = False,
300
wt = self.make_branch_and_tree('.')
302
file('hello', 'w').write('hello world')
304
wt.commit(message='add hello', strict=False)
306
def test_signed_commit(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
310
wt = self.make_branch_and_tree('.')
312
wt.commit("base", allow_pointless=True, rev_id='A')
313
self.failIf(branch.repository.has_signature_for_revision_id('A'))
315
from bzrlib.testament import Testament
316
# monkey patch gpg signing mechanism
317
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
318
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
319
allow_pointless=True,
322
self.assertEqual(Testament.from_revision(branch.repository,
323
'B').as_short_text(),
324
branch.repository.get_signature_text('B'))
326
bzrlib.gpg.GPGStrategy = oldstrategy
328
def test_commit_failed_signature(self):
330
import bzrlib.commit as commit
331
oldstrategy = bzrlib.gpg.GPGStrategy
332
wt = self.make_branch_and_tree('.')
334
wt.commit("base", allow_pointless=True, rev_id='A')
335
self.failIf(branch.repository.has_signature_for_revision_id('A'))
337
from bzrlib.testament import Testament
338
# monkey patch gpg signing mechanism
339
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
340
config = MustSignConfig(branch)
341
self.assertRaises(SigningFailed,
342
commit.Commit(config=config).commit,
344
allow_pointless=True,
347
branch = Branch.open(self.get_url('.'))
348
self.assertEqual(branch.revision_history(), ['A'])
349
self.failIf(branch.repository.has_revision('B'))
351
bzrlib.gpg.GPGStrategy = oldstrategy
353
def test_commit_invokes_hooks(self):
354
import bzrlib.commit as commit
355
wt = self.make_branch_and_tree('.')
358
def called(branch, rev_id):
359
calls.append('called')
360
bzrlib.ahook = called
362
config = BranchWithHooks(branch)
363
commit.Commit(config=config).commit(
365
allow_pointless=True,
366
rev_id='A', working_tree = wt)
367
self.assertEqual(['called', 'called'], calls)
371
def test_commit_object_doesnt_set_nick(self):
372
# using the Commit object directly does not set the branch nick.
373
wt = self.make_branch_and_tree('.')
375
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
376
self.assertEquals(wt.branch.revno(), 1)
378
wt.branch.repository.get_revision(
379
wt.branch.last_revision()).properties)
381
def test_safe_master_lock(self):
383
master = BzrDirMetaFormat1().initialize('master')
384
master.create_repository()
385
master_branch = master.create_branch()
386
master.create_workingtree()
387
bound = master.sprout('bound')
388
wt = bound.open_workingtree()
389
wt.branch.set_bound_location(os.path.realpath('master'))
390
master_branch.lock_write()
391
self.assertRaises(LockContention, wt.commit, 'silly')