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
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
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
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
237
inv = b.repository.get_revision_inventory(r4)
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
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)
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.repository.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
342
self.assertEqual(Testament.from_revision(branch.repository,
311
343
'B').as_short_text(),
312
branch.repository.revision_store.get('B',
344
branch.repository.get_signature_text('B'))
315
346
bzrlib.gpg.GPGStrategy = oldstrategy
348
382
config = BranchWithHooks(branch)
349
383
commit.Commit(config=config).commit(
351
385
allow_pointless=True,
386
rev_id='A', working_tree = wt)
353
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'),
511
def test_commit_removals_respects_filespec(self):
512
"""Commit respects the specified_files for removals."""
513
tree = self.make_branch_and_tree('.')
514
self.build_tree(['a', 'b'])
516
tree.commit('added a, b')
517
tree.remove(['a', 'b'])
518
Commit().commit(message='removed a', working_tree=tree,
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)