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