43
46
return "bzrlib.ahook bzrlib.ahook"
46
class TestCommit(TestCaseInTempDir):
49
class CapturingReporter(NullCommitReporter):
50
"""This reporter captures the calls made to it for evaluation later."""
53
# a list of the calls this received
56
def snapshot_change(self, change, path):
57
self.calls.append(('change', change, path))
59
def deleted(self, file_id):
60
self.calls.append(('deleted', file_id))
62
def missing(self, path):
63
self.calls.append(('missing', path))
65
def renamed(self, change, old_path, new_path):
66
self.calls.append(('renamed', change, old_path, new_path))
69
class TestCommit(TestCaseWithTransport):
48
71
def test_simple_commit(self):
49
72
"""Commit and check two versions of a single file."""
50
b = Branch.initialize('.')
73
wt = self.make_branch_and_tree('.')
51
75
file('hello', 'w').write('hello world')
53
b.working_tree().commit(message='add hello')
54
file_id = b.working_tree().path2id('hello')
77
wt.commit(message='add hello')
78
file_id = wt.path2id('hello')
56
80
file('hello', 'w').write('version 2')
57
b.working_tree().commit(message='commit 2')
81
wt.commit(message='commit 2')
59
83
eq = self.assertEquals
61
85
rh = b.revision_history()
62
rev = b.get_revision(rh[0])
86
rev = b.repository.get_revision(rh[0])
63
87
eq(rev.message, 'add hello')
65
tree1 = b.revision_tree(rh[0])
89
tree1 = b.repository.revision_tree(rh[0])
66
90
text = tree1.get_file_text(file_id)
67
91
eq(text, 'hello world')
69
tree2 = b.revision_tree(rh[1])
93
tree2 = b.repository.revision_tree(rh[1])
70
94
eq(tree2.get_file_text(file_id), 'version 2')
72
96
def test_delete_commit(self):
73
97
"""Test a commit with a deleted file"""
74
b = Branch.initialize('.')
98
wt = self.make_branch_and_tree('.')
75
100
file('hello', 'w').write('hello world')
76
b.add(['hello'], ['hello-id'])
77
b.working_tree().commit(message='add hello')
101
wt.add(['hello'], ['hello-id'])
102
wt.commit(message='add hello')
79
104
os.remove('hello')
80
b.working_tree().commit('removed hello', rev_id='rev2')
105
wt.commit('removed hello', rev_id='rev2')
82
tree = b.revision_tree('rev2')
107
tree = b.repository.revision_tree('rev2')
83
108
self.assertFalse(tree.has_id('hello-id'))
85
110
def test_pointless_commit(self):
86
111
"""Commit refuses unless there are changes or it's forced."""
87
b = Branch.initialize('.')
112
wt = self.make_branch_and_tree('.')
88
114
file('hello', 'w').write('hello')
90
b.working_tree().commit(message='add hello')
116
wt.commit(message='add hello')
91
117
self.assertEquals(b.revno(), 1)
92
118
self.assertRaises(PointlessCommit,
93
b.working_tree().commit,
95
121
allow_pointless=False)
96
122
self.assertEquals(b.revno(), 1)
98
124
def test_commit_empty(self):
99
125
"""Commiting an empty tree works."""
100
b = Branch.initialize('.')
101
b.working_tree().commit(message='empty tree', allow_pointless=True)
126
wt = self.make_branch_and_tree('.')
128
wt.commit(message='empty tree', allow_pointless=True)
102
129
self.assertRaises(PointlessCommit,
103
b.working_tree().commit,
104
131
message='empty tree',
105
132
allow_pointless=False)
106
b.working_tree().commit(message='empty tree', allow_pointless=True)
133
wt.commit(message='empty tree', allow_pointless=True)
107
134
self.assertEquals(b.revno(), 2)
110
136
def test_selective_delete(self):
111
137
"""Selective commit in tree with deletions"""
112
b = Branch.initialize('.')
138
wt = self.make_branch_and_tree('.')
113
140
file('hello', 'w').write('hello')
114
141
file('buongia', 'w').write('buongia')
115
b.add(['hello', 'buongia'],
142
wt.add(['hello', 'buongia'],
116
143
['hello-id', 'buongia-id'])
117
b.working_tree().commit(message='add files',
144
wt.commit(message='add files',
118
145
rev_id='test@rev-1')
120
147
os.remove('hello')
121
148
file('buongia', 'w').write('new text')
122
b.working_tree().commit(message='update text',
149
wt.commit(message='update text',
123
150
specific_files=['buongia'],
124
151
allow_pointless=False,
125
152
rev_id='test@rev-2')
127
b.working_tree().commit(message='remove hello',
154
wt.commit(message='remove hello',
128
155
specific_files=['hello'],
129
156
allow_pointless=False,
130
157
rev_id='test@rev-3')
132
159
eq = self.assertEquals
135
tree2 = b.revision_tree('test@rev-2')
162
tree2 = b.repository.revision_tree('test@rev-2')
136
163
self.assertTrue(tree2.has_filename('hello'))
137
164
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
138
165
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
140
tree3 = b.revision_tree('test@rev-3')
167
tree3 = b.repository.revision_tree('test@rev-3')
141
168
self.assertFalse(tree3.has_filename('hello'))
142
169
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
145
171
def test_commit_rename(self):
146
172
"""Test commit of a revision where a file is renamed."""
147
b = Branch.initialize('.')
173
tree = self.make_branch_and_tree('.')
148
175
self.build_tree(['hello'], line_endings='binary')
149
b.add(['hello'], ['hello-id'])
150
b.working_tree().commit(message='one', rev_id='test@rev-1', allow_pointless=False)
176
tree.add(['hello'], ['hello-id'])
177
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
152
b.rename_one('hello', 'fruity')
153
b.working_tree().commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
179
tree.rename_one('hello', 'fruity')
180
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
155
182
eq = self.assertEquals
156
tree1 = b.revision_tree('test@rev-1')
183
tree1 = b.repository.revision_tree('test@rev-1')
157
184
eq(tree1.id2path('hello-id'), 'hello')
158
185
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
159
186
self.assertFalse(tree1.has_filename('fruity'))
161
188
ie = tree1.inventory['hello-id']
162
189
eq(ie.revision, 'test@rev-1')
164
tree2 = b.revision_tree('test@rev-2')
191
tree2 = b.repository.revision_tree('test@rev-2')
165
192
eq(tree2.id2path('hello-id'), 'fruity')
166
193
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
167
194
self.check_inventory_shape(tree2.inventory, ['fruity'])
168
195
ie = tree2.inventory['hello-id']
169
196
eq(ie.revision, 'test@rev-2')
172
198
def test_reused_rev_id(self):
173
199
"""Test that a revision id cannot be reused in a branch"""
174
b = Branch.initialize('.')
175
b.working_tree().commit('initial', rev_id='test@rev-1', allow_pointless=True)
200
wt = self.make_branch_and_tree('.')
202
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
176
203
self.assertRaises(Exception,
177
b.working_tree().commit,
178
205
message='reused id',
179
206
rev_id='test@rev-1',
180
207
allow_pointless=True)
184
209
def test_commit_move(self):
185
210
"""Test commit of revisions with moved files and directories"""
186
211
eq = self.assertEquals
187
b = Branch.initialize('.')
212
wt = self.make_branch_and_tree('.')
188
214
r1 = 'test@rev-1'
189
215
self.build_tree(['hello', 'a/', 'b/'])
190
b.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
191
b.working_tree().commit('initial', rev_id=r1, allow_pointless=False)
193
b.move(['hello'], 'a')
216
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
217
wt.commit('initial', rev_id=r1, allow_pointless=False)
218
wt.move(['hello'], 'a')
194
219
r2 = 'test@rev-2'
195
b.working_tree().commit('two', rev_id=r2, allow_pointless=False)
196
self.check_inventory_shape(b.working_tree().read_working_inventory(),
220
wt.commit('two', rev_id=r2, allow_pointless=False)
221
self.check_inventory_shape(wt.read_working_inventory(),
197
222
['a', 'a/hello', 'b'])
200
225
r3 = 'test@rev-3'
201
b.working_tree().commit('three', rev_id=r3, allow_pointless=False)
202
self.check_inventory_shape(b.working_tree().read_working_inventory(),
226
wt.commit('three', rev_id=r3, allow_pointless=False)
227
self.check_inventory_shape(wt.read_working_inventory(),
203
228
['a', 'a/hello', 'a/b'])
204
self.check_inventory_shape(b.get_revision_inventory(r3),
229
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
205
230
['a', 'a/hello', 'a/b'])
207
b.move([os.sep.join(['a', 'hello'])],
208
os.sep.join(['a', 'b']))
232
wt.move(['a/hello'], 'a/b')
209
233
r4 = 'test@rev-4'
210
b.working_tree().commit('four', rev_id=r4, allow_pointless=False)
211
self.check_inventory_shape(b.working_tree().read_working_inventory(),
234
wt.commit('four', rev_id=r4, allow_pointless=False)
235
self.check_inventory_shape(wt.read_working_inventory(),
212
236
['a', 'a/b/hello', 'a/b'])
214
inv = b.get_revision_inventory(r4)
238
inv = b.repository.get_revision_inventory(r4)
215
239
eq(inv['hello-id'].revision, r4)
216
240
eq(inv['a-id'].revision, r1)
217
241
eq(inv['b-id'].revision, r3)
220
243
def test_removed_commit(self):
221
244
"""Commit with a removed file"""
222
b = Branch.initialize('.')
223
wt = b.working_tree()
245
wt = self.make_branch_and_tree('.')
224
247
file('hello', 'w').write('hello world')
225
b.add(['hello'], ['hello-id'])
226
b.working_tree().commit(message='add hello')
228
wt = b.working_tree() # FIXME: kludge for aliasing of working inventory
248
wt.add(['hello'], ['hello-id'])
249
wt.commit(message='add hello')
229
250
wt.remove('hello')
230
b.working_tree().commit('removed hello', rev_id='rev2')
251
wt.commit('removed hello', rev_id='rev2')
232
tree = b.revision_tree('rev2')
253
tree = b.repository.revision_tree('rev2')
233
254
self.assertFalse(tree.has_id('hello-id'))
236
256
def test_committed_ancestry(self):
237
257
"""Test commit appends revisions to ancestry."""
238
b = Branch.initialize('.')
258
wt = self.make_branch_and_tree('.')
240
261
for i in range(4):
241
262
file('hello', 'w').write((str(i) * 4) + '\n')
243
b.add(['hello'], ['hello-id'])
264
wt.add(['hello'], ['hello-id'])
244
265
rev_id = 'test@rev-%d' % (i+1)
245
266
rev_ids.append(rev_id)
246
b.working_tree().commit(message='rev %d' % (i+1),
267
wt.commit(message='rev %d' % (i+1),
248
269
eq = self.assertEquals
249
270
eq(b.revision_history(), rev_ids)
250
271
for i in range(4):
251
anc = b.get_ancestry(rev_ids[i])
272
anc = b.repository.get_ancestry(rev_ids[i])
252
273
eq(anc, [None] + rev_ids[:i+1])
254
275
def test_commit_new_subdir_child_selective(self):
255
b = Branch.initialize('.')
276
wt = self.make_branch_and_tree('.')
256
278
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
257
b.add(['dir', 'dir/file1', 'dir/file2'],
279
wt.add(['dir', 'dir/file1', 'dir/file2'],
258
280
['dirid', 'file1id', 'file2id'])
259
b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
260
inv = b.get_inventory('1')
281
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
282
inv = b.repository.get_inventory('1')
261
283
self.assertEqual('1', inv['dirid'].revision)
262
284
self.assertEqual('1', inv['file1id'].revision)
263
285
# FIXME: This should raise a KeyError I think, rbc20051006
266
288
def test_strict_commit(self):
267
289
"""Try and commit with unknown files and strict = True, should fail."""
268
290
from bzrlib.errors import StrictCommitFailed
269
b = Branch.initialize('.')
291
wt = self.make_branch_and_tree('.')
270
293
file('hello', 'w').write('hello world')
272
295
file('goodbye', 'w').write('goodbye cruel world!')
273
self.assertRaises(StrictCommitFailed, b.working_tree().commit,
296
self.assertRaises(StrictCommitFailed, wt.commit,
274
297
message='add hello but not goodbye', strict=True)
276
299
def test_strict_commit_without_unknowns(self):
277
300
"""Try and commit with no unknown files and strict = True,
279
302
from bzrlib.errors import StrictCommitFailed
280
b = Branch.initialize('.')
303
wt = self.make_branch_and_tree('.')
281
305
file('hello', 'w').write('hello world')
283
b.working_tree().commit(message='add hello', strict=True)
307
wt.commit(message='add hello', strict=True)
285
309
def test_nonstrict_commit(self):
286
310
"""Try and commit with unknown files and strict = False, should work."""
287
b = Branch.initialize('.')
311
wt = self.make_branch_and_tree('.')
288
313
file('hello', 'w').write('hello world')
290
315
file('goodbye', 'w').write('goodbye cruel world!')
291
b.working_tree().commit(message='add hello but not goodbye', strict=False)
316
wt.commit(message='add hello but not goodbye', strict=False)
293
318
def test_nonstrict_commit_without_unknowns(self):
294
319
"""Try and commit with no unknown files and strict = False,
296
b = Branch.initialize('.')
321
wt = self.make_branch_and_tree('.')
297
323
file('hello', 'w').write('hello world')
299
b.working_tree().commit(message='add hello', strict=False)
325
wt.commit(message='add hello', strict=False)
301
327
def test_signed_commit(self):
302
328
import bzrlib.gpg
303
329
import bzrlib.commit as commit
304
330
oldstrategy = bzrlib.gpg.GPGStrategy
305
branch = Branch.initialize('.')
306
branch.working_tree().commit("base", allow_pointless=True, rev_id='A')
307
self.failIf(branch.revision_store.has_id('A', 'sig'))
331
wt = self.make_branch_and_tree('.')
333
wt.commit("base", allow_pointless=True, rev_id='A')
334
self.failIf(branch.repository.has_signature_for_revision_id('A'))
309
336
from bzrlib.testament import Testament
310
337
# monkey patch gpg signing mechanism
311
338
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
312
commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
339
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
313
340
allow_pointless=True,
315
self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
316
branch.revision_store.get('B', 'sig').read())
343
self.assertEqual(Testament.from_revision(branch.repository,
344
'B').as_short_text(),
345
branch.repository.get_signature_text('B'))
318
347
bzrlib.gpg.GPGStrategy = oldstrategy
351
383
config = BranchWithHooks(branch)
352
384
commit.Commit(config=config).commit(
354
386
allow_pointless=True,
387
rev_id='A', working_tree = wt)
356
388
self.assertEqual(['called', 'called'], calls)
392
def test_commit_object_doesnt_set_nick(self):
393
# using the Commit object directly does not set the branch nick.
394
wt = self.make_branch_and_tree('.')
396
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
397
self.assertEquals(wt.branch.revno(), 1)
399
wt.branch.repository.get_revision(
400
wt.branch.last_revision()).properties)
402
def test_safe_master_lock(self):
404
master = BzrDirMetaFormat1().initialize('master')
405
master.create_repository()
406
master_branch = master.create_branch()
407
master.create_workingtree()
408
bound = master.sprout('bound')
409
wt = bound.open_workingtree()
410
wt.branch.set_bound_location(os.path.realpath('master'))
411
master_branch.lock_write()
413
self.assertRaises(LockContention, wt.commit, 'silly')
415
master_branch.unlock()
417
def test_commit_bound_merge(self):
418
# see bug #43959; commit of a merge in a bound branch fails to push
419
# the new commit into the master
420
master_branch = self.make_branch('master')
421
bound_tree = self.make_branch_and_tree('bound')
422
bound_tree.branch.bind(master_branch)
424
self.build_tree_contents([('bound/content_file', 'initial contents\n')])
425
bound_tree.add(['content_file'])
426
bound_tree.commit(message='woo!')
428
other_bzrdir = master_branch.bzrdir.sprout('other')
429
other_tree = other_bzrdir.open_workingtree()
431
# do a commit to the the other branch changing the content file so
432
# that our commit after merging will have a merged revision in the
433
# content file history.
434
self.build_tree_contents([('other/content_file', 'change in other\n')])
435
other_tree.commit('change in other')
437
# do a merge into the bound branch from other, and then change the
438
# content file locally to force a new revision (rather than using the
439
# revision from other). This forces extra processing in commit.
440
bound_tree.merge_from_branch(other_tree.branch)
441
self.build_tree_contents([('bound/content_file', 'change in bound\n')])
443
# before #34959 was fixed, this failed with 'revision not present in
444
# weave' when trying to implicitly push from the bound branch to the master
445
bound_tree.commit(message='commit of merge in bound tree')
447
def test_commit_reporting_after_merge(self):
448
# when doing a commit of a merge, the reporter needs to still
449
# be called for each item that is added/removed/deleted.
450
this_tree = self.make_branch_and_tree('this')
451
# we need a bunch of files and dirs, to perform one action on each.
454
'this/dirtoreparent/',
457
'this/filetoreparent',
474
this_tree.commit('create_files')
475
other_dir = this_tree.bzrdir.sprout('other')
476
other_tree = other_dir.open_workingtree()
477
other_tree.lock_write()
478
# perform the needed actions on the files and dirs.
480
other_tree.rename_one('dirtorename', 'renameddir')
481
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
482
other_tree.rename_one('filetorename', 'renamedfile')
483
other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
484
other_tree.remove(['dirtoremove', 'filetoremove'])
485
self.build_tree_contents([
487
('other/filetomodify', 'new content'),
488
('other/newfile', 'new file content')])
489
other_tree.add('newfile')
490
other_tree.add('newdir/')
491
other_tree.commit('modify all sample files and dirs.')
494
this_tree.merge_from_branch(other_tree.branch)
495
reporter = CapturingReporter()
496
this_tree.commit('do the commit', reporter=reporter)
498
('change', 'unchanged', ''),
499
('change', 'unchanged', 'dirtoleave'),
500
('change', 'unchanged', 'filetoleave'),
501
('change', 'modified', 'filetomodify'),
502
('change', 'added', 'newdir'),
503
('change', 'added', 'newfile'),
504
('renamed', 'renamed', 'dirtorename', 'renameddir'),
505
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
506
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
507
('renamed', 'renamed', 'filetorename', 'renamedfile'),
508
('deleted', 'dirtoremove'),
509
('deleted', 'filetoremove'),
513
def test_commit_removals_respects_filespec(self):
514
"""Commit respects the specified_files for removals."""
515
tree = self.make_branch_and_tree('.')
516
self.build_tree(['a', 'b'])
518
tree.commit('added a, b')
519
tree.remove(['a', 'b'])
520
tree.commit('removed a', specific_files='a')
521
basis = tree.basis_tree().inventory
522
self.assertIs(None, basis.path2id('a'))
523
self.assertFalse(basis.path2id('b') is None)
525
def test_commit_saves_1ms_timestamp(self):
526
"""Passing in a timestamp is saved with 1ms resolution"""
527
tree = self.make_branch_and_tree('.')
528
self.build_tree(['a'])
530
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
533
rev = tree.branch.repository.get_revision('a1')
534
self.assertEqual(1153248633.419, rev.timestamp)
536
def test_commit_has_1ms_resolution(self):
537
"""Allowing commit to generate the timestamp also has 1ms resolution"""
538
tree = self.make_branch_and_tree('.')
539
self.build_tree(['a'])
541
tree.commit('added a', rev_id='a1')
543
rev = tree.branch.repository.get_revision('a1')
544
timestamp = rev.timestamp
545
timestamp_1ms = round(timestamp, 3)
546
self.assertEqual(timestamp_1ms, timestamp)
548
def test_commit_unversioned_specified(self):
549
"""Commit should raise if specified files isn't in basis or worktree"""
550
tree = self.make_branch_and_tree('.')
551
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
552
'message', specific_files=['bogus'])