43
45
return "bzrlib.ahook bzrlib.ahook"
46
class TestCommit(TestCaseInTempDir):
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):
48
70
def test_simple_commit(self):
49
71
"""Commit and check two versions of a single file."""
50
b = Branch.initialize(u'.')
72
wt = self.make_branch_and_tree('.')
51
74
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')
76
wt.commit(message='add hello')
77
file_id = wt.path2id('hello')
56
79
file('hello', 'w').write('version 2')
57
b.working_tree().commit(message='commit 2')
80
wt.commit(message='commit 2')
59
82
eq = self.assertEquals
61
84
rh = b.revision_history()
62
rev = b.get_revision(rh[0])
85
rev = b.repository.get_revision(rh[0])
63
86
eq(rev.message, 'add hello')
65
tree1 = b.revision_tree(rh[0])
88
tree1 = b.repository.revision_tree(rh[0])
66
89
text = tree1.get_file_text(file_id)
67
90
eq(text, 'hello world')
69
tree2 = b.revision_tree(rh[1])
92
tree2 = b.repository.revision_tree(rh[1])
70
93
eq(tree2.get_file_text(file_id), 'version 2')
72
95
def test_delete_commit(self):
73
96
"""Test a commit with a deleted file"""
74
b = Branch.initialize(u'.')
97
wt = self.make_branch_and_tree('.')
75
99
file('hello', 'w').write('hello world')
76
b.working_tree().add(['hello'], ['hello-id'])
77
b.working_tree().commit(message='add hello')
100
wt.add(['hello'], ['hello-id'])
101
wt.commit(message='add hello')
79
103
os.remove('hello')
80
b.working_tree().commit('removed hello', rev_id='rev2')
104
wt.commit('removed hello', rev_id='rev2')
82
tree = b.revision_tree('rev2')
106
tree = b.repository.revision_tree('rev2')
83
107
self.assertFalse(tree.has_id('hello-id'))
85
109
def test_pointless_commit(self):
86
110
"""Commit refuses unless there are changes or it's forced."""
87
b = Branch.initialize(u'.')
111
wt = self.make_branch_and_tree('.')
88
113
file('hello', 'w').write('hello')
89
b.working_tree().add(['hello'])
90
b.working_tree().commit(message='add hello')
115
wt.commit(message='add hello')
91
116
self.assertEquals(b.revno(), 1)
92
117
self.assertRaises(PointlessCommit,
93
b.working_tree().commit,
95
120
allow_pointless=False)
96
121
self.assertEquals(b.revno(), 1)
98
123
def test_commit_empty(self):
99
124
"""Commiting an empty tree works."""
100
b = Branch.initialize(u'.')
101
b.working_tree().commit(message='empty tree', allow_pointless=True)
125
wt = self.make_branch_and_tree('.')
127
wt.commit(message='empty tree', allow_pointless=True)
102
128
self.assertRaises(PointlessCommit,
103
b.working_tree().commit,
104
130
message='empty tree',
105
131
allow_pointless=False)
106
b.working_tree().commit(message='empty tree', allow_pointless=True)
132
wt.commit(message='empty tree', allow_pointless=True)
107
133
self.assertEquals(b.revno(), 2)
110
135
def test_selective_delete(self):
111
136
"""Selective commit in tree with deletions"""
112
b = Branch.initialize(u'.')
137
wt = self.make_branch_and_tree('.')
113
139
file('hello', 'w').write('hello')
114
140
file('buongia', 'w').write('buongia')
115
b.working_tree().add(['hello', 'buongia'],
141
wt.add(['hello', 'buongia'],
116
142
['hello-id', 'buongia-id'])
117
b.working_tree().commit(message='add files',
143
wt.commit(message='add files',
118
144
rev_id='test@rev-1')
120
146
os.remove('hello')
121
147
file('buongia', 'w').write('new text')
122
b.working_tree().commit(message='update text',
148
wt.commit(message='update text',
123
149
specific_files=['buongia'],
124
150
allow_pointless=False,
125
151
rev_id='test@rev-2')
127
b.working_tree().commit(message='remove hello',
153
wt.commit(message='remove hello',
128
154
specific_files=['hello'],
129
155
allow_pointless=False,
130
156
rev_id='test@rev-3')
182
208
def test_commit_move(self):
183
209
"""Test commit of revisions with moved files and directories"""
184
210
eq = self.assertEquals
185
b = Branch.initialize(u'.')
211
wt = self.make_branch_and_tree('.')
186
213
r1 = 'test@rev-1'
187
214
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')
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')
191
218
r2 = 'test@rev-2'
192
b.working_tree().commit('two', rev_id=r2, allow_pointless=False)
193
self.check_inventory_shape(b.working_tree().read_working_inventory(),
219
wt.commit('two', rev_id=r2, allow_pointless=False)
220
self.check_inventory_shape(wt.read_working_inventory(),
194
221
['a', 'a/hello', 'b'])
196
b.working_tree().move(['b'], 'a')
197
224
r3 = 'test@rev-3'
198
b.working_tree().commit('three', rev_id=r3, allow_pointless=False)
199
self.check_inventory_shape(b.working_tree().read_working_inventory(),
225
wt.commit('three', rev_id=r3, allow_pointless=False)
226
self.check_inventory_shape(wt.read_working_inventory(),
200
227
['a', 'a/hello', 'a/b'])
201
self.check_inventory_shape(b.get_revision_inventory(r3),
228
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
202
229
['a', 'a/hello', 'a/b'])
204
b.working_tree().move(['a/hello'], 'a/b')
231
wt.move(['a/hello'], 'a/b')
205
232
r4 = 'test@rev-4'
206
b.working_tree().commit('four', rev_id=r4, allow_pointless=False)
207
self.check_inventory_shape(b.working_tree().read_working_inventory(),
233
wt.commit('four', rev_id=r4, allow_pointless=False)
234
self.check_inventory_shape(wt.read_working_inventory(),
208
235
['a', 'a/b/hello', 'a/b'])
210
inv = b.get_revision_inventory(r4)
237
inv = b.repository.get_revision_inventory(r4)
211
238
eq(inv['hello-id'].revision, r4)
212
239
eq(inv['a-id'].revision, r1)
213
240
eq(inv['b-id'].revision, r3)
215
242
def test_removed_commit(self):
216
243
"""Commit with a removed file"""
217
b = Branch.initialize(u'.')
218
wt = b.working_tree()
244
wt = self.make_branch_and_tree('.')
219
246
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
247
wt.add(['hello'], ['hello-id'])
248
wt.commit(message='add hello')
224
249
wt.remove('hello')
225
b.working_tree().commit('removed hello', rev_id='rev2')
250
wt.commit('removed hello', rev_id='rev2')
227
tree = b.revision_tree('rev2')
252
tree = b.repository.revision_tree('rev2')
228
253
self.assertFalse(tree.has_id('hello-id'))
231
255
def test_committed_ancestry(self):
232
256
"""Test commit appends revisions to ancestry."""
233
b = Branch.initialize(u'.')
257
wt = self.make_branch_and_tree('.')
235
260
for i in range(4):
236
261
file('hello', 'w').write((str(i) * 4) + '\n')
238
b.working_tree().add(['hello'], ['hello-id'])
263
wt.add(['hello'], ['hello-id'])
239
264
rev_id = 'test@rev-%d' % (i+1)
240
265
rev_ids.append(rev_id)
241
b.working_tree().commit(message='rev %d' % (i+1),
266
wt.commit(message='rev %d' % (i+1),
243
268
eq = self.assertEquals
244
269
eq(b.revision_history(), rev_ids)
245
270
for i in range(4):
246
anc = b.get_ancestry(rev_ids[i])
271
anc = b.repository.get_ancestry(rev_ids[i])
247
272
eq(anc, [None] + rev_ids[:i+1])
249
274
def test_commit_new_subdir_child_selective(self):
250
b = Branch.initialize(u'.')
275
wt = self.make_branch_and_tree('.')
251
277
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
252
b.working_tree().add(['dir', 'dir/file1', 'dir/file2'],
278
wt.add(['dir', 'dir/file1', 'dir/file2'],
253
279
['dirid', 'file1id', 'file2id'])
254
b.working_tree().commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
255
inv = b.get_inventory('1')
280
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
281
inv = b.repository.get_inventory('1')
256
282
self.assertEqual('1', inv['dirid'].revision)
257
283
self.assertEqual('1', inv['file1id'].revision)
258
284
# FIXME: This should raise a KeyError I think, rbc20051006
261
287
def test_strict_commit(self):
262
288
"""Try and commit with unknown files and strict = True, should fail."""
263
289
from bzrlib.errors import StrictCommitFailed
264
b = Branch.initialize(u'.')
290
wt = self.make_branch_and_tree('.')
265
292
file('hello', 'w').write('hello world')
266
b.working_tree().add('hello')
267
294
file('goodbye', 'w').write('goodbye cruel world!')
268
self.assertRaises(StrictCommitFailed, b.working_tree().commit,
295
self.assertRaises(StrictCommitFailed, wt.commit,
269
296
message='add hello but not goodbye', strict=True)
271
298
def test_strict_commit_without_unknowns(self):
272
299
"""Try and commit with no unknown files and strict = True,
274
301
from bzrlib.errors import StrictCommitFailed
275
b = Branch.initialize(u'.')
302
wt = self.make_branch_and_tree('.')
276
304
file('hello', 'w').write('hello world')
277
b.working_tree().add('hello')
278
b.working_tree().commit(message='add hello', strict=True)
306
wt.commit(message='add hello', strict=True)
280
308
def test_nonstrict_commit(self):
281
309
"""Try and commit with unknown files and strict = False, should work."""
282
b = Branch.initialize(u'.')
310
wt = self.make_branch_and_tree('.')
283
312
file('hello', 'w').write('hello world')
284
b.working_tree().add('hello')
285
314
file('goodbye', 'w').write('goodbye cruel world!')
286
b.working_tree().commit(message='add hello but not goodbye', strict=False)
315
wt.commit(message='add hello but not goodbye', strict=False)
288
317
def test_nonstrict_commit_without_unknowns(self):
289
318
"""Try and commit with no unknown files and strict = False,
291
b = Branch.initialize(u'.')
320
wt = self.make_branch_and_tree('.')
292
322
file('hello', 'w').write('hello world')
293
b.working_tree().add('hello')
294
b.working_tree().commit(message='add hello', strict=False)
324
wt.commit(message='add hello', strict=False)
296
326
def test_signed_commit(self):
297
327
import bzrlib.gpg
298
328
import bzrlib.commit as commit
299
329
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.revision_store.has_id('A', 'sig'))
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'))
304
335
from bzrlib.testament import Testament
305
336
# monkey patch gpg signing mechanism
306
337
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
307
commit.Commit(config=MustSignConfig(branch)).commit(branch, "base",
338
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
308
339
allow_pointless=True,
310
self.assertEqual(Testament.from_revision(branch,'B').as_short_text(),
311
branch.revision_store.get('B', 'sig').read())
342
self.assertEqual(Testament.from_revision(branch.repository,
343
'B').as_short_text(),
344
branch.repository.get_signature_text('B'))
313
346
bzrlib.gpg.GPGStrategy = oldstrategy
346
382
config = BranchWithHooks(branch)
347
383
commit.Commit(config=config).commit(
349
385
allow_pointless=True,
386
rev_id='A', working_tree = wt)
351
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', ''),
498
('change', 'unchanged', 'dirtoleave'),
499
('change', 'unchanged', 'filetoleave'),
500
('change', 'modified', 'filetomodify'),
501
('change', 'added', 'newdir'),
502
('change', 'added', 'newfile'),
503
('renamed', 'renamed', 'dirtorename', 'renameddir'),
504
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
505
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
506
('renamed', 'renamed', 'filetorename', 'renamedfile'),
507
('deleted', 'dirtoremove'),
508
('deleted', 'filetoremove'),
512
def test_commit_removals_respects_filespec(self):
513
"""Commit respects the specified_files for removals."""
514
tree = self.make_branch_and_tree('.')
515
self.build_tree(['a', 'b'])
517
tree.commit('added a, b')
518
tree.remove(['a', 'b'])
519
tree.commit('removed a', specific_files='a')
520
basis = tree.basis_tree().inventory
521
self.assertIs(None, basis.path2id('a'))
522
self.assertFalse(basis.path2id('b') is None)
524
def test_commit_saves_1ms_timestamp(self):
525
"""Passing in a timestamp is saved with 1ms resolution"""
526
tree = self.make_branch_and_tree('.')
527
self.build_tree(['a'])
529
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
532
rev = tree.branch.repository.get_revision('a1')
533
self.assertEqual(1153248633.419, rev.timestamp)
535
def test_commit_has_1ms_resolution(self):
536
"""Allowing commit to generate the timestamp also has 1ms resolution"""
537
tree = self.make_branch_and_tree('.')
538
self.build_tree(['a'])
540
tree.commit('added a', rev_id='a1')
542
rev = tree.branch.repository.get_revision('a1')
543
timestamp = rev.timestamp
544
timestamp_1ms = round(timestamp, 3)
545
self.assertEqual(timestamp_1ms, timestamp)