51
88
"""Commit and check two versions of a single file."""
52
89
wt = self.make_branch_and_tree('.')
54
file('hello', 'w').write('hello world')
91
with open('hello', 'w') as f:
92
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
94
rev1 = wt.commit(message='add hello')
96
with open('hello', 'w') as f:
98
rev2 = wt.commit(message='commit 2')
100
eq = self.assertEqual
64
rh = b.revision_history()
65
rev = b.repository.get_revision(rh[0])
102
rev = b.repository.get_revision(rev1)
66
103
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'])
105
tree1 = b.repository.revision_tree(rev1)
107
text = tree1.get_file_text('hello')
109
self.assertEqual(b'hello world', text)
111
tree2 = b.repository.revision_tree(rev2)
113
text = tree2.get_file_text('hello')
115
self.assertEqual(b'version 2', text)
117
def test_commit_lossy_native(self):
118
"""Attempt a lossy commit to a native branch."""
119
wt = self.make_branch_and_tree('.')
121
with open('hello', 'w') as f:
122
f.write('hello world')
124
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
125
self.assertEqual(b'revid', revid)
127
def test_commit_lossy_foreign(self):
128
"""Attempt a lossy commit to a foreign branch."""
129
test_foreign.register_dummy_foreign_for_test(self)
130
wt = self.make_branch_and_tree('.',
131
format=test_foreign.DummyForeignVcsDirFormat())
133
with open('hello', 'w') as f:
134
f.write('hello world')
136
revid = wt.commit(message='add hello', lossy=True,
137
timestamp=1302659388, timezone=0)
138
self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
140
def test_commit_bound_lossy_foreign(self):
141
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
142
test_foreign.register_dummy_foreign_for_test(self)
143
foreign_branch = self.make_branch('foreign',
144
format=test_foreign.DummyForeignVcsDirFormat())
145
wt = foreign_branch.create_checkout("local")
147
with open('local/hello', 'w') as f:
148
f.write('hello world')
150
revid = wt.commit(message='add hello', lossy=True,
151
timestamp=1302659388, timezone=0)
152
self.assertEqual(b'dummy-v1:1302659388-0-0', revid)
153
self.assertEqual(b'dummy-v1:1302659388-0-0',
154
foreign_branch.last_revision())
155
self.assertEqual(b'dummy-v1:1302659388-0-0',
156
wt.branch.last_revision())
158
def test_missing_commit(self):
159
"""Test a commit with a missing file"""
160
wt = self.make_branch_and_tree('.')
162
with open('hello', 'w') as f:
163
f.write('hello world')
164
wt.add(['hello'], [b'hello-id'])
81
165
wt.commit(message='add hello')
83
167
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'))
168
reporter = CapturingReporter()
169
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
171
[('missing', u'hello'), ('deleted', u'hello')],
174
tree = b.repository.revision_tree(b'rev2')
175
self.assertFalse(tree.has_id(b'hello-id'))
177
def test_partial_commit_move(self):
178
"""Test a partial commit where a file was renamed but not committed.
180
https://bugs.launchpad.net/bzr/+bug/83039
182
If not handled properly, commit will try to snapshot
183
dialog.py with olive/ as a parent, while
184
olive/ has not been snapshotted yet.
186
wt = self.make_branch_and_tree('.')
188
self.build_tree(['annotate/', 'annotate/foo.py',
189
'olive/', 'olive/dialog.py'
191
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
192
wt.commit(message='add files')
193
wt.rename_one("olive/dialog.py", "aaa")
194
self.build_tree_contents([('annotate/foo.py', b'modified\n')])
195
wt.commit('renamed hello', specific_files=["annotate"])
89
197
def test_pointless_commit(self):
90
198
"""Commit refuses unless there are changes or it's forced."""
91
199
wt = self.make_branch_and_tree('.')
93
file('hello', 'w').write('hello')
201
with open('hello', 'w') as f:
95
204
wt.commit(message='add hello')
96
self.assertEquals(b.revno(), 1)
205
self.assertEqual(b.revno(), 1)
97
206
self.assertRaises(PointlessCommit,
100
209
allow_pointless=False)
101
self.assertEquals(b.revno(), 1)
210
self.assertEqual(b.revno(), 1)
103
212
def test_commit_empty(self):
104
213
"""Commiting an empty tree works."""
105
214
wt = self.make_branch_and_tree('.')
110
219
message='empty tree',
111
220
allow_pointless=False)
112
221
wt.commit(message='empty tree', allow_pointless=True)
113
self.assertEquals(b.revno(), 2)
222
self.assertEqual(b.revno(), 2)
115
224
def test_selective_delete(self):
116
225
"""Selective commit in tree with deletions"""
117
226
wt = self.make_branch_and_tree('.')
119
file('hello', 'w').write('hello')
120
file('buongia', 'w').write('buongia')
228
with open('hello', 'w') as f:
230
with open('buongia', 'w') as f:
121
232
wt.add(['hello', 'buongia'],
122
['hello-id', 'buongia-id'])
233
[b'hello-id', b'buongia-id'])
123
234
wt.commit(message='add files',
235
rev_id=b'test@rev-1')
126
237
os.remove('hello')
127
file('buongia', 'w').write('new text')
238
with open('buongia', 'w') as f:
128
240
wt.commit(message='update text',
129
specific_files=['buongia'],
130
allow_pointless=False,
241
specific_files=['buongia'],
242
allow_pointless=False,
243
rev_id=b'test@rev-2')
133
245
wt.commit(message='remove hello',
134
specific_files=['hello'],
135
allow_pointless=False,
246
specific_files=['hello'],
247
allow_pointless=False,
248
rev_id=b'test@rev-3')
138
eq = self.assertEquals
250
eq = self.assertEqual
141
tree2 = b.repository.revision_tree('test@rev-2')
253
tree2 = b.repository.revision_tree(b'test@rev-2')
255
self.addCleanup(tree2.unlock)
142
256
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')
257
self.assertEqual(tree2.get_file_text('hello'), b'hello')
258
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
260
tree3 = b.repository.revision_tree(b'test@rev-3')
262
self.addCleanup(tree3.unlock)
147
263
self.assertFalse(tree3.has_filename('hello'))
148
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
264
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
150
266
def test_commit_rename(self):
151
267
"""Test commit of a revision where a file is renamed."""
152
268
tree = self.make_branch_and_tree('.')
154
270
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)
271
tree.add(['hello'], [b'hello-id'])
272
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
158
274
tree.rename_one('hello', 'fruity')
159
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
275
tree.commit(message='renamed', rev_id=b'test@rev-2',
276
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')
278
eq = self.assertEqual
279
tree1 = b.repository.revision_tree(b'test@rev-1')
281
self.addCleanup(tree1.unlock)
282
eq(tree1.id2path(b'hello-id'), 'hello')
283
eq(tree1.get_file_text('hello'), b'contents of hello\n')
165
284
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')
285
self.check_tree_shape(tree1, ['hello'])
286
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')
288
tree2 = b.repository.revision_tree(b'test@rev-2')
290
self.addCleanup(tree2.unlock)
291
eq(tree2.id2path(b'hello-id'), 'fruity')
292
eq(tree2.get_file_text('fruity'), b'contents of hello\n')
293
self.check_tree_shape(tree2, ['fruity'])
294
eq(tree2.get_file_revision('fruity'), b'test@rev-2')
177
296
def test_reused_rev_id(self):
178
297
"""Test that a revision id cannot be reused in a branch"""
179
298
wt = self.make_branch_and_tree('.')
181
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
300
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
182
301
self.assertRaises(Exception,
184
303
message='reused id',
304
rev_id=b'test@rev-1',
186
305
allow_pointless=True)
188
307
def test_commit_move(self):
189
308
"""Test commit of revisions with moved files and directories"""
190
eq = self.assertEquals
309
eq = self.assertEqual
191
310
wt = self.make_branch_and_tree('.')
194
313
self.build_tree(['hello', 'a/', 'b/'])
195
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
314
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
196
315
wt.commit('initial', rev_id=r1, allow_pointless=False)
197
316
wt.move(['hello'], 'a')
199
318
wt.commit('two', rev_id=r2, allow_pointless=False)
200
self.check_inventory_shape(wt.read_working_inventory(),
201
['a', 'a/hello', 'b'])
321
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
203
325
wt.move(['b'], 'a')
205
327
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'])
330
self.check_tree_shape(wt,
331
['a/', 'a/hello', 'a/b/'])
332
self.check_tree_shape(b.repository.revision_tree(r3),
333
['a/', 'a/hello', 'a/b/'])
211
337
wt.move(['a/hello'], 'a/b')
213
339
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)
342
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
346
inv = b.repository.get_inventory(r4)
347
eq(inv.get_entry(b'hello-id').revision, r4)
348
eq(inv.get_entry(b'a-id').revision, r1)
349
eq(inv.get_entry(b'b-id').revision, r3)
222
351
def test_removed_commit(self):
223
352
"""Commit with a removed file"""
224
353
wt = self.make_branch_and_tree('.')
226
file('hello', 'w').write('hello world')
227
wt.add(['hello'], ['hello-id'])
355
with open('hello', 'w') as f:
356
f.write('hello world')
357
wt.add(['hello'], [b'hello-id'])
228
358
wt.commit(message='add hello')
229
359
wt.remove('hello')
230
wt.commit('removed hello', rev_id='rev2')
360
wt.commit('removed hello', rev_id=b'rev2')
232
tree = b.repository.revision_tree('rev2')
233
self.assertFalse(tree.has_id('hello-id'))
362
tree = b.repository.revision_tree(b'rev2')
363
self.assertFalse(tree.has_id(b'hello-id'))
235
365
def test_committed_ancestry(self):
236
366
"""Test commit appends revisions to ancestry."""
240
370
for i in range(4):
241
file('hello', 'w').write((str(i) * 4) + '\n')
371
with open('hello', 'w') as f:
372
f.write((str(i) * 4) + '\n')
243
wt.add(['hello'], ['hello-id'])
244
rev_id = 'test@rev-%d' % (i+1)
374
wt.add(['hello'], [b'hello-id'])
375
rev_id = b'test@rev-%d' % (i + 1)
245
376
rev_ids.append(rev_id)
246
wt.commit(message='rev %d' % (i+1),
248
eq = self.assertEquals
249
eq(b.revision_history(), rev_ids)
377
wt.commit(message='rev %d' % (i + 1),
250
379
for i in range(4):
251
anc = b.repository.get_ancestry(rev_ids[i])
252
eq(anc, [None] + rev_ids[:i+1])
380
self.assertThat(rev_ids[:i + 1],
381
MatchesAncestry(b.repository, rev_ids[i]))
254
383
def test_commit_new_subdir_child_selective(self):
255
384
wt = self.make_branch_and_tree('.')
257
386
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
258
387
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)
388
[b'dirid', b'file1id', b'file2id'])
389
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id=b'1')
390
inv = b.repository.get_inventory(b'1')
391
self.assertEqual(b'1', inv.get_entry(b'dirid').revision)
392
self.assertEqual(b'1', inv.get_entry(b'file1id').revision)
264
393
# FIXME: This should raise a KeyError I think, rbc20051006
265
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
394
self.assertRaises(BzrError, inv.get_entry, b'file2id')
267
396
def test_strict_commit(self):
268
397
"""Try and commit with unknown files and strict = True, should fail."""
269
from bzrlib.errors import StrictCommitFailed
398
from ..errors import StrictCommitFailed
270
399
wt = self.make_branch_and_tree('.')
272
file('hello', 'w').write('hello world')
401
with open('hello', 'w') as f:
402
f.write('hello world')
274
file('goodbye', 'w').write('goodbye cruel world!')
404
with open('goodbye', 'w') as f:
405
f.write('goodbye cruel world!')
275
406
self.assertRaises(StrictCommitFailed, wt.commit,
276
message='add hello but not goodbye', strict=True)
407
message='add hello but not goodbye', strict=True)
278
409
def test_strict_commit_without_unknowns(self):
279
410
"""Try and commit with no unknown files and strict = True,
281
from bzrlib.errors import StrictCommitFailed
282
412
wt = self.make_branch_and_tree('.')
284
file('hello', 'w').write('hello world')
414
with open('hello', 'w') as f:
415
f.write('hello world')
286
417
wt.commit(message='add hello', strict=True)
300
433
wt = self.make_branch_and_tree('.')
302
file('hello', 'w').write('hello world')
435
with open('hello', 'w') as f:
436
f.write('hello world')
304
438
wt.commit(message='add hello', strict=False)
306
440
def test_signed_commit(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
442
import breezy.commit as commit
443
oldstrategy = breezy.gpg.GPGStrategy
310
444
wt = self.make_branch_and_tree('.')
311
445
branch = wt.branch
312
wt.commit("base", allow_pointless=True, rev_id='A')
313
self.failIf(branch.repository.has_signature_for_revision_id('A'))
446
wt.commit("base", allow_pointless=True, rev_id=b'A')
447
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
315
from bzrlib.testament import Testament
449
from ..bzr.testament import Testament
316
450
# 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'))
451
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
452
conf = config.MemoryStack(b'''
453
create_signatures=always
455
commit.Commit(config_stack=conf).commit(
456
message="base", allow_pointless=True, rev_id=b'B',
460
return breezy.gpg.LoopbackGPGStrategy(None).sign(
461
text, breezy.gpg.MODE_CLEAR)
462
self.assertEqual(sign(Testament.from_revision(branch.repository,
463
b'B').as_short_text()),
464
branch.repository.get_signature_text(b'B'))
326
bzrlib.gpg.GPGStrategy = oldstrategy
466
breezy.gpg.GPGStrategy = oldstrategy
328
468
def test_commit_failed_signature(self):
330
import bzrlib.commit as commit
331
oldstrategy = bzrlib.gpg.GPGStrategy
470
import breezy.commit as commit
471
oldstrategy = breezy.gpg.GPGStrategy
332
472
wt = self.make_branch_and_tree('.')
333
473
branch = wt.branch
334
wt.commit("base", allow_pointless=True, rev_id='A')
335
self.failIf(branch.repository.has_signature_for_revision_id('A'))
474
wt.commit("base", allow_pointless=True, rev_id=b'A')
475
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
337
from bzrlib.testament import Testament
338
477
# 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,
478
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
479
conf = config.MemoryStack(b'''
480
create_signatures=always
482
self.assertRaises(breezy.gpg.SigningFailed,
483
commit.Commit(config_stack=conf).commit,
344
485
allow_pointless=True,
347
488
branch = Branch.open(self.get_url('.'))
348
self.assertEqual(branch.revision_history(), ['A'])
349
self.failIf(branch.repository.has_revision('B'))
489
self.assertEqual(branch.last_revision(), b'A')
490
self.assertFalse(branch.repository.has_revision(b'B'))
351
bzrlib.gpg.GPGStrategy = oldstrategy
492
breezy.gpg.GPGStrategy = oldstrategy
353
494
def test_commit_invokes_hooks(self):
354
import bzrlib.commit as commit
495
import breezy.commit as commit
355
496
wt = self.make_branch_and_tree('.')
356
497
branch = wt.branch
358
500
def called(branch, rev_id):
359
501
calls.append('called')
360
bzrlib.ahook = called
502
breezy.ahook = called
362
config = BranchWithHooks(branch)
363
commit.Commit(config=config).commit(
365
allow_pointless=True,
366
rev_id='A', working_tree = wt)
504
conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
505
commit.Commit(config_stack=conf).commit(
506
message="base", allow_pointless=True, rev_id=b'A',
367
508
self.assertEqual(['called', 'called'], calls)
371
512
def test_commit_object_doesnt_set_nick(self):
372
513
# using the Commit object directly does not set the branch nick.
373
514
wt = self.make_branch_and_tree('.')
375
516
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
376
self.assertEquals(wt.branch.revno(), 1)
517
self.assertEqual(wt.branch.revno(), 1)
377
518
self.assertEqual({},
378
519
wt.branch.repository.get_revision(
379
wt.branch.last_revision()).properties)
520
wt.branch.last_revision()).properties)
381
522
def test_safe_master_lock(self):
382
523
os.mkdir('master')
392
533
self.assertRaises(LockContention, wt.commit, 'silly')
394
535
master_branch.unlock()
537
def test_commit_bound_merge(self):
538
# see bug #43959; commit of a merge in a bound branch fails to push
539
# the new commit into the master
540
master_branch = self.make_branch('master')
541
bound_tree = self.make_branch_and_tree('bound')
542
bound_tree.branch.bind(master_branch)
544
self.build_tree_contents(
545
[('bound/content_file', b'initial contents\n')])
546
bound_tree.add(['content_file'])
547
bound_tree.commit(message='woo!')
549
other_bzrdir = master_branch.controldir.sprout('other')
550
other_tree = other_bzrdir.open_workingtree()
552
# do a commit to the other branch changing the content file so
553
# that our commit after merging will have a merged revision in the
554
# content file history.
555
self.build_tree_contents(
556
[('other/content_file', b'change in other\n')])
557
other_tree.commit('change in other')
559
# do a merge into the bound branch from other, and then change the
560
# content file locally to force a new revision (rather than using the
561
# revision from other). This forces extra processing in commit.
562
bound_tree.merge_from_branch(other_tree.branch)
563
self.build_tree_contents(
564
[('bound/content_file', b'change in bound\n')])
566
# before #34959 was fixed, this failed with 'revision not present in
567
# weave' when trying to implicitly push from the bound branch to the master
568
bound_tree.commit(message='commit of merge in bound tree')
570
def test_commit_reporting_after_merge(self):
571
# when doing a commit of a merge, the reporter needs to still
572
# be called for each item that is added/removed/deleted.
573
this_tree = self.make_branch_and_tree('this')
574
# we need a bunch of files and dirs, to perform one action on each.
577
'this/dirtoreparent/',
580
'this/filetoreparent',
597
this_tree.commit('create_files')
598
other_dir = this_tree.controldir.sprout('other')
599
other_tree = other_dir.open_workingtree()
600
other_tree.lock_write()
601
# perform the needed actions on the files and dirs.
603
other_tree.rename_one('dirtorename', 'renameddir')
604
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
605
other_tree.rename_one('filetorename', 'renamedfile')
606
other_tree.rename_one(
607
'filetoreparent', 'renameddir/reparentedfile')
608
other_tree.remove(['dirtoremove', 'filetoremove'])
609
self.build_tree_contents([
611
('other/filetomodify', b'new content'),
612
('other/newfile', b'new file content')])
613
other_tree.add('newfile')
614
other_tree.add('newdir/')
615
other_tree.commit('modify all sample files and dirs.')
618
this_tree.merge_from_branch(other_tree.branch)
619
reporter = CapturingReporter()
620
this_tree.commit('do the commit', reporter=reporter)
622
('change', 'modified', 'filetomodify'),
623
('change', 'added', 'newdir'),
624
('change', 'added', 'newfile'),
625
('renamed', 'renamed', 'dirtorename', 'renameddir'),
626
('renamed', 'renamed', 'filetorename', 'renamedfile'),
627
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
628
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
629
('deleted', 'dirtoremove'),
630
('deleted', 'filetoremove'),
632
result = set(reporter.calls)
633
missing = expected - result
634
new = result - expected
635
self.assertEqual((set(), set()), (missing, new))
637
def test_commit_removals_respects_filespec(self):
638
"""Commit respects the specified_files for removals."""
639
tree = self.make_branch_and_tree('.')
640
self.build_tree(['a', 'b'])
642
tree.commit('added a, b')
643
tree.remove(['a', 'b'])
644
tree.commit('removed a', specific_files='a')
645
basis = tree.basis_tree()
646
with tree.lock_read():
647
self.assertFalse(basis.is_versioned('a'))
648
self.assertTrue(basis.is_versioned('b'))
650
def test_commit_saves_1ms_timestamp(self):
651
"""Passing in a timestamp is saved with 1ms resolution"""
652
tree = self.make_branch_and_tree('.')
653
self.build_tree(['a'])
655
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
658
rev = tree.branch.repository.get_revision(b'a1')
659
self.assertEqual(1153248633.419, rev.timestamp)
661
def test_commit_has_1ms_resolution(self):
662
"""Allowing commit to generate the timestamp also has 1ms resolution"""
663
tree = self.make_branch_and_tree('.')
664
self.build_tree(['a'])
666
tree.commit('added a', rev_id=b'a1')
668
rev = tree.branch.repository.get_revision(b'a1')
669
timestamp = rev.timestamp
670
timestamp_1ms = round(timestamp, 3)
671
self.assertEqual(timestamp_1ms, timestamp)
673
def assertBasisTreeKind(self, kind, tree, path):
674
basis = tree.basis_tree()
677
self.assertEqual(kind, basis.kind(path))
681
def test_unsupported_symlink_commit(self):
682
self.requireFeature(SymlinkFeature)
683
tree = self.make_branch_and_tree('.')
684
self.build_tree(['hello'])
686
tree.commit('added hello', rev_id=b'hello_id')
687
os.symlink('hello', 'foo')
689
tree.commit('added foo', rev_id=b'foo_id')
691
trace.push_log_file(log)
692
os_symlink = getattr(os, 'symlink', None)
695
# At this point as bzr thinks symlinks are not supported
696
# we should get a warning about symlink foo and bzr should
697
# not think its removed.
699
self.build_tree(['world'])
701
tree.commit('added world', rev_id=b'world_id')
704
os.symlink = os_symlink
705
self.assertContainsRe(
707
b'Ignoring "foo" as symlinks are not '
708
b'supported on this filesystem\\.')
710
def test_commit_kind_changes(self):
711
self.requireFeature(SymlinkFeature)
712
tree = self.make_branch_and_tree('.')
713
os.symlink('target', 'name')
714
tree.add('name', b'a-file-id')
715
tree.commit('Added a symlink')
716
self.assertBasisTreeKind('symlink', tree, 'name')
719
self.build_tree(['name'])
720
tree.commit('Changed symlink to file')
721
self.assertBasisTreeKind('file', tree, 'name')
724
os.symlink('target', 'name')
725
tree.commit('file to symlink')
726
self.assertBasisTreeKind('symlink', tree, 'name')
730
tree.commit('symlink to directory')
731
self.assertBasisTreeKind('directory', tree, 'name')
734
os.symlink('target', 'name')
735
tree.commit('directory to symlink')
736
self.assertBasisTreeKind('symlink', tree, 'name')
738
# prepare for directory <-> file tests
741
tree.commit('symlink to directory')
742
self.assertBasisTreeKind('directory', tree, 'name')
745
self.build_tree(['name'])
746
tree.commit('Changed directory to file')
747
self.assertBasisTreeKind('file', tree, 'name')
751
tree.commit('file to directory')
752
self.assertBasisTreeKind('directory', tree, 'name')
754
def test_commit_unversioned_specified(self):
755
"""Commit should raise if specified files isn't in basis or worktree"""
756
tree = self.make_branch_and_tree('.')
757
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
758
'message', specific_files=['bogus'])
760
class Callback(object):
762
def __init__(self, message, testcase):
764
self.message = message
765
self.testcase = testcase
767
def __call__(self, commit_obj):
769
self.testcase.assertTrue(isinstance(commit_obj, Commit))
772
def test_commit_callback(self):
773
"""Commit should invoke a callback to get the message"""
775
tree = self.make_branch_and_tree('.')
778
except Exception as e:
779
self.assertTrue(isinstance(e, BzrError))
780
self.assertEqual('The message or message_callback keyword'
781
' parameter is required for commit().', str(e))
783
self.fail('exception not raised')
784
cb = self.Callback(u'commit 1', self)
785
tree.commit(message_callback=cb)
786
self.assertTrue(cb.called)
787
repository = tree.branch.repository
788
message = repository.get_revision(tree.last_revision()).message
789
self.assertEqual('commit 1', message)
791
def test_no_callback_pointless(self):
792
"""Callback should not be invoked for pointless commit"""
793
tree = self.make_branch_and_tree('.')
794
cb = self.Callback(u'commit 2', self)
795
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
796
allow_pointless=False)
797
self.assertFalse(cb.called)
799
def test_no_callback_netfailure(self):
800
"""Callback should not be invoked if connectivity fails"""
801
tree = self.make_branch_and_tree('.')
802
cb = self.Callback(u'commit 2', self)
803
repository = tree.branch.repository
804
# simulate network failure
806
def raise_(self, arg, arg2, arg3=None, arg4=None):
807
raise errors.NoSuchFile('foo')
808
repository.add_inventory = raise_
809
repository.add_inventory_by_delta = raise_
810
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
811
self.assertFalse(cb.called)
813
def test_selected_file_merge_commit(self):
814
"""Ensure the correct error is raised"""
815
tree = self.make_branch_and_tree('foo')
816
# pending merge would turn into a left parent
817
tree.commit('commit 1')
818
tree.add_parent_tree_id(b'example')
819
self.build_tree(['foo/bar', 'foo/baz'])
820
tree.add(['bar', 'baz'])
821
err = self.assertRaises(CannotCommitSelectedFileMerge,
822
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
823
self.assertEqual(['bar', 'baz'], err.files)
824
self.assertEqual('Selected-file commit of merges is not supported'
825
' yet: files bar, baz', str(err))
827
def test_commit_ordering(self):
828
"""Test of corner-case commit ordering error"""
829
tree = self.make_branch_and_tree('.')
830
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
831
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
833
self.build_tree(['a/c/d/'])
835
tree.rename_one('a/z/x', 'a/c/d/x')
836
tree.commit('test', specific_files=['a/z/y'])
838
def test_commit_no_author(self):
839
"""The default kwarg author in MutableTree.commit should not add
840
the 'author' revision property.
842
tree = self.make_branch_and_tree('foo')
843
rev_id = tree.commit('commit 1')
844
rev = tree.branch.repository.get_revision(rev_id)
845
self.assertFalse('author' in rev.properties)
846
self.assertFalse('authors' in rev.properties)
848
def test_commit_author(self):
849
"""Passing a non-empty authors kwarg to MutableTree.commit should add
850
the 'author' revision property.
852
tree = self.make_branch_and_tree('foo')
853
rev_id = tree.commit(
855
authors=['John Doe <jdoe@example.com>'])
856
rev = tree.branch.repository.get_revision(rev_id)
857
self.assertEqual('John Doe <jdoe@example.com>',
858
rev.properties['authors'])
859
self.assertFalse('author' in rev.properties)
861
def test_commit_empty_authors_list(self):
862
"""Passing an empty list to authors shouldn't add the property."""
863
tree = self.make_branch_and_tree('foo')
864
rev_id = tree.commit('commit 1', authors=[])
865
rev = tree.branch.repository.get_revision(rev_id)
866
self.assertFalse('author' in rev.properties)
867
self.assertFalse('authors' in rev.properties)
869
def test_multiple_authors(self):
870
tree = self.make_branch_and_tree('foo')
871
rev_id = tree.commit('commit 1',
872
authors=['John Doe <jdoe@example.com>',
873
'Jane Rey <jrey@example.com>'])
874
rev = tree.branch.repository.get_revision(rev_id)
875
self.assertEqual('John Doe <jdoe@example.com>\n'
876
'Jane Rey <jrey@example.com>', rev.properties['authors'])
877
self.assertFalse('author' in rev.properties)
879
def test_author_with_newline_rejected(self):
880
tree = self.make_branch_and_tree('foo')
881
self.assertRaises(AssertionError, tree.commit, 'commit 1',
882
authors=['John\nDoe <jdoe@example.com>'])
884
def test_commit_with_checkout_and_branch_sharing_repo(self):
885
repo = self.make_repository('repo', shared=True)
886
# make_branch_and_tree ignores shared repos
887
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
888
tree2 = branch.create_checkout('repo/tree2')
889
tree2.commit('message', rev_id=b'rev1')
890
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
893
class FilterExcludedTests(TestCase):
895
def test_add_file_not_excluded(self):
897
('fid', (None, 'newpath'),
898
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
899
('file', 'file'), (True, True))]
900
self.assertEqual(changes, list(
901
filter_excluded(changes, ['otherpath'])))
903
def test_add_file_excluded(self):
905
('fid', (None, 'newpath'),
906
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
907
('file', 'file'), (True, True))]
908
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
910
def test_delete_file_excluded(self):
912
('fid', ('somepath', None),
913
0, (False, None), ('pid', None), ('newpath', None),
914
('file', None), (True, None))]
915
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
917
def test_move_from_or_to_excluded(self):
919
('fid', ('oldpath', 'newpath'),
920
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
921
('file', 'file'), (True, True))]
922
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
923
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))