21
from bzrlib.tests import TestCaseWithTransport
20
from bzrlib.selftest import TestCaseInTempDir
22
21
from bzrlib.branch import Branch
23
from bzrlib.bzrdir import BzrDir, BzrDirMetaFormat1
24
from bzrlib.workingtree import WorkingTree
25
from bzrlib.commit import Commit, NullCommitReporter
26
from bzrlib.config import BranchConfig
27
from bzrlib.errors import (PointlessCommit, BzrError, SigningFailed,
22
from bzrlib.commit import Commit
23
from bzrlib.errors import PointlessCommit
31
26
# 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 CapturingReporter(NullCommitReporter):
49
"""This reporter captures the calls made to it for evaluation later."""
52
# a list of the calls this received
55
def snapshot_change(self, change, path):
56
self.calls.append(('change', change, path))
58
def deleted(self, file_id):
59
self.calls.append(('deleted', file_id))
61
def missing(self, path):
62
self.calls.append(('missing', path))
64
def renamed(self, change, old_path, new_path):
65
self.calls.append(('renamed', change, old_path, new_path))
68
class TestCommit(TestCaseWithTransport):
28
class TestCommit(TestCaseInTempDir):
70
29
def test_simple_commit(self):
71
30
"""Commit and check two versions of a single file."""
72
wt = self.make_branch_and_tree('.')
31
b = Branch('.', init=True)
74
32
file('hello', 'w').write('hello world')
76
wt.commit(message='add hello')
77
file_id = wt.path2id('hello')
34
b.commit(message='add hello')
35
file_id = b.working_tree().path2id('hello')
79
37
file('hello', 'w').write('version 2')
80
wt.commit(message='commit 2')
38
b.commit(message='commit 2')
82
40
eq = self.assertEquals
84
42
rh = b.revision_history()
85
rev = b.repository.get_revision(rh[0])
43
rev = b.get_revision(rh[0])
86
44
eq(rev.message, 'add hello')
88
tree1 = b.repository.revision_tree(rh[0])
46
tree1 = b.revision_tree(rh[0])
89
47
text = tree1.get_file_text(file_id)
90
48
eq(text, 'hello world')
92
tree2 = b.repository.revision_tree(rh[1])
50
tree2 = b.revision_tree(rh[1])
93
51
eq(tree2.get_file_text(file_id), 'version 2')
95
54
def test_delete_commit(self):
96
55
"""Test a commit with a deleted file"""
97
wt = self.make_branch_and_tree('.')
56
b = Branch('.', init=True)
99
57
file('hello', 'w').write('hello world')
100
wt.add(['hello'], ['hello-id'])
101
wt.commit(message='add hello')
58
b.add(['hello'], ['hello-id'])
59
b.commit(message='add hello')
103
61
os.remove('hello')
104
wt.commit('removed hello', rev_id='rev2')
62
b.commit('removed hello', rev_id='rev2')
106
tree = b.repository.revision_tree('rev2')
64
tree = b.revision_tree('rev2')
107
65
self.assertFalse(tree.has_id('hello-id'))
109
68
def test_pointless_commit(self):
110
69
"""Commit refuses unless there are changes or it's forced."""
111
wt = self.make_branch_and_tree('.')
70
b = Branch('.', init=True)
113
71
file('hello', 'w').write('hello')
115
wt.commit(message='add hello')
73
b.commit(message='add hello')
116
74
self.assertEquals(b.revno(), 1)
117
75
self.assertRaises(PointlessCommit,
120
78
allow_pointless=False)
121
79
self.assertEquals(b.revno(), 1)
123
83
def test_commit_empty(self):
124
84
"""Commiting an empty tree works."""
125
wt = self.make_branch_and_tree('.')
127
wt.commit(message='empty tree', allow_pointless=True)
85
b = Branch('.', init=True)
86
b.commit(message='empty tree', allow_pointless=True)
128
87
self.assertRaises(PointlessCommit,
130
89
message='empty tree',
131
90
allow_pointless=False)
132
wt.commit(message='empty tree', allow_pointless=True)
91
b.commit(message='empty tree', allow_pointless=True)
133
92
self.assertEquals(b.revno(), 2)
135
95
def test_selective_delete(self):
136
96
"""Selective commit in tree with deletions"""
137
wt = self.make_branch_and_tree('.')
97
b = Branch('.', init=True)
139
98
file('hello', 'w').write('hello')
140
99
file('buongia', 'w').write('buongia')
141
wt.add(['hello', 'buongia'],
100
b.add(['hello', 'buongia'],
142
101
['hello-id', 'buongia-id'])
143
wt.commit(message='add files',
102
b.commit(message='add files',
144
103
rev_id='test@rev-1')
146
105
os.remove('hello')
147
106
file('buongia', 'w').write('new text')
148
wt.commit(message='update text',
107
b.commit(message='update text',
149
108
specific_files=['buongia'],
150
109
allow_pointless=False,
151
110
rev_id='test@rev-2')
153
wt.commit(message='remove hello',
112
b.commit(message='remove hello',
154
113
specific_files=['hello'],
155
114
allow_pointless=False,
156
115
rev_id='test@rev-3')
158
117
eq = self.assertEquals
161
tree2 = b.repository.revision_tree('test@rev-2')
120
tree2 = b.revision_tree('test@rev-2')
162
121
self.assertTrue(tree2.has_filename('hello'))
163
122
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
164
123
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
166
tree3 = b.repository.revision_tree('test@rev-3')
125
tree3 = b.revision_tree('test@rev-3')
167
126
self.assertFalse(tree3.has_filename('hello'))
168
127
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
170
130
def test_commit_rename(self):
171
131
"""Test commit of a revision where a file is renamed."""
172
tree = self.make_branch_and_tree('.')
174
self.build_tree(['hello'], line_endings='binary')
175
tree.add(['hello'], ['hello-id'])
176
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
178
tree.rename_one('hello', 'fruity')
179
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
181
eq = self.assertEquals
182
tree1 = b.repository.revision_tree('test@rev-1')
183
eq(tree1.id2path('hello-id'), 'hello')
184
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
132
b = Branch('.', init=True)
133
self.build_tree(['hello'])
134
b.add(['hello'], ['hello-id'])
135
b.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
137
b.rename_one('hello', 'fruity')
138
b.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
140
tree1 = b.revision_tree('test@rev-1')
141
self.assertEquals(tree1.id2path('hello-id'), 'hello')
142
self.assertEquals(tree1.get_file_text('hello-id'), 'contents of hello\n')
185
143
self.assertFalse(tree1.has_filename('fruity'))
186
self.check_inventory_shape(tree1.inventory, ['hello'])
187
ie = tree1.inventory['hello-id']
188
eq(ie.revision, 'test@rev-1')
190
tree2 = b.repository.revision_tree('test@rev-2')
191
eq(tree2.id2path('hello-id'), 'fruity')
192
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
193
self.check_inventory_shape(tree2.inventory, ['fruity'])
194
ie = tree2.inventory['hello-id']
195
eq(ie.revision, 'test@rev-2')
197
def test_reused_rev_id(self):
198
"""Test that a revision id cannot be reused in a branch"""
199
wt = self.make_branch_and_tree('.')
201
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
202
self.assertRaises(Exception,
206
allow_pointless=True)
208
def test_commit_move(self):
209
"""Test commit of revisions with moved files and directories"""
210
eq = self.assertEquals
211
wt = self.make_branch_and_tree('.')
214
self.build_tree(['hello', 'a/', 'b/'])
215
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
216
wt.commit('initial', rev_id=r1, allow_pointless=False)
217
wt.move(['hello'], 'a')
219
wt.commit('two', rev_id=r2, allow_pointless=False)
220
self.check_inventory_shape(wt.read_working_inventory(),
221
['a', 'a/hello', 'b'])
225
wt.commit('three', rev_id=r3, allow_pointless=False)
226
self.check_inventory_shape(wt.read_working_inventory(),
227
['a', 'a/hello', 'a/b'])
228
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
229
['a', 'a/hello', 'a/b'])
231
wt.move(['a/hello'], 'a/b')
233
wt.commit('four', rev_id=r4, allow_pointless=False)
234
self.check_inventory_shape(wt.read_working_inventory(),
235
['a', 'a/b/hello', 'a/b'])
237
inv = b.repository.get_revision_inventory(r4)
238
eq(inv['hello-id'].revision, r4)
239
eq(inv['a-id'].revision, r1)
240
eq(inv['b-id'].revision, r3)
145
tree2 = b.revision_tree('test@rev-2')
146
self.assertEquals(tree2.id2path('hello-id'), 'fruity')
147
self.assertEquals(tree2.get_file_text('hello-id'), 'contents of hello\n')
148
self.assertFalse(tree2.has_filename('hello'))
242
151
def test_removed_commit(self):
243
"""Commit with a removed file"""
244
wt = self.make_branch_and_tree('.')
152
"""Test a commit with a removed file"""
153
b = Branch('.', init=True)
246
154
file('hello', 'w').write('hello world')
247
wt.add(['hello'], ['hello-id'])
248
wt.commit(message='add hello')
250
wt.commit('removed hello', rev_id='rev2')
252
tree = b.repository.revision_tree('rev2')
155
b.add(['hello'], ['hello-id'])
156
b.commit(message='add hello')
159
b.commit('removed hello', rev_id='rev2')
161
tree = b.revision_tree('rev2')
253
162
self.assertFalse(tree.has_id('hello-id'))
255
165
def test_committed_ancestry(self):
256
166
"""Test commit appends revisions to ancestry."""
257
wt = self.make_branch_and_tree('.')
167
b = Branch('.', init=True)
260
169
for i in range(4):
261
170
file('hello', 'w').write((str(i) * 4) + '\n')
263
wt.add(['hello'], ['hello-id'])
172
b.add(['hello'], ['hello-id'])
264
173
rev_id = 'test@rev-%d' % (i+1)
265
174
rev_ids.append(rev_id)
266
wt.commit(message='rev %d' % (i+1),
175
b.commit(message='rev %d' % (i+1),
268
177
eq = self.assertEquals
269
178
eq(b.revision_history(), rev_ids)
270
179
for i in range(4):
271
anc = b.repository.get_ancestry(rev_ids[i])
272
eq(anc, [None] + rev_ids[:i+1])
274
def test_commit_new_subdir_child_selective(self):
275
wt = self.make_branch_and_tree('.')
277
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
278
wt.add(['dir', 'dir/file1', 'dir/file2'],
279
['dirid', 'file1id', 'file2id'])
280
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
281
inv = b.repository.get_inventory('1')
282
self.assertEqual('1', inv['dirid'].revision)
283
self.assertEqual('1', inv['file1id'].revision)
284
# FIXME: This should raise a KeyError I think, rbc20051006
285
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
287
def test_strict_commit(self):
288
"""Try and commit with unknown files and strict = True, should fail."""
289
from bzrlib.errors import StrictCommitFailed
290
wt = self.make_branch_and_tree('.')
292
file('hello', 'w').write('hello world')
294
file('goodbye', 'w').write('goodbye cruel world!')
295
self.assertRaises(StrictCommitFailed, wt.commit,
296
message='add hello but not goodbye', strict=True)
298
def test_strict_commit_without_unknowns(self):
299
"""Try and commit with no unknown files and strict = True,
301
from bzrlib.errors import StrictCommitFailed
302
wt = self.make_branch_and_tree('.')
304
file('hello', 'w').write('hello world')
306
wt.commit(message='add hello', strict=True)
308
def test_nonstrict_commit(self):
309
"""Try and commit with unknown files and strict = False, should work."""
310
wt = self.make_branch_and_tree('.')
312
file('hello', 'w').write('hello world')
314
file('goodbye', 'w').write('goodbye cruel world!')
315
wt.commit(message='add hello but not goodbye', strict=False)
317
def test_nonstrict_commit_without_unknowns(self):
318
"""Try and commit with no unknown files and strict = False,
320
wt = self.make_branch_and_tree('.')
322
file('hello', 'w').write('hello world')
324
wt.commit(message='add hello', strict=False)
326
def test_signed_commit(self):
328
import bzrlib.commit as commit
329
oldstrategy = bzrlib.gpg.GPGStrategy
330
wt = self.make_branch_and_tree('.')
332
wt.commit("base", allow_pointless=True, rev_id='A')
333
self.failIf(branch.repository.has_signature_for_revision_id('A'))
335
from bzrlib.testament import Testament
336
# monkey patch gpg signing mechanism
337
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
338
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
339
allow_pointless=True,
342
self.assertEqual(Testament.from_revision(branch.repository,
343
'B').as_short_text(),
344
branch.repository.get_signature_text('B'))
346
bzrlib.gpg.GPGStrategy = oldstrategy
348
def test_commit_failed_signature(self):
350
import bzrlib.commit as commit
351
oldstrategy = bzrlib.gpg.GPGStrategy
352
wt = self.make_branch_and_tree('.')
354
wt.commit("base", allow_pointless=True, rev_id='A')
355
self.failIf(branch.repository.has_signature_for_revision_id('A'))
357
from bzrlib.testament import Testament
358
# monkey patch gpg signing mechanism
359
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
360
config = MustSignConfig(branch)
361
self.assertRaises(SigningFailed,
362
commit.Commit(config=config).commit,
364
allow_pointless=True,
367
branch = Branch.open(self.get_url('.'))
368
self.assertEqual(branch.revision_history(), ['A'])
369
self.failIf(branch.repository.has_revision('B'))
371
bzrlib.gpg.GPGStrategy = oldstrategy
373
def test_commit_invokes_hooks(self):
374
import bzrlib.commit as commit
375
wt = self.make_branch_and_tree('.')
378
def called(branch, rev_id):
379
calls.append('called')
380
bzrlib.ahook = called
382
config = BranchWithHooks(branch)
383
commit.Commit(config=config).commit(
385
allow_pointless=True,
386
rev_id='A', working_tree = wt)
387
self.assertEqual(['called', 'called'], calls)
391
def test_commit_object_doesnt_set_nick(self):
392
# using the Commit object directly does not set the branch nick.
393
wt = self.make_branch_and_tree('.')
395
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
396
self.assertEquals(wt.branch.revno(), 1)
398
wt.branch.repository.get_revision(
399
wt.branch.last_revision()).properties)
401
def test_safe_master_lock(self):
403
master = BzrDirMetaFormat1().initialize('master')
404
master.create_repository()
405
master_branch = master.create_branch()
406
master.create_workingtree()
407
bound = master.sprout('bound')
408
wt = bound.open_workingtree()
409
wt.branch.set_bound_location(os.path.realpath('master'))
410
master_branch.lock_write()
412
self.assertRaises(LockContention, wt.commit, 'silly')
414
master_branch.unlock()
416
def test_commit_bound_merge(self):
417
# see bug #43959; commit of a merge in a bound branch fails to push
418
# the new commit into the master
419
master_branch = self.make_branch('master')
420
bound_tree = self.make_branch_and_tree('bound')
421
bound_tree.branch.bind(master_branch)
423
self.build_tree_contents([('bound/content_file', 'initial contents\n')])
424
bound_tree.add(['content_file'])
425
bound_tree.commit(message='woo!')
427
other_bzrdir = master_branch.bzrdir.sprout('other')
428
other_tree = other_bzrdir.open_workingtree()
430
# do a commit to the the other branch changing the content file so
431
# that our commit after merging will have a merged revision in the
432
# content file history.
433
self.build_tree_contents([('other/content_file', 'change in other\n')])
434
other_tree.commit('change in other')
436
# do a merge into the bound branch from other, and then change the
437
# content file locally to force a new revision (rather than using the
438
# revision from other). This forces extra processing in commit.
439
self.merge(other_tree.branch, bound_tree)
440
self.build_tree_contents([('bound/content_file', 'change in bound\n')])
442
# before #34959 was fixed, this failed with 'revision not present in
443
# weave' when trying to implicitly push from the bound branch to the master
444
bound_tree.commit(message='commit of merge in bound tree')
446
def test_commit_reporting_after_merge(self):
447
# when doing a commit of a merge, the reporter needs to still
448
# be called for each item that is added/removed/deleted.
449
this_tree = self.make_branch_and_tree('this')
450
# we need a bunch of files and dirs, to perform one action on each.
453
'this/dirtoreparent/',
456
'this/filetoreparent',
473
this_tree.commit('create_files')
474
other_dir = this_tree.bzrdir.sprout('other')
475
other_tree = other_dir.open_workingtree()
476
other_tree.lock_write()
477
# perform the needed actions on the files and dirs.
479
other_tree.rename_one('dirtorename', 'renameddir')
480
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
481
other_tree.rename_one('filetorename', 'renamedfile')
482
other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
483
other_tree.remove(['dirtoremove', 'filetoremove'])
484
self.build_tree_contents([
486
('other/filetomodify', 'new content'),
487
('other/newfile', 'new file content')])
488
other_tree.add('newfile')
489
other_tree.add('newdir/')
490
other_tree.commit('modify all sample files and dirs.')
493
self.merge(other_tree.branch, this_tree)
494
reporter = CapturingReporter()
495
this_tree.commit('do the commit', reporter=reporter)
497
('change', 'unchanged', 'dirtoleave'),
498
('change', 'unchanged', 'filetoleave'),
499
('change', 'modified', 'filetomodify'),
500
('change', 'added', 'newdir'),
501
('change', 'added', 'newfile'),
502
('renamed', 'renamed', 'dirtorename', 'renameddir'),
503
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
504
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
505
('renamed', 'renamed', 'filetorename', 'renamedfile'),
506
('deleted', 'dirtoremove'),
507
('deleted', 'filetoremove'),
180
anc = b.get_ancestry(rev_ids[i])
181
eq(anc, rev_ids[:i+1])
186
if __name__ == '__main__':