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 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(u'.')
51
file('hello', 'w').write('hello world')
52
b.working_tree().add('hello')
53
b.working_tree().commit(message='add hello')
54
file_id = b.working_tree().path2id('hello')
56
file('hello', 'w').write('version 2')
57
b.working_tree().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(u'.')
75
file('hello', 'w').write('hello world')
76
b.working_tree().add(['hello'], ['hello-id'])
77
b.working_tree().commit(message='add hello')
80
b.working_tree().commit('removed hello', rev_id='rev2')
82
tree = b.revision_tree('rev2')
83
self.assertFalse(tree.has_id('hello-id'))
85
def test_pointless_commit(self):
86
"""Commit refuses unless there are changes or it's forced."""
87
b = Branch.initialize(u'.')
88
file('hello', 'w').write('hello')
89
b.working_tree().add(['hello'])
90
b.working_tree().commit(message='add hello')
91
self.assertEquals(b.revno(), 1)
92
self.assertRaises(PointlessCommit,
93
b.working_tree().commit,
95
allow_pointless=False)
96
self.assertEquals(b.revno(), 1)
98
def test_commit_empty(self):
99
"""Commiting an empty tree works."""
100
b = Branch.initialize(u'.')
101
b.working_tree().commit(message='empty tree', allow_pointless=True)
102
self.assertRaises(PointlessCommit,
103
b.working_tree().commit,
104
message='empty tree',
105
allow_pointless=False)
106
b.working_tree().commit(message='empty tree', allow_pointless=True)
107
self.assertEquals(b.revno(), 2)
110
def test_selective_delete(self):
111
"""Selective commit in tree with deletions"""
112
b = Branch.initialize(u'.')
113
file('hello', 'w').write('hello')
114
file('buongia', 'w').write('buongia')
115
b.working_tree().add(['hello', 'buongia'],
116
['hello-id', 'buongia-id'])
117
b.working_tree().commit(message='add files',
121
file('buongia', 'w').write('new text')
122
b.working_tree().commit(message='update text',
123
specific_files=['buongia'],
124
allow_pointless=False,
127
b.working_tree().commit(message='remove hello',
128
specific_files=['hello'],
129
allow_pointless=False,
132
eq = self.assertEquals
135
tree2 = b.revision_tree('test@rev-2')
136
self.assertTrue(tree2.has_filename('hello'))
137
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
138
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
140
tree3 = b.revision_tree('test@rev-3')
141
self.assertFalse(tree3.has_filename('hello'))
142
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
145
def test_commit_rename(self):
146
"""Test commit of a revision where a file is renamed."""
147
b = Branch.initialize(u'.')
148
tree = WorkingTree(u'.', b)
149
self.build_tree(['hello'], line_endings='binary')
150
tree.add(['hello'], ['hello-id'])
151
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
153
tree.rename_one('hello', 'fruity')
154
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
156
eq = self.assertEquals
157
tree1 = b.revision_tree('test@rev-1')
158
eq(tree1.id2path('hello-id'), 'hello')
159
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
160
self.assertFalse(tree1.has_filename('fruity'))
161
self.check_inventory_shape(tree1.inventory, ['hello'])
162
ie = tree1.inventory['hello-id']
163
eq(ie.revision, 'test@rev-1')
165
tree2 = b.revision_tree('test@rev-2')
166
eq(tree2.id2path('hello-id'), 'fruity')
167
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
168
self.check_inventory_shape(tree2.inventory, ['fruity'])
169
ie = tree2.inventory['hello-id']
170
eq(ie.revision, 'test@rev-2')
172
def test_reused_rev_id(self):
173
"""Test that a revision id cannot be reused in a branch"""
174
b = Branch.initialize(u'.')
175
b.working_tree().commit('initial', rev_id='test@rev-1', allow_pointless=True)
176
self.assertRaises(Exception,
177
b.working_tree().commit,
180
allow_pointless=True)
182
def test_commit_move(self):
183
"""Test commit of revisions with moved files and directories"""
184
eq = self.assertEquals
185
b = Branch.initialize(u'.')
187
self.build_tree(['hello', 'a/', 'b/'])
188
b.working_tree().add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
189
b.working_tree().commit('initial', rev_id=r1, allow_pointless=False)
190
b.working_tree().move(['hello'], 'a')
192
b.working_tree().commit('two', rev_id=r2, allow_pointless=False)
193
self.check_inventory_shape(b.working_tree().read_working_inventory(),
194
['a', 'a/hello', 'b'])
196
b.working_tree().move(['b'], 'a')
198
b.working_tree().commit('three', rev_id=r3, allow_pointless=False)
199
self.check_inventory_shape(b.working_tree().read_working_inventory(),
200
['a', 'a/hello', 'a/b'])
201
self.check_inventory_shape(b.get_revision_inventory(r3),
202
['a', 'a/hello', 'a/b'])
204
b.working_tree().move([os.sep.join(['a', 'hello'])],
205
os.sep.join(['a', 'b']))
207
b.working_tree().commit('four', rev_id=r4, allow_pointless=False)
208
self.check_inventory_shape(b.working_tree().read_working_inventory(),
209
['a', 'a/b/hello', 'a/b'])
211
inv = b.get_revision_inventory(r4)
212
eq(inv['hello-id'].revision, r4)
213
eq(inv['a-id'].revision, r1)
214
eq(inv['b-id'].revision, r3)
216
def test_removed_commit(self):
217
"""Commit with a removed file"""
218
b = Branch.initialize(u'.')
219
wt = b.working_tree()
220
file('hello', 'w').write('hello world')
221
b.working_tree().add(['hello'], ['hello-id'])
222
b.working_tree().commit(message='add hello')
224
wt = b.working_tree() # FIXME: kludge for aliasing of working inventory
226
b.working_tree().commit('removed hello', rev_id='rev2')
228
tree = b.revision_tree('rev2')
229
self.assertFalse(tree.has_id('hello-id'))
232
def test_committed_ancestry(self):
233
"""Test commit appends revisions to ancestry."""
234
b = Branch.initialize(u'.')
237
file('hello', 'w').write((str(i) * 4) + '\n')
239
b.working_tree().add(['hello'], ['hello-id'])
240
rev_id = 'test@rev-%d' % (i+1)
241
rev_ids.append(rev_id)
242
b.working_tree().commit(message='rev %d' % (i+1),
244
eq = self.assertEquals
245
eq(b.revision_history(), rev_ids)
247
anc = b.get_ancestry(rev_ids[i])
248
eq(anc, [None] + rev_ids[:i+1])
250
def test_commit_new_subdir_child_selective(self):
251
b = Branch.initialize(u'.')
252
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
253
b.working_tree().add(['dir', 'dir/file1', 'dir/file2'],
254
['dirid', 'file1id', 'file2id'])
255
b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
256
inv = b.get_inventory('1')
257
self.assertEqual('1', inv['dirid'].revision)
258
self.assertEqual('1', inv['file1id'].revision)
259
# FIXME: This should raise a KeyError I think, rbc20051006
260
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
262
def test_strict_commit(self):
263
"""Try and commit with unknown files and strict = True, should fail."""
264
from bzrlib.errors import StrictCommitFailed
265
b = Branch.initialize(u'.')
266
file('hello', 'w').write('hello world')
267
b.working_tree().add('hello')
268
file('goodbye', 'w').write('goodbye cruel world!')
269
self.assertRaises(StrictCommitFailed, b.working_tree().commit,
270
message='add hello but not goodbye', strict=True)
272
def test_strict_commit_without_unknowns(self):
273
"""Try and commit with no unknown files and strict = True,
275
from bzrlib.errors import StrictCommitFailed
276
b = Branch.initialize(u'.')
277
file('hello', 'w').write('hello world')
278
b.working_tree().add('hello')
279
b.working_tree().commit(message='add hello', strict=True)
281
def test_nonstrict_commit(self):
282
"""Try and commit with unknown files and strict = False, should work."""
283
b = Branch.initialize(u'.')
284
file('hello', 'w').write('hello world')
285
b.working_tree().add('hello')
286
file('goodbye', 'w').write('goodbye cruel world!')
287
b.working_tree().commit(message='add hello but not goodbye', strict=False)
289
def test_nonstrict_commit_without_unknowns(self):
290
"""Try and commit with no unknown files and strict = False,
292
b = Branch.initialize(u'.')
293
file('hello', 'w').write('hello world')
294
b.working_tree().add('hello')
295
b.working_tree().commit(message='add hello', strict=False)
297
def test_signed_commit(self):
299
import bzrlib.commit as commit
300
oldstrategy = bzrlib.gpg.GPGStrategy
301
branch = Branch.initialize(u'.')
302
branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
303
self.failIf(branch.revision_store.has_id('A', 'sig'))
305
from bzrlib.testament import Testament
306
# monkey patch gpg signing mechanism
307
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
308
commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
309
allow_pointless=True,
311
self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
312
branch.revision_store.get('B', 'sig').read())
314
bzrlib.gpg.GPGStrategy = oldstrategy
316
def test_commit_failed_signature(self):
318
import bzrlib.commit as commit
319
oldstrategy = bzrlib.gpg.GPGStrategy
320
branch = Branch.initialize(u'.')
321
branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
322
self.failIf(branch.revision_store.has_id('A', 'sig'))
324
from bzrlib.testament import Testament
325
# monkey patch gpg signing mechanism
326
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
327
config = MustSignConfig(branch)
328
self.assertRaises(SigningFailed,
329
commit.Commit(config=config).commit,
331
allow_pointless=True,
333
branch = Branch.open(u'.')
334
self.assertEqual(branch.revision_history(), ['A'])
335
self.failIf(branch.revision_store.has_id('B'))
337
bzrlib.gpg.GPGStrategy = oldstrategy
339
def test_commit_invokes_hooks(self):
340
import bzrlib.commit as commit
341
branch = Branch.initialize(u'.')
343
def called(branch, rev_id):
344
calls.append('called')
345
bzrlib.ahook = called
347
config = BranchWithHooks(branch)
348
commit.Commit(config=config).commit(
350
allow_pointless=True,
352
self.assertEqual(['called', 'called'], calls)