51
86
"""Commit and check two versions of a single file."""
52
87
wt = self.make_branch_and_tree('.')
54
file('hello', 'w').write('hello world')
89
with open('hello', 'w') as f: f.write('hello world')
56
wt.commit(message='add hello')
57
file_id = wt.path2id('hello')
59
file('hello', 'w').write('version 2')
60
wt.commit(message='commit 2')
62
eq = self.assertEquals
91
rev1 = wt.commit(message='add hello')
93
with open('hello', 'w') as f: f.write('version 2')
94
rev2 = wt.commit(message='commit 2')
64
rh = b.revision_history()
65
rev = b.repository.get_revision(rh[0])
98
rev = b.repository.get_revision(rev1)
66
99
eq(rev.message, 'add hello')
68
tree1 = b.repository.revision_tree(rh[0])
69
text = tree1.get_file_text(file_id)
70
eq(text, 'hello world')
72
tree2 = b.repository.revision_tree(rh[1])
73
eq(tree2.get_file_text(file_id), 'version 2')
75
def test_delete_commit(self):
76
"""Test a commit with a deleted file"""
77
wt = self.make_branch_and_tree('.')
79
file('hello', 'w').write('hello world')
80
wt.add(['hello'], ['hello-id'])
101
tree1 = b.repository.revision_tree(rev1)
103
text = tree1.get_file_text('hello')
105
self.assertEqual(b'hello world', text)
107
tree2 = b.repository.revision_tree(rev2)
109
text = tree2.get_file_text('hello')
111
self.assertEqual(b'version 2', text)
113
def test_commit_lossy_native(self):
114
"""Attempt a lossy commit to a native branch."""
115
wt = self.make_branch_and_tree('.')
117
with open('hello', 'w') as f: f.write('hello world')
119
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
120
self.assertEqual(b'revid', revid)
122
def test_commit_lossy_foreign(self):
123
"""Attempt a lossy commit to a foreign branch."""
124
test_foreign.register_dummy_foreign_for_test(self)
125
wt = self.make_branch_and_tree('.',
126
format=test_foreign.DummyForeignVcsDirFormat())
128
with open('hello', 'w') as f: f.write('hello world')
130
revid = wt.commit(message='add hello', lossy=True,
131
timestamp=1302659388, timezone=0)
132
self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
134
def test_commit_bound_lossy_foreign(self):
135
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
136
test_foreign.register_dummy_foreign_for_test(self)
137
foreign_branch = self.make_branch('foreign',
138
format=test_foreign.DummyForeignVcsDirFormat())
139
wt = foreign_branch.create_checkout("local")
141
with open('local/hello', 'w') as f: f.write('hello world')
143
revid = wt.commit(message='add hello', lossy=True,
144
timestamp=1302659388, timezone=0)
145
self.assertEqual(b'dummy-v1:1302659388-0-0', revid)
146
self.assertEqual(b'dummy-v1:1302659388-0-0',
147
foreign_branch.last_revision())
148
self.assertEqual(b'dummy-v1:1302659388-0-0',
149
wt.branch.last_revision())
151
def test_missing_commit(self):
152
"""Test a commit with a missing file"""
153
wt = self.make_branch_and_tree('.')
155
with open('hello', 'w') as f: f.write('hello world')
156
wt.add(['hello'], [b'hello-id'])
81
157
wt.commit(message='add hello')
83
159
os.remove('hello')
84
wt.commit('removed hello', rev_id='rev2')
86
tree = b.repository.revision_tree('rev2')
87
self.assertFalse(tree.has_id('hello-id'))
160
reporter = CapturingReporter()
161
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
163
[('missing', u'hello'), ('deleted', u'hello')],
166
tree = b.repository.revision_tree(b'rev2')
167
self.assertFalse(tree.has_id(b'hello-id'))
169
def test_partial_commit_move(self):
170
"""Test a partial commit where a file was renamed but not committed.
172
https://bugs.launchpad.net/bzr/+bug/83039
174
If not handled properly, commit will try to snapshot
175
dialog.py with olive/ as a parent, while
176
olive/ has not been snapshotted yet.
178
wt = self.make_branch_and_tree('.')
180
self.build_tree(['annotate/', 'annotate/foo.py',
181
'olive/', 'olive/dialog.py'
183
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
184
wt.commit(message='add files')
185
wt.rename_one("olive/dialog.py", "aaa")
186
self.build_tree_contents([('annotate/foo.py', b'modified\n')])
187
wt.commit('renamed hello', specific_files=["annotate"])
89
189
def test_pointless_commit(self):
90
190
"""Commit refuses unless there are changes or it's forced."""
91
191
wt = self.make_branch_and_tree('.')
93
file('hello', 'w').write('hello')
193
with open('hello', 'w') as f: f.write('hello')
95
195
wt.commit(message='add hello')
96
self.assertEquals(b.revno(), 1)
196
self.assertEqual(b.revno(), 1)
97
197
self.assertRaises(PointlessCommit,
100
200
allow_pointless=False)
101
self.assertEquals(b.revno(), 1)
201
self.assertEqual(b.revno(), 1)
103
203
def test_commit_empty(self):
104
204
"""Commiting an empty tree works."""
105
205
wt = self.make_branch_and_tree('.')
110
210
message='empty tree',
111
211
allow_pointless=False)
112
212
wt.commit(message='empty tree', allow_pointless=True)
113
self.assertEquals(b.revno(), 2)
213
self.assertEqual(b.revno(), 2)
115
215
def test_selective_delete(self):
116
216
"""Selective commit in tree with deletions"""
117
217
wt = self.make_branch_and_tree('.')
119
file('hello', 'w').write('hello')
120
file('buongia', 'w').write('buongia')
219
with open('hello', 'w') as f: f.write('hello')
220
with open('buongia', 'w') as f: f.write('buongia')
121
221
wt.add(['hello', 'buongia'],
122
['hello-id', 'buongia-id'])
222
[b'hello-id', b'buongia-id'])
123
223
wt.commit(message='add files',
224
rev_id=b'test@rev-1')
126
226
os.remove('hello')
127
file('buongia', 'w').write('new text')
227
with open('buongia', 'w') as f: f.write('new text')
128
228
wt.commit(message='update text',
129
229
specific_files=['buongia'],
130
230
allow_pointless=False,
231
rev_id=b'test@rev-2')
133
233
wt.commit(message='remove hello',
134
234
specific_files=['hello'],
135
235
allow_pointless=False,
236
rev_id=b'test@rev-3')
138
eq = self.assertEquals
238
eq = self.assertEqual
141
tree2 = b.repository.revision_tree('test@rev-2')
241
tree2 = b.repository.revision_tree(b'test@rev-2')
243
self.addCleanup(tree2.unlock)
142
244
self.assertTrue(tree2.has_filename('hello'))
143
self.assertEquals(tree2.get_file_text('hello-id'), 'hello')
144
self.assertEquals(tree2.get_file_text('buongia-id'), 'new text')
146
tree3 = b.repository.revision_tree('test@rev-3')
245
self.assertEqual(tree2.get_file_text('hello'), b'hello')
246
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
248
tree3 = b.repository.revision_tree(b'test@rev-3')
250
self.addCleanup(tree3.unlock)
147
251
self.assertFalse(tree3.has_filename('hello'))
148
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
252
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
150
254
def test_commit_rename(self):
151
255
"""Test commit of a revision where a file is renamed."""
152
256
tree = self.make_branch_and_tree('.')
154
258
self.build_tree(['hello'], line_endings='binary')
155
tree.add(['hello'], ['hello-id'])
156
tree.commit(message='one', rev_id='test@rev-1', allow_pointless=False)
259
tree.add(['hello'], [b'hello-id'])
260
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
158
262
tree.rename_one('hello', 'fruity')
159
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
263
tree.commit(message='renamed', rev_id=b'test@rev-2', allow_pointless=False)
161
eq = self.assertEquals
162
tree1 = b.repository.revision_tree('test@rev-1')
163
eq(tree1.id2path('hello-id'), 'hello')
164
eq(tree1.get_file_text('hello-id'), 'contents of hello\n')
265
eq = self.assertEqual
266
tree1 = b.repository.revision_tree(b'test@rev-1')
268
self.addCleanup(tree1.unlock)
269
eq(tree1.id2path(b'hello-id'), 'hello')
270
eq(tree1.get_file_text('hello'), b'contents of hello\n')
165
271
self.assertFalse(tree1.has_filename('fruity'))
166
self.check_inventory_shape(tree1.inventory, ['hello'])
167
ie = tree1.inventory['hello-id']
168
eq(ie.revision, 'test@rev-1')
272
self.check_tree_shape(tree1, ['hello'])
273
eq(tree1.get_file_revision('hello'), b'test@rev-1')
170
tree2 = b.repository.revision_tree('test@rev-2')
171
eq(tree2.id2path('hello-id'), 'fruity')
172
eq(tree2.get_file_text('hello-id'), 'contents of hello\n')
173
self.check_inventory_shape(tree2.inventory, ['fruity'])
174
ie = tree2.inventory['hello-id']
175
eq(ie.revision, 'test@rev-2')
275
tree2 = b.repository.revision_tree(b'test@rev-2')
277
self.addCleanup(tree2.unlock)
278
eq(tree2.id2path(b'hello-id'), 'fruity')
279
eq(tree2.get_file_text('fruity'), b'contents of hello\n')
280
self.check_tree_shape(tree2, ['fruity'])
281
eq(tree2.get_file_revision('fruity'), b'test@rev-2')
177
283
def test_reused_rev_id(self):
178
284
"""Test that a revision id cannot be reused in a branch"""
179
285
wt = self.make_branch_and_tree('.')
181
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
287
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
182
288
self.assertRaises(Exception,
184
290
message='reused id',
291
rev_id=b'test@rev-1',
186
292
allow_pointless=True)
188
294
def test_commit_move(self):
189
295
"""Test commit of revisions with moved files and directories"""
190
eq = self.assertEquals
296
eq = self.assertEqual
191
297
wt = self.make_branch_and_tree('.')
194
300
self.build_tree(['hello', 'a/', 'b/'])
195
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
301
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
196
302
wt.commit('initial', rev_id=r1, allow_pointless=False)
197
303
wt.move(['hello'], 'a')
199
305
wt.commit('two', rev_id=r2, allow_pointless=False)
200
self.check_inventory_shape(wt.read_working_inventory(),
201
['a', 'a/hello', 'b'])
308
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
203
312
wt.move(['b'], 'a')
205
314
wt.commit('three', rev_id=r3, allow_pointless=False)
206
self.check_inventory_shape(wt.read_working_inventory(),
207
['a', 'a/hello', 'a/b'])
208
self.check_inventory_shape(b.repository.get_revision_inventory(r3),
209
['a', 'a/hello', 'a/b'])
317
self.check_tree_shape(wt,
318
['a/', 'a/hello', 'a/b/'])
319
self.check_tree_shape(b.repository.revision_tree(r3),
320
['a/', 'a/hello', 'a/b/'])
211
324
wt.move(['a/hello'], 'a/b')
213
326
wt.commit('four', rev_id=r4, allow_pointless=False)
214
self.check_inventory_shape(wt.read_working_inventory(),
215
['a', 'a/b/hello', 'a/b'])
217
inv = b.repository.get_revision_inventory(r4)
218
eq(inv['hello-id'].revision, r4)
219
eq(inv['a-id'].revision, r1)
220
eq(inv['b-id'].revision, r3)
329
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
333
inv = b.repository.get_inventory(r4)
334
eq(inv.get_entry(b'hello-id').revision, r4)
335
eq(inv.get_entry(b'a-id').revision, r1)
336
eq(inv.get_entry(b'b-id').revision, r3)
222
338
def test_removed_commit(self):
223
339
"""Commit with a removed file"""
224
340
wt = self.make_branch_and_tree('.')
226
file('hello', 'w').write('hello world')
227
wt.add(['hello'], ['hello-id'])
342
with open('hello', 'w') as f: f.write('hello world')
343
wt.add(['hello'], [b'hello-id'])
228
344
wt.commit(message='add hello')
229
345
wt.remove('hello')
230
wt.commit('removed hello', rev_id='rev2')
346
wt.commit('removed hello', rev_id=b'rev2')
232
tree = b.repository.revision_tree('rev2')
233
self.assertFalse(tree.has_id('hello-id'))
348
tree = b.repository.revision_tree(b'rev2')
349
self.assertFalse(tree.has_id(b'hello-id'))
235
351
def test_committed_ancestry(self):
236
352
"""Test commit appends revisions to ancestry."""
240
356
for i in range(4):
241
file('hello', 'w').write((str(i) * 4) + '\n')
357
with open('hello', 'w') as f: f.write((str(i) * 4) + '\n')
243
wt.add(['hello'], ['hello-id'])
244
rev_id = 'test@rev-%d' % (i+1)
359
wt.add(['hello'], [b'hello-id'])
360
rev_id = b'test@rev-%d' % (i+1)
245
361
rev_ids.append(rev_id)
246
362
wt.commit(message='rev %d' % (i+1),
248
eq = self.assertEquals
249
eq(b.revision_history(), rev_ids)
250
364
for i in range(4):
251
anc = b.repository.get_ancestry(rev_ids[i])
252
eq(anc, [None] + rev_ids[:i+1])
365
self.assertThat(rev_ids[:i+1],
366
MatchesAncestry(b.repository, rev_ids[i]))
254
368
def test_commit_new_subdir_child_selective(self):
255
369
wt = self.make_branch_and_tree('.')
257
371
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
258
372
wt.add(['dir', 'dir/file1', 'dir/file2'],
259
['dirid', 'file1id', 'file2id'])
260
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id='1')
261
inv = b.repository.get_inventory('1')
262
self.assertEqual('1', inv['dirid'].revision)
263
self.assertEqual('1', inv['file1id'].revision)
373
[b'dirid', b'file1id', b'file2id'])
374
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id=b'1')
375
inv = b.repository.get_inventory(b'1')
376
self.assertEqual(b'1', inv.get_entry(b'dirid').revision)
377
self.assertEqual(b'1', inv.get_entry(b'file1id').revision)
264
378
# FIXME: This should raise a KeyError I think, rbc20051006
265
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
379
self.assertRaises(BzrError, inv.get_entry, b'file2id')
267
381
def test_strict_commit(self):
268
382
"""Try and commit with unknown files and strict = True, should fail."""
269
from bzrlib.errors import StrictCommitFailed
383
from ..errors import StrictCommitFailed
270
384
wt = self.make_branch_and_tree('.')
272
file('hello', 'w').write('hello world')
386
with open('hello', 'w') as f: f.write('hello world')
274
file('goodbye', 'w').write('goodbye cruel world!')
388
with open('goodbye', 'w') as f: f.write('goodbye cruel world!')
275
389
self.assertRaises(StrictCommitFailed, wt.commit,
276
390
message='add hello but not goodbye', strict=True)
278
392
def test_strict_commit_without_unknowns(self):
279
393
"""Try and commit with no unknown files and strict = True,
281
from bzrlib.errors import StrictCommitFailed
282
395
wt = self.make_branch_and_tree('.')
284
file('hello', 'w').write('hello world')
397
with open('hello', 'w') as f: f.write('hello world')
286
399
wt.commit(message='add hello', strict=True)
300
413
wt = self.make_branch_and_tree('.')
302
file('hello', 'w').write('hello world')
415
with open('hello', 'w') as f: f.write('hello world')
304
417
wt.commit(message='add hello', strict=False)
306
419
def test_signed_commit(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
421
import breezy.commit as commit
422
oldstrategy = breezy.gpg.GPGStrategy
310
423
wt = self.make_branch_and_tree('.')
311
424
branch = wt.branch
312
wt.commit("base", allow_pointless=True, rev_id='A')
313
self.failIf(branch.repository.has_signature_for_revision_id('A'))
425
wt.commit("base", allow_pointless=True, rev_id=b'A')
426
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
315
from bzrlib.testament import Testament
428
from ..testament import Testament
316
429
# monkey patch gpg signing mechanism
317
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
318
commit.Commit(config=MustSignConfig(branch)).commit(message="base",
319
allow_pointless=True,
322
self.assertEqual(Testament.from_revision(branch.repository,
323
'B').as_short_text(),
324
branch.repository.get_signature_text('B'))
430
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
431
conf = config.MemoryStack(b'''
432
create_signatures=always
434
commit.Commit(config_stack=conf).commit(
435
message="base", allow_pointless=True, rev_id=b'B',
438
return breezy.gpg.LoopbackGPGStrategy(None).sign(
439
text, breezy.gpg.MODE_CLEAR)
440
self.assertEqual(sign(Testament.from_revision(branch.repository,
441
b'B').as_short_text()),
442
branch.repository.get_signature_text(b'B'))
326
bzrlib.gpg.GPGStrategy = oldstrategy
444
breezy.gpg.GPGStrategy = oldstrategy
328
446
def test_commit_failed_signature(self):
330
import bzrlib.commit as commit
331
oldstrategy = bzrlib.gpg.GPGStrategy
448
import breezy.commit as commit
449
oldstrategy = breezy.gpg.GPGStrategy
332
450
wt = self.make_branch_and_tree('.')
333
451
branch = wt.branch
334
wt.commit("base", allow_pointless=True, rev_id='A')
335
self.failIf(branch.repository.has_signature_for_revision_id('A'))
452
wt.commit("base", allow_pointless=True, rev_id=b'A')
453
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
337
from bzrlib.testament import Testament
338
455
# monkey patch gpg signing mechanism
339
bzrlib.gpg.GPGStrategy = bzrlib.gpg.DisabledGPGStrategy
340
config = MustSignConfig(branch)
341
self.assertRaises(SigningFailed,
342
commit.Commit(config=config).commit,
456
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
457
conf = config.MemoryStack(b'''
458
create_signatures=always
460
self.assertRaises(breezy.gpg.SigningFailed,
461
commit.Commit(config_stack=conf).commit,
344
463
allow_pointless=True,
347
466
branch = Branch.open(self.get_url('.'))
348
self.assertEqual(branch.revision_history(), ['A'])
349
self.failIf(branch.repository.has_revision('B'))
467
self.assertEqual(branch.last_revision(), b'A')
468
self.assertFalse(branch.repository.has_revision(b'B'))
351
bzrlib.gpg.GPGStrategy = oldstrategy
470
breezy.gpg.GPGStrategy = oldstrategy
353
472
def test_commit_invokes_hooks(self):
354
import bzrlib.commit as commit
473
import breezy.commit as commit
355
474
wt = self.make_branch_and_tree('.')
356
475
branch = wt.branch
358
477
def called(branch, rev_id):
359
478
calls.append('called')
360
bzrlib.ahook = called
479
breezy.ahook = called
362
config = BranchWithHooks(branch)
363
commit.Commit(config=config).commit(
365
allow_pointless=True,
366
rev_id='A', working_tree = wt)
481
conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
482
commit.Commit(config_stack=conf).commit(
483
message = "base", allow_pointless=True, rev_id=b'A',
367
485
self.assertEqual(['called', 'called'], calls)
371
489
def test_commit_object_doesnt_set_nick(self):
372
490
# using the Commit object directly does not set the branch nick.
373
491
wt = self.make_branch_and_tree('.')
375
493
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
376
self.assertEquals(wt.branch.revno(), 1)
494
self.assertEqual(wt.branch.revno(), 1)
377
495
self.assertEqual({},
378
496
wt.branch.repository.get_revision(
379
497
wt.branch.last_revision()).properties)
392
510
self.assertRaises(LockContention, wt.commit, 'silly')
394
512
master_branch.unlock()
514
def test_commit_bound_merge(self):
515
# see bug #43959; commit of a merge in a bound branch fails to push
516
# the new commit into the master
517
master_branch = self.make_branch('master')
518
bound_tree = self.make_branch_and_tree('bound')
519
bound_tree.branch.bind(master_branch)
521
self.build_tree_contents([('bound/content_file', b'initial contents\n')])
522
bound_tree.add(['content_file'])
523
bound_tree.commit(message='woo!')
525
other_bzrdir = master_branch.controldir.sprout('other')
526
other_tree = other_bzrdir.open_workingtree()
528
# do a commit to the other branch changing the content file so
529
# that our commit after merging will have a merged revision in the
530
# content file history.
531
self.build_tree_contents([('other/content_file', b'change in other\n')])
532
other_tree.commit('change in other')
534
# do a merge into the bound branch from other, and then change the
535
# content file locally to force a new revision (rather than using the
536
# revision from other). This forces extra processing in commit.
537
bound_tree.merge_from_branch(other_tree.branch)
538
self.build_tree_contents([('bound/content_file', b'change in bound\n')])
540
# before #34959 was fixed, this failed with 'revision not present in
541
# weave' when trying to implicitly push from the bound branch to the master
542
bound_tree.commit(message='commit of merge in bound tree')
544
def test_commit_reporting_after_merge(self):
545
# when doing a commit of a merge, the reporter needs to still
546
# be called for each item that is added/removed/deleted.
547
this_tree = self.make_branch_and_tree('this')
548
# we need a bunch of files and dirs, to perform one action on each.
551
'this/dirtoreparent/',
554
'this/filetoreparent',
571
this_tree.commit('create_files')
572
other_dir = this_tree.controldir.sprout('other')
573
other_tree = other_dir.open_workingtree()
574
other_tree.lock_write()
575
# perform the needed actions on the files and dirs.
577
other_tree.rename_one('dirtorename', 'renameddir')
578
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
579
other_tree.rename_one('filetorename', 'renamedfile')
580
other_tree.rename_one('filetoreparent', 'renameddir/reparentedfile')
581
other_tree.remove(['dirtoremove', 'filetoremove'])
582
self.build_tree_contents([
584
('other/filetomodify', b'new content'),
585
('other/newfile', b'new file content')])
586
other_tree.add('newfile')
587
other_tree.add('newdir/')
588
other_tree.commit('modify all sample files and dirs.')
591
this_tree.merge_from_branch(other_tree.branch)
592
reporter = CapturingReporter()
593
this_tree.commit('do the commit', reporter=reporter)
595
('change', 'modified', 'filetomodify'),
596
('change', 'added', 'newdir'),
597
('change', 'added', 'newfile'),
598
('renamed', 'renamed', 'dirtorename', 'renameddir'),
599
('renamed', 'renamed', 'filetorename', 'renamedfile'),
600
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
601
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
602
('deleted', 'dirtoremove'),
603
('deleted', 'filetoremove'),
605
result = set(reporter.calls)
606
missing = expected - result
607
new = result - expected
608
self.assertEqual((set(), set()), (missing, new))
610
def test_commit_removals_respects_filespec(self):
611
"""Commit respects the specified_files for removals."""
612
tree = self.make_branch_and_tree('.')
613
self.build_tree(['a', 'b'])
615
tree.commit('added a, b')
616
tree.remove(['a', 'b'])
617
tree.commit('removed a', specific_files='a')
618
basis = tree.basis_tree()
619
with tree.lock_read():
620
self.assertFalse(basis.is_versioned('a'))
621
self.assertTrue(basis.is_versioned('b'))
623
def test_commit_saves_1ms_timestamp(self):
624
"""Passing in a timestamp is saved with 1ms resolution"""
625
tree = self.make_branch_and_tree('.')
626
self.build_tree(['a'])
628
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
631
rev = tree.branch.repository.get_revision(b'a1')
632
self.assertEqual(1153248633.419, rev.timestamp)
634
def test_commit_has_1ms_resolution(self):
635
"""Allowing commit to generate the timestamp also has 1ms resolution"""
636
tree = self.make_branch_and_tree('.')
637
self.build_tree(['a'])
639
tree.commit('added a', rev_id=b'a1')
641
rev = tree.branch.repository.get_revision(b'a1')
642
timestamp = rev.timestamp
643
timestamp_1ms = round(timestamp, 3)
644
self.assertEqual(timestamp_1ms, timestamp)
646
def assertBasisTreeKind(self, kind, tree, path):
647
basis = tree.basis_tree()
650
self.assertEqual(kind, basis.kind(path))
654
def test_commit_kind_changes(self):
655
self.requireFeature(SymlinkFeature)
656
tree = self.make_branch_and_tree('.')
657
os.symlink('target', 'name')
658
tree.add('name', b'a-file-id')
659
tree.commit('Added a symlink')
660
self.assertBasisTreeKind('symlink', tree, 'name')
663
self.build_tree(['name'])
664
tree.commit('Changed symlink to file')
665
self.assertBasisTreeKind('file', tree, 'name')
668
os.symlink('target', 'name')
669
tree.commit('file to symlink')
670
self.assertBasisTreeKind('symlink', tree, 'name')
674
tree.commit('symlink to directory')
675
self.assertBasisTreeKind('directory', tree, 'name')
678
os.symlink('target', 'name')
679
tree.commit('directory to symlink')
680
self.assertBasisTreeKind('symlink', tree, 'name')
682
# prepare for directory <-> file tests
685
tree.commit('symlink to directory')
686
self.assertBasisTreeKind('directory', tree, 'name')
689
self.build_tree(['name'])
690
tree.commit('Changed directory to file')
691
self.assertBasisTreeKind('file', tree, 'name')
695
tree.commit('file to directory')
696
self.assertBasisTreeKind('directory', tree, 'name')
698
def test_commit_unversioned_specified(self):
699
"""Commit should raise if specified files isn't in basis or worktree"""
700
tree = self.make_branch_and_tree('.')
701
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
702
'message', specific_files=['bogus'])
704
class Callback(object):
706
def __init__(self, message, testcase):
708
self.message = message
709
self.testcase = testcase
711
def __call__(self, commit_obj):
713
self.testcase.assertTrue(isinstance(commit_obj, Commit))
716
def test_commit_callback(self):
717
"""Commit should invoke a callback to get the message"""
719
tree = self.make_branch_and_tree('.')
722
except Exception as e:
723
self.assertTrue(isinstance(e, BzrError))
724
self.assertEqual('The message or message_callback keyword'
725
' parameter is required for commit().', str(e))
727
self.fail('exception not raised')
728
cb = self.Callback(u'commit 1', self)
729
tree.commit(message_callback=cb)
730
self.assertTrue(cb.called)
731
repository = tree.branch.repository
732
message = repository.get_revision(tree.last_revision()).message
733
self.assertEqual('commit 1', message)
735
def test_no_callback_pointless(self):
736
"""Callback should not be invoked for pointless commit"""
737
tree = self.make_branch_and_tree('.')
738
cb = self.Callback(u'commit 2', self)
739
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
740
allow_pointless=False)
741
self.assertFalse(cb.called)
743
def test_no_callback_netfailure(self):
744
"""Callback should not be invoked if connectivity fails"""
745
tree = self.make_branch_and_tree('.')
746
cb = self.Callback(u'commit 2', self)
747
repository = tree.branch.repository
748
# simulate network failure
749
def raise_(self, arg, arg2, arg3=None, arg4=None):
750
raise errors.NoSuchFile('foo')
751
repository.add_inventory = raise_
752
repository.add_inventory_by_delta = raise_
753
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
754
self.assertFalse(cb.called)
756
def test_selected_file_merge_commit(self):
757
"""Ensure the correct error is raised"""
758
tree = self.make_branch_and_tree('foo')
759
# pending merge would turn into a left parent
760
tree.commit('commit 1')
761
tree.add_parent_tree_id(b'example')
762
self.build_tree(['foo/bar', 'foo/baz'])
763
tree.add(['bar', 'baz'])
764
err = self.assertRaises(CannotCommitSelectedFileMerge,
765
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
766
self.assertEqual(['bar', 'baz'], err.files)
767
self.assertEqual('Selected-file commit of merges is not supported'
768
' yet: files bar, baz', str(err))
770
def test_commit_ordering(self):
771
"""Test of corner-case commit ordering error"""
772
tree = self.make_branch_and_tree('.')
773
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
774
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
776
self.build_tree(['a/c/d/'])
778
tree.rename_one('a/z/x', 'a/c/d/x')
779
tree.commit('test', specific_files=['a/z/y'])
781
def test_commit_no_author(self):
782
"""The default kwarg author in MutableTree.commit should not add
783
the 'author' revision property.
785
tree = self.make_branch_and_tree('foo')
786
rev_id = tree.commit('commit 1')
787
rev = tree.branch.repository.get_revision(rev_id)
788
self.assertFalse('author' in rev.properties)
789
self.assertFalse('authors' in rev.properties)
791
def test_commit_author(self):
792
"""Passing a non-empty authors kwarg to MutableTree.commit should add
793
the 'author' revision property.
795
tree = self.make_branch_and_tree('foo')
796
rev_id = tree.commit(
798
authors=['John Doe <jdoe@example.com>'])
799
rev = tree.branch.repository.get_revision(rev_id)
800
self.assertEqual('John Doe <jdoe@example.com>',
801
rev.properties['authors'])
802
self.assertFalse('author' in rev.properties)
804
def test_commit_empty_authors_list(self):
805
"""Passing an empty list to authors shouldn't add the property."""
806
tree = self.make_branch_and_tree('foo')
807
rev_id = tree.commit('commit 1', authors=[])
808
rev = tree.branch.repository.get_revision(rev_id)
809
self.assertFalse('author' in rev.properties)
810
self.assertFalse('authors' in rev.properties)
812
def test_multiple_authors(self):
813
tree = self.make_branch_and_tree('foo')
814
rev_id = tree.commit('commit 1',
815
authors=['John Doe <jdoe@example.com>',
816
'Jane Rey <jrey@example.com>'])
817
rev = tree.branch.repository.get_revision(rev_id)
818
self.assertEqual('John Doe <jdoe@example.com>\n'
819
'Jane Rey <jrey@example.com>', rev.properties['authors'])
820
self.assertFalse('author' in rev.properties)
822
def test_author_with_newline_rejected(self):
823
tree = self.make_branch_and_tree('foo')
824
self.assertRaises(AssertionError, tree.commit, 'commit 1',
825
authors=['John\nDoe <jdoe@example.com>'])
827
def test_commit_with_checkout_and_branch_sharing_repo(self):
828
repo = self.make_repository('repo', shared=True)
829
# make_branch_and_tree ignores shared repos
830
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
831
tree2 = branch.create_checkout('repo/tree2')
832
tree2.commit('message', rev_id=b'rev1')
833
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
836
class FilterExcludedTests(TestCase):
838
def test_add_file_not_excluded(self):
840
('fid', (None, 'newpath'),
841
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
842
('file', 'file'), (True, True))]
843
self.assertEqual(changes, list(filter_excluded(changes, ['otherpath'])))
845
def test_add_file_excluded(self):
847
('fid', (None, 'newpath'),
848
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
849
('file', 'file'), (True, True))]
850
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
852
def test_delete_file_excluded(self):
854
('fid', ('somepath', None),
855
0, (False, None), ('pid', None), ('newpath', None),
856
('file', None), (True, None))]
857
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
859
def test_move_from_or_to_excluded(self):
861
('fid', ('oldpath', 'newpath'),
862
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
863
('file', 'file'), (True, True))]
864
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
865
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))