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.repository.get_revision(rh[0])
63
eq(rev.message, 'add hello')
65
tree1 = b.repository.revision_tree(rh[0])
66
text = tree1.get_file_text(file_id)
67
eq(text, 'hello world')
69
tree2 = b.repository.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.repository.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.repository.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.repository.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.repository.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.repository.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.repository.get_revision_inventory(r3),
202
['a', 'a/hello', 'a/b'])
204
b.working_tree().move(['a/hello'], 'a/b')
206
b.working_tree().commit('four', rev_id=r4, allow_pointless=False)
207
self.check_inventory_shape(b.working_tree().read_working_inventory(),
208
['a', 'a/b/hello', 'a/b'])
210
inv = b.repository.get_revision_inventory(r4)
211
eq(inv['hello-id'].revision, r4)
212
eq(inv['a-id'].revision, r1)
213
eq(inv['b-id'].revision, r3)
215
def test_removed_commit(self):
216
"""Commit with a removed file"""
217
b = Branch.initialize(u'.')
218
wt = b.working_tree()
219
file('hello', 'w').write('hello world')
220
b.working_tree().add(['hello'], ['hello-id'])
221
b.working_tree().commit(message='add hello')
223
wt = b.working_tree() # FIXME: kludge for aliasing of working inventory
225
b.working_tree().commit('removed hello', rev_id='rev2')
227
tree = b.repository.revision_tree('rev2')
228
self.assertFalse(tree.has_id('hello-id'))
231
def test_committed_ancestry(self):
232
"""Test commit appends revisions to ancestry."""
233
b = Branch.initialize(u'.')
236
file('hello', 'w').write((str(i) * 4) + '\n')
238
b.working_tree().add(['hello'], ['hello-id'])
239
rev_id = 'test@rev-%d' % (i+1)
240
rev_ids.append(rev_id)
241
b.working_tree().commit(message='rev %d' % (i+1),
243
eq = self.assertEquals
244
eq(b.revision_history(), rev_ids)
246
anc = b.repository.get_ancestry(rev_ids[i])
247
eq(anc, [None] + rev_ids[:i+1])
249
def test_commit_new_subdir_child_selective(self):
250
b = Branch.initialize(u'.')
251
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
252
b.working_tree().add(['dir', 'dir/file1', 'dir/file2'],
253
['dirid', 'file1id', 'file2id'])
254
b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
255
inv = b.repository.get_inventory('1')
256
self.assertEqual('1', inv['dirid'].revision)
257
self.assertEqual('1', inv['file1id'].revision)
258
# FIXME: This should raise a KeyError I think, rbc20051006
259
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
261
def test_strict_commit(self):
262
"""Try and commit with unknown files and strict = True, should fail."""
263
from bzrlib.errors import StrictCommitFailed
264
b = Branch.initialize(u'.')
265
file('hello', 'w').write('hello world')
266
b.working_tree().add('hello')
267
file('goodbye', 'w').write('goodbye cruel world!')
268
self.assertRaises(StrictCommitFailed, b.working_tree().commit,
269
message='add hello but not goodbye', strict=True)
271
def test_strict_commit_without_unknowns(self):
272
"""Try and commit with no unknown files and strict = True,
274
from bzrlib.errors import StrictCommitFailed
275
b = Branch.initialize(u'.')
276
file('hello', 'w').write('hello world')
277
b.working_tree().add('hello')
278
b.working_tree().commit(message='add hello', strict=True)
280
def test_nonstrict_commit(self):
281
"""Try and commit with unknown files and strict = False, should work."""
282
b = Branch.initialize(u'.')
283
file('hello', 'w').write('hello world')
284
b.working_tree().add('hello')
285
file('goodbye', 'w').write('goodbye cruel world!')
286
b.working_tree().commit(message='add hello but not goodbye', strict=False)
288
def test_nonstrict_commit_without_unknowns(self):
289
"""Try and commit with no unknown files and strict = False,
291
b = Branch.initialize(u'.')
292
file('hello', 'w').write('hello world')
293
b.working_tree().add('hello')
294
b.working_tree().commit(message='add hello', strict=False)
296
def test_signed_commit(self):
298
import bzrlib.commit as commit
299
oldstrategy = bzrlib.gpg.GPGStrategy
300
branch = Branch.initialize(u'.')
301
branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
302
self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
304
from bzrlib.testament import Testament
305
# monkey patch gpg signing mechanism
306
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
307
commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
308
allow_pointless=True,
310
self.assertEqual(Testament.from_revision(branch.repository,
311
'B').as_short_text(),
312
branch.repository.revision_store.get('B',
315
bzrlib.gpg.GPGStrategy = oldstrategy
317
def test_commit_failed_signature(self):
319
import bzrlib.commit as commit
320
oldstrategy = bzrlib.gpg.GPGStrategy
321
branch = Branch.initialize(u'.')
322
branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
323
self.failIf(branch.repository.revision_store.has_id('A', 'sig'))
325
from bzrlib.testament import Testament
326
# monkey patch gpg signing mechanism
327
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
328
config = MustSignConfig(branch)
329
self.assertRaises(SigningFailed,
330
commit.Commit(config=config).commit,
332
allow_pointless=True,
334
branch = Branch.open(u'.')
335
self.assertEqual(branch.revision_history(), ['A'])
336
self.failIf(branch.repository.revision_store.has_id('B'))
338
bzrlib.gpg.GPGStrategy = oldstrategy
340
def test_commit_invokes_hooks(self):
341
import bzrlib.commit as commit
342
branch = Branch.initialize(u'.')
344
def called(branch, rev_id):
345
calls.append('called')
346
bzrlib.ahook = called
348
config = BranchWithHooks(branch)
349
commit.Commit(config=config).commit(
351
allow_pointless=True,
353
self.assertEqual(['called', 'called'], calls)