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:
90
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
92
rev1 = wt.commit(message='add hello')
94
with open('hello', 'w') as f:
96
rev2 = wt.commit(message='commit 2')
64
rh = b.revision_history()
65
rev = b.repository.get_revision(rh[0])
100
rev = b.repository.get_revision(rev1)
66
101
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'])
103
tree1 = b.repository.revision_tree(rev1)
105
text = tree1.get_file_text('hello')
107
self.assertEqual(b'hello world', text)
109
tree2 = b.repository.revision_tree(rev2)
111
text = tree2.get_file_text('hello')
113
self.assertEqual(b'version 2', text)
115
def test_commit_lossy_native(self):
116
"""Attempt a lossy commit to a native branch."""
117
wt = self.make_branch_and_tree('.')
119
with open('hello', 'w') as f:
120
f.write('hello world')
122
revid = wt.commit(message='add hello', rev_id=b'revid', lossy=True)
123
self.assertEqual(b'revid', revid)
125
def test_commit_lossy_foreign(self):
126
"""Attempt a lossy commit to a foreign branch."""
127
test_foreign.register_dummy_foreign_for_test(self)
128
wt = self.make_branch_and_tree('.',
129
format=test_foreign.DummyForeignVcsDirFormat())
131
with open('hello', 'w') as f:
132
f.write('hello world')
134
revid = wt.commit(message='add hello', lossy=True,
135
timestamp=1302659388, timezone=0)
136
self.assertEqual(b'dummy-v1:1302659388-0-UNKNOWN', revid)
138
def test_commit_bound_lossy_foreign(self):
139
"""Attempt a lossy commit to a bzr branch bound to a foreign branch."""
140
test_foreign.register_dummy_foreign_for_test(self)
141
foreign_branch = self.make_branch('foreign',
142
format=test_foreign.DummyForeignVcsDirFormat())
143
wt = foreign_branch.create_checkout("local")
145
with open('local/hello', 'w') as f:
146
f.write('hello world')
148
revid = wt.commit(message='add hello', lossy=True,
149
timestamp=1302659388, timezone=0)
150
self.assertEqual(b'dummy-v1:1302659388-0-0', revid)
151
self.assertEqual(b'dummy-v1:1302659388-0-0',
152
foreign_branch.last_revision())
153
self.assertEqual(b'dummy-v1:1302659388-0-0',
154
wt.branch.last_revision())
156
def test_missing_commit(self):
157
"""Test a commit with a missing file"""
158
wt = self.make_branch_and_tree('.')
160
with open('hello', 'w') as f:
161
f.write('hello world')
162
wt.add(['hello'], [b'hello-id'])
81
163
wt.commit(message='add hello')
83
165
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'))
166
reporter = CapturingReporter()
167
wt.commit('removed hello', rev_id=b'rev2', reporter=reporter)
169
[('missing', u'hello'), ('deleted', u'hello')],
172
tree = b.repository.revision_tree(b'rev2')
173
self.assertFalse(tree.has_id(b'hello-id'))
175
def test_partial_commit_move(self):
176
"""Test a partial commit where a file was renamed but not committed.
178
https://bugs.launchpad.net/bzr/+bug/83039
180
If not handled properly, commit will try to snapshot
181
dialog.py with olive/ as a parent, while
182
olive/ has not been snapshotted yet.
184
wt = self.make_branch_and_tree('.')
186
self.build_tree(['annotate/', 'annotate/foo.py',
187
'olive/', 'olive/dialog.py'
189
wt.add(['annotate', 'olive', 'annotate/foo.py', 'olive/dialog.py'])
190
wt.commit(message='add files')
191
wt.rename_one("olive/dialog.py", "aaa")
192
self.build_tree_contents([('annotate/foo.py', b'modified\n')])
193
wt.commit('renamed hello', specific_files=["annotate"])
89
195
def test_pointless_commit(self):
90
196
"""Commit refuses unless there are changes or it's forced."""
91
197
wt = self.make_branch_and_tree('.')
93
file('hello', 'w').write('hello')
199
with open('hello', 'w') as f:
95
202
wt.commit(message='add hello')
96
self.assertEquals(b.revno(), 1)
203
self.assertEqual(b.revno(), 1)
97
204
self.assertRaises(PointlessCommit,
100
207
allow_pointless=False)
101
self.assertEquals(b.revno(), 1)
208
self.assertEqual(b.revno(), 1)
103
210
def test_commit_empty(self):
104
211
"""Commiting an empty tree works."""
105
212
wt = self.make_branch_and_tree('.')
110
217
message='empty tree',
111
218
allow_pointless=False)
112
219
wt.commit(message='empty tree', allow_pointless=True)
113
self.assertEquals(b.revno(), 2)
220
self.assertEqual(b.revno(), 2)
115
222
def test_selective_delete(self):
116
223
"""Selective commit in tree with deletions"""
117
224
wt = self.make_branch_and_tree('.')
119
file('hello', 'w').write('hello')
120
file('buongia', 'w').write('buongia')
226
with open('hello', 'w') as f:
228
with open('buongia', 'w') as f:
121
230
wt.add(['hello', 'buongia'],
122
['hello-id', 'buongia-id'])
231
[b'hello-id', b'buongia-id'])
123
232
wt.commit(message='add files',
233
rev_id=b'test@rev-1')
126
235
os.remove('hello')
127
file('buongia', 'w').write('new text')
236
with open('buongia', 'w') as f:
128
238
wt.commit(message='update text',
129
specific_files=['buongia'],
130
allow_pointless=False,
239
specific_files=['buongia'],
240
allow_pointless=False,
241
rev_id=b'test@rev-2')
133
243
wt.commit(message='remove hello',
134
specific_files=['hello'],
135
allow_pointless=False,
244
specific_files=['hello'],
245
allow_pointless=False,
246
rev_id=b'test@rev-3')
138
eq = self.assertEquals
248
eq = self.assertEqual
141
tree2 = b.repository.revision_tree('test@rev-2')
251
tree2 = b.repository.revision_tree(b'test@rev-2')
253
self.addCleanup(tree2.unlock)
142
254
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')
255
self.assertEqual(tree2.get_file_text('hello'), b'hello')
256
self.assertEqual(tree2.get_file_text('buongia'), b'new text')
258
tree3 = b.repository.revision_tree(b'test@rev-3')
260
self.addCleanup(tree3.unlock)
147
261
self.assertFalse(tree3.has_filename('hello'))
148
self.assertEquals(tree3.get_file_text('buongia-id'), 'new text')
262
self.assertEqual(tree3.get_file_text('buongia'), b'new text')
150
264
def test_commit_rename(self):
151
265
"""Test commit of a revision where a file is renamed."""
152
266
tree = self.make_branch_and_tree('.')
154
268
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)
269
tree.add(['hello'], [b'hello-id'])
270
tree.commit(message='one', rev_id=b'test@rev-1', allow_pointless=False)
158
272
tree.rename_one('hello', 'fruity')
159
tree.commit(message='renamed', rev_id='test@rev-2', allow_pointless=False)
273
tree.commit(message='renamed', rev_id=b'test@rev-2',
274
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')
276
eq = self.assertEqual
277
tree1 = b.repository.revision_tree(b'test@rev-1')
279
self.addCleanup(tree1.unlock)
280
eq(tree1.id2path(b'hello-id'), 'hello')
281
eq(tree1.get_file_text('hello'), b'contents of hello\n')
165
282
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')
283
self.check_tree_shape(tree1, ['hello'])
284
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')
286
tree2 = b.repository.revision_tree(b'test@rev-2')
288
self.addCleanup(tree2.unlock)
289
eq(tree2.id2path(b'hello-id'), 'fruity')
290
eq(tree2.get_file_text('fruity'), b'contents of hello\n')
291
self.check_tree_shape(tree2, ['fruity'])
292
eq(tree2.get_file_revision('fruity'), b'test@rev-2')
177
294
def test_reused_rev_id(self):
178
295
"""Test that a revision id cannot be reused in a branch"""
179
296
wt = self.make_branch_and_tree('.')
181
wt.commit('initial', rev_id='test@rev-1', allow_pointless=True)
298
wt.commit('initial', rev_id=b'test@rev-1', allow_pointless=True)
182
299
self.assertRaises(Exception,
184
301
message='reused id',
302
rev_id=b'test@rev-1',
186
303
allow_pointless=True)
188
305
def test_commit_move(self):
189
306
"""Test commit of revisions with moved files and directories"""
190
eq = self.assertEquals
307
eq = self.assertEqual
191
308
wt = self.make_branch_and_tree('.')
194
311
self.build_tree(['hello', 'a/', 'b/'])
195
wt.add(['hello', 'a', 'b'], ['hello-id', 'a-id', 'b-id'])
312
wt.add(['hello', 'a', 'b'], [b'hello-id', b'a-id', b'b-id'])
196
313
wt.commit('initial', rev_id=r1, allow_pointless=False)
197
314
wt.move(['hello'], 'a')
199
316
wt.commit('two', rev_id=r2, allow_pointless=False)
200
self.check_inventory_shape(wt.read_working_inventory(),
201
['a', 'a/hello', 'b'])
319
self.check_tree_shape(wt, ['a/', 'a/hello', 'b/'])
203
323
wt.move(['b'], 'a')
205
325
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'])
328
self.check_tree_shape(wt,
329
['a/', 'a/hello', 'a/b/'])
330
self.check_tree_shape(b.repository.revision_tree(r3),
331
['a/', 'a/hello', 'a/b/'])
211
335
wt.move(['a/hello'], 'a/b')
213
337
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)
340
self.check_tree_shape(wt, ['a/', 'a/b/hello', 'a/b/'])
344
inv = b.repository.get_inventory(r4)
345
eq(inv.get_entry(b'hello-id').revision, r4)
346
eq(inv.get_entry(b'a-id').revision, r1)
347
eq(inv.get_entry(b'b-id').revision, r3)
222
349
def test_removed_commit(self):
223
350
"""Commit with a removed file"""
224
351
wt = self.make_branch_and_tree('.')
226
file('hello', 'w').write('hello world')
227
wt.add(['hello'], ['hello-id'])
353
with open('hello', 'w') as f:
354
f.write('hello world')
355
wt.add(['hello'], [b'hello-id'])
228
356
wt.commit(message='add hello')
229
357
wt.remove('hello')
230
wt.commit('removed hello', rev_id='rev2')
358
wt.commit('removed hello', rev_id=b'rev2')
232
tree = b.repository.revision_tree('rev2')
233
self.assertFalse(tree.has_id('hello-id'))
360
tree = b.repository.revision_tree(b'rev2')
361
self.assertFalse(tree.has_id(b'hello-id'))
235
363
def test_committed_ancestry(self):
236
364
"""Test commit appends revisions to ancestry."""
240
368
for i in range(4):
241
file('hello', 'w').write((str(i) * 4) + '\n')
369
with open('hello', 'w') as f:
370
f.write((str(i) * 4) + '\n')
243
wt.add(['hello'], ['hello-id'])
244
rev_id = 'test@rev-%d' % (i+1)
372
wt.add(['hello'], [b'hello-id'])
373
rev_id = b'test@rev-%d' % (i + 1)
245
374
rev_ids.append(rev_id)
246
wt.commit(message='rev %d' % (i+1),
248
eq = self.assertEquals
249
eq(b.revision_history(), rev_ids)
375
wt.commit(message='rev %d' % (i + 1),
250
377
for i in range(4):
251
anc = b.repository.get_ancestry(rev_ids[i])
252
eq(anc, [None] + rev_ids[:i+1])
378
self.assertThat(rev_ids[:i + 1],
379
MatchesAncestry(b.repository, rev_ids[i]))
254
381
def test_commit_new_subdir_child_selective(self):
255
382
wt = self.make_branch_and_tree('.')
257
384
self.build_tree(['dir/', 'dir/file1', 'dir/file2'])
258
385
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)
386
[b'dirid', b'file1id', b'file2id'])
387
wt.commit('dir/file1', specific_files=['dir/file1'], rev_id=b'1')
388
inv = b.repository.get_inventory(b'1')
389
self.assertEqual(b'1', inv.get_entry(b'dirid').revision)
390
self.assertEqual(b'1', inv.get_entry(b'file1id').revision)
264
391
# FIXME: This should raise a KeyError I think, rbc20051006
265
self.assertRaises(BzrError, inv.__getitem__, 'file2id')
392
self.assertRaises(BzrError, inv.get_entry, b'file2id')
267
394
def test_strict_commit(self):
268
395
"""Try and commit with unknown files and strict = True, should fail."""
269
from bzrlib.errors import StrictCommitFailed
396
from ..errors import StrictCommitFailed
270
397
wt = self.make_branch_and_tree('.')
272
file('hello', 'w').write('hello world')
399
with open('hello', 'w') as f:
400
f.write('hello world')
274
file('goodbye', 'w').write('goodbye cruel world!')
402
with open('goodbye', 'w') as f:
403
f.write('goodbye cruel world!')
275
404
self.assertRaises(StrictCommitFailed, wt.commit,
276
message='add hello but not goodbye', strict=True)
405
message='add hello but not goodbye', strict=True)
278
407
def test_strict_commit_without_unknowns(self):
279
408
"""Try and commit with no unknown files and strict = True,
281
from bzrlib.errors import StrictCommitFailed
282
410
wt = self.make_branch_and_tree('.')
284
file('hello', 'w').write('hello world')
412
with open('hello', 'w') as f:
413
f.write('hello world')
286
415
wt.commit(message='add hello', strict=True)
300
431
wt = self.make_branch_and_tree('.')
302
file('hello', 'w').write('hello world')
433
with open('hello', 'w') as f:
434
f.write('hello world')
304
436
wt.commit(message='add hello', strict=False)
306
438
def test_signed_commit(self):
308
import bzrlib.commit as commit
309
oldstrategy = bzrlib.gpg.GPGStrategy
440
import breezy.commit as commit
441
oldstrategy = breezy.gpg.GPGStrategy
310
442
wt = self.make_branch_and_tree('.')
311
443
branch = wt.branch
312
wt.commit("base", allow_pointless=True, rev_id='A')
313
self.failIf(branch.repository.has_signature_for_revision_id('A'))
444
wt.commit("base", allow_pointless=True, rev_id=b'A')
445
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
315
from bzrlib.testament import Testament
447
from ..bzr.testament import Testament
316
448
# 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'))
449
breezy.gpg.GPGStrategy = breezy.gpg.LoopbackGPGStrategy
450
conf = config.MemoryStack(b'''
451
create_signatures=always
453
commit.Commit(config_stack=conf).commit(
454
message="base", allow_pointless=True, rev_id=b'B',
458
return breezy.gpg.LoopbackGPGStrategy(None).sign(
459
text, breezy.gpg.MODE_CLEAR)
460
self.assertEqual(sign(Testament.from_revision(branch.repository,
461
b'B').as_short_text()),
462
branch.repository.get_signature_text(b'B'))
326
bzrlib.gpg.GPGStrategy = oldstrategy
464
breezy.gpg.GPGStrategy = oldstrategy
328
466
def test_commit_failed_signature(self):
330
import bzrlib.commit as commit
331
oldstrategy = bzrlib.gpg.GPGStrategy
468
import breezy.commit as commit
469
oldstrategy = breezy.gpg.GPGStrategy
332
470
wt = self.make_branch_and_tree('.')
333
471
branch = wt.branch
334
wt.commit("base", allow_pointless=True, rev_id='A')
335
self.failIf(branch.repository.has_signature_for_revision_id('A'))
472
wt.commit("base", allow_pointless=True, rev_id=b'A')
473
self.assertFalse(branch.repository.has_signature_for_revision_id(b'A'))
337
from bzrlib.testament import Testament
338
475
# 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,
476
breezy.gpg.GPGStrategy = breezy.gpg.DisabledGPGStrategy
477
conf = config.MemoryStack(b'''
478
create_signatures=always
480
self.assertRaises(breezy.gpg.SigningFailed,
481
commit.Commit(config_stack=conf).commit,
344
483
allow_pointless=True,
347
486
branch = Branch.open(self.get_url('.'))
348
self.assertEqual(branch.revision_history(), ['A'])
349
self.failIf(branch.repository.has_revision('B'))
487
self.assertEqual(branch.last_revision(), b'A')
488
self.assertFalse(branch.repository.has_revision(b'B'))
351
bzrlib.gpg.GPGStrategy = oldstrategy
490
breezy.gpg.GPGStrategy = oldstrategy
353
492
def test_commit_invokes_hooks(self):
354
import bzrlib.commit as commit
493
import breezy.commit as commit
355
494
wt = self.make_branch_and_tree('.')
356
495
branch = wt.branch
358
498
def called(branch, rev_id):
359
499
calls.append('called')
360
bzrlib.ahook = called
500
breezy.ahook = called
362
config = BranchWithHooks(branch)
363
commit.Commit(config=config).commit(
365
allow_pointless=True,
366
rev_id='A', working_tree = wt)
502
conf = config.MemoryStack(b'post_commit=breezy.ahook breezy.ahook')
503
commit.Commit(config_stack=conf).commit(
504
message="base", allow_pointless=True, rev_id=b'A',
367
506
self.assertEqual(['called', 'called'], calls)
371
510
def test_commit_object_doesnt_set_nick(self):
372
511
# using the Commit object directly does not set the branch nick.
373
512
wt = self.make_branch_and_tree('.')
375
514
c.commit(working_tree=wt, message='empty tree', allow_pointless=True)
376
self.assertEquals(wt.branch.revno(), 1)
515
self.assertEqual(wt.branch.revno(), 1)
377
516
self.assertEqual({},
378
517
wt.branch.repository.get_revision(
379
wt.branch.last_revision()).properties)
518
wt.branch.last_revision()).properties)
381
520
def test_safe_master_lock(self):
382
521
os.mkdir('master')
392
531
self.assertRaises(LockContention, wt.commit, 'silly')
394
533
master_branch.unlock()
535
def test_commit_bound_merge(self):
536
# see bug #43959; commit of a merge in a bound branch fails to push
537
# the new commit into the master
538
master_branch = self.make_branch('master')
539
bound_tree = self.make_branch_and_tree('bound')
540
bound_tree.branch.bind(master_branch)
542
self.build_tree_contents(
543
[('bound/content_file', b'initial contents\n')])
544
bound_tree.add(['content_file'])
545
bound_tree.commit(message='woo!')
547
other_bzrdir = master_branch.controldir.sprout('other')
548
other_tree = other_bzrdir.open_workingtree()
550
# do a commit to the other branch changing the content file so
551
# that our commit after merging will have a merged revision in the
552
# content file history.
553
self.build_tree_contents(
554
[('other/content_file', b'change in other\n')])
555
other_tree.commit('change in other')
557
# do a merge into the bound branch from other, and then change the
558
# content file locally to force a new revision (rather than using the
559
# revision from other). This forces extra processing in commit.
560
bound_tree.merge_from_branch(other_tree.branch)
561
self.build_tree_contents(
562
[('bound/content_file', b'change in bound\n')])
564
# before #34959 was fixed, this failed with 'revision not present in
565
# weave' when trying to implicitly push from the bound branch to the master
566
bound_tree.commit(message='commit of merge in bound tree')
568
def test_commit_reporting_after_merge(self):
569
# when doing a commit of a merge, the reporter needs to still
570
# be called for each item that is added/removed/deleted.
571
this_tree = self.make_branch_and_tree('this')
572
# we need a bunch of files and dirs, to perform one action on each.
575
'this/dirtoreparent/',
578
'this/filetoreparent',
595
this_tree.commit('create_files')
596
other_dir = this_tree.controldir.sprout('other')
597
other_tree = other_dir.open_workingtree()
598
other_tree.lock_write()
599
# perform the needed actions on the files and dirs.
601
other_tree.rename_one('dirtorename', 'renameddir')
602
other_tree.rename_one('dirtoreparent', 'renameddir/reparenteddir')
603
other_tree.rename_one('filetorename', 'renamedfile')
604
other_tree.rename_one(
605
'filetoreparent', 'renameddir/reparentedfile')
606
other_tree.remove(['dirtoremove', 'filetoremove'])
607
self.build_tree_contents([
609
('other/filetomodify', b'new content'),
610
('other/newfile', b'new file content')])
611
other_tree.add('newfile')
612
other_tree.add('newdir/')
613
other_tree.commit('modify all sample files and dirs.')
616
this_tree.merge_from_branch(other_tree.branch)
617
reporter = CapturingReporter()
618
this_tree.commit('do the commit', reporter=reporter)
620
('change', 'modified', 'filetomodify'),
621
('change', 'added', 'newdir'),
622
('change', 'added', 'newfile'),
623
('renamed', 'renamed', 'dirtorename', 'renameddir'),
624
('renamed', 'renamed', 'filetorename', 'renamedfile'),
625
('renamed', 'renamed', 'dirtoreparent', 'renameddir/reparenteddir'),
626
('renamed', 'renamed', 'filetoreparent', 'renameddir/reparentedfile'),
627
('deleted', 'dirtoremove'),
628
('deleted', 'filetoremove'),
630
result = set(reporter.calls)
631
missing = expected - result
632
new = result - expected
633
self.assertEqual((set(), set()), (missing, new))
635
def test_commit_removals_respects_filespec(self):
636
"""Commit respects the specified_files for removals."""
637
tree = self.make_branch_and_tree('.')
638
self.build_tree(['a', 'b'])
640
tree.commit('added a, b')
641
tree.remove(['a', 'b'])
642
tree.commit('removed a', specific_files='a')
643
basis = tree.basis_tree()
644
with tree.lock_read():
645
self.assertFalse(basis.is_versioned('a'))
646
self.assertTrue(basis.is_versioned('b'))
648
def test_commit_saves_1ms_timestamp(self):
649
"""Passing in a timestamp is saved with 1ms resolution"""
650
tree = self.make_branch_and_tree('.')
651
self.build_tree(['a'])
653
tree.commit('added a', timestamp=1153248633.4186721, timezone=0,
656
rev = tree.branch.repository.get_revision(b'a1')
657
self.assertEqual(1153248633.419, rev.timestamp)
659
def test_commit_has_1ms_resolution(self):
660
"""Allowing commit to generate the timestamp also has 1ms resolution"""
661
tree = self.make_branch_and_tree('.')
662
self.build_tree(['a'])
664
tree.commit('added a', rev_id=b'a1')
666
rev = tree.branch.repository.get_revision(b'a1')
667
timestamp = rev.timestamp
668
timestamp_1ms = round(timestamp, 3)
669
self.assertEqual(timestamp_1ms, timestamp)
671
def assertBasisTreeKind(self, kind, tree, path):
672
basis = tree.basis_tree()
675
self.assertEqual(kind, basis.kind(path))
679
def test_commit_kind_changes(self):
680
self.requireFeature(SymlinkFeature)
681
tree = self.make_branch_and_tree('.')
682
os.symlink('target', 'name')
683
tree.add('name', b'a-file-id')
684
tree.commit('Added a symlink')
685
self.assertBasisTreeKind('symlink', tree, 'name')
688
self.build_tree(['name'])
689
tree.commit('Changed symlink to file')
690
self.assertBasisTreeKind('file', tree, 'name')
693
os.symlink('target', 'name')
694
tree.commit('file to symlink')
695
self.assertBasisTreeKind('symlink', tree, 'name')
699
tree.commit('symlink to directory')
700
self.assertBasisTreeKind('directory', tree, 'name')
703
os.symlink('target', 'name')
704
tree.commit('directory to symlink')
705
self.assertBasisTreeKind('symlink', tree, 'name')
707
# prepare for directory <-> file tests
710
tree.commit('symlink to directory')
711
self.assertBasisTreeKind('directory', tree, 'name')
714
self.build_tree(['name'])
715
tree.commit('Changed directory to file')
716
self.assertBasisTreeKind('file', tree, 'name')
720
tree.commit('file to directory')
721
self.assertBasisTreeKind('directory', tree, 'name')
723
def test_commit_unversioned_specified(self):
724
"""Commit should raise if specified files isn't in basis or worktree"""
725
tree = self.make_branch_and_tree('.')
726
self.assertRaises(errors.PathsNotVersionedError, tree.commit,
727
'message', specific_files=['bogus'])
729
class Callback(object):
731
def __init__(self, message, testcase):
733
self.message = message
734
self.testcase = testcase
736
def __call__(self, commit_obj):
738
self.testcase.assertTrue(isinstance(commit_obj, Commit))
741
def test_commit_callback(self):
742
"""Commit should invoke a callback to get the message"""
744
tree = self.make_branch_and_tree('.')
747
except Exception as e:
748
self.assertTrue(isinstance(e, BzrError))
749
self.assertEqual('The message or message_callback keyword'
750
' parameter is required for commit().', str(e))
752
self.fail('exception not raised')
753
cb = self.Callback(u'commit 1', self)
754
tree.commit(message_callback=cb)
755
self.assertTrue(cb.called)
756
repository = tree.branch.repository
757
message = repository.get_revision(tree.last_revision()).message
758
self.assertEqual('commit 1', message)
760
def test_no_callback_pointless(self):
761
"""Callback should not be invoked for pointless commit"""
762
tree = self.make_branch_and_tree('.')
763
cb = self.Callback(u'commit 2', self)
764
self.assertRaises(PointlessCommit, tree.commit, message_callback=cb,
765
allow_pointless=False)
766
self.assertFalse(cb.called)
768
def test_no_callback_netfailure(self):
769
"""Callback should not be invoked if connectivity fails"""
770
tree = self.make_branch_and_tree('.')
771
cb = self.Callback(u'commit 2', self)
772
repository = tree.branch.repository
773
# simulate network failure
775
def raise_(self, arg, arg2, arg3=None, arg4=None):
776
raise errors.NoSuchFile('foo')
777
repository.add_inventory = raise_
778
repository.add_inventory_by_delta = raise_
779
self.assertRaises(errors.NoSuchFile, tree.commit, message_callback=cb)
780
self.assertFalse(cb.called)
782
def test_selected_file_merge_commit(self):
783
"""Ensure the correct error is raised"""
784
tree = self.make_branch_and_tree('foo')
785
# pending merge would turn into a left parent
786
tree.commit('commit 1')
787
tree.add_parent_tree_id(b'example')
788
self.build_tree(['foo/bar', 'foo/baz'])
789
tree.add(['bar', 'baz'])
790
err = self.assertRaises(CannotCommitSelectedFileMerge,
791
tree.commit, 'commit 2', specific_files=['bar', 'baz'])
792
self.assertEqual(['bar', 'baz'], err.files)
793
self.assertEqual('Selected-file commit of merges is not supported'
794
' yet: files bar, baz', str(err))
796
def test_commit_ordering(self):
797
"""Test of corner-case commit ordering error"""
798
tree = self.make_branch_and_tree('.')
799
self.build_tree(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
800
tree.add(['a/', 'a/z/', 'a/c/', 'a/z/x', 'a/z/y'])
802
self.build_tree(['a/c/d/'])
804
tree.rename_one('a/z/x', 'a/c/d/x')
805
tree.commit('test', specific_files=['a/z/y'])
807
def test_commit_no_author(self):
808
"""The default kwarg author in MutableTree.commit should not add
809
the 'author' revision property.
811
tree = self.make_branch_and_tree('foo')
812
rev_id = tree.commit('commit 1')
813
rev = tree.branch.repository.get_revision(rev_id)
814
self.assertFalse('author' in rev.properties)
815
self.assertFalse('authors' in rev.properties)
817
def test_commit_author(self):
818
"""Passing a non-empty authors kwarg to MutableTree.commit should add
819
the 'author' revision property.
821
tree = self.make_branch_and_tree('foo')
822
rev_id = tree.commit(
824
authors=['John Doe <jdoe@example.com>'])
825
rev = tree.branch.repository.get_revision(rev_id)
826
self.assertEqual('John Doe <jdoe@example.com>',
827
rev.properties['authors'])
828
self.assertFalse('author' in rev.properties)
830
def test_commit_empty_authors_list(self):
831
"""Passing an empty list to authors shouldn't add the property."""
832
tree = self.make_branch_and_tree('foo')
833
rev_id = tree.commit('commit 1', authors=[])
834
rev = tree.branch.repository.get_revision(rev_id)
835
self.assertFalse('author' in rev.properties)
836
self.assertFalse('authors' in rev.properties)
838
def test_multiple_authors(self):
839
tree = self.make_branch_and_tree('foo')
840
rev_id = tree.commit('commit 1',
841
authors=['John Doe <jdoe@example.com>',
842
'Jane Rey <jrey@example.com>'])
843
rev = tree.branch.repository.get_revision(rev_id)
844
self.assertEqual('John Doe <jdoe@example.com>\n'
845
'Jane Rey <jrey@example.com>', rev.properties['authors'])
846
self.assertFalse('author' in rev.properties)
848
def test_author_with_newline_rejected(self):
849
tree = self.make_branch_and_tree('foo')
850
self.assertRaises(AssertionError, tree.commit, 'commit 1',
851
authors=['John\nDoe <jdoe@example.com>'])
853
def test_commit_with_checkout_and_branch_sharing_repo(self):
854
repo = self.make_repository('repo', shared=True)
855
# make_branch_and_tree ignores shared repos
856
branch = controldir.ControlDir.create_branch_convenience('repo/branch')
857
tree2 = branch.create_checkout('repo/tree2')
858
tree2.commit('message', rev_id=b'rev1')
859
self.assertTrue(tree2.branch.repository.has_revision(b'rev1'))
862
class FilterExcludedTests(TestCase):
864
def test_add_file_not_excluded(self):
866
('fid', (None, 'newpath'),
867
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
868
('file', 'file'), (True, True))]
869
self.assertEqual(changes, list(
870
filter_excluded(changes, ['otherpath'])))
872
def test_add_file_excluded(self):
874
('fid', (None, 'newpath'),
875
0, (False, False), ('pid', 'pid'), ('newpath', 'newpath'),
876
('file', 'file'), (True, True))]
877
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))
879
def test_delete_file_excluded(self):
881
('fid', ('somepath', None),
882
0, (False, None), ('pid', None), ('newpath', None),
883
('file', None), (True, None))]
884
self.assertEqual([], list(filter_excluded(changes, ['somepath'])))
886
def test_move_from_or_to_excluded(self):
888
('fid', ('oldpath', 'newpath'),
889
0, (False, False), ('pid', 'pid'), ('oldpath', 'newpath'),
890
('file', 'file'), (True, True))]
891
self.assertEqual([], list(filter_excluded(changes, ['oldpath'])))
892
self.assertEqual([], list(filter_excluded(changes, ['newpath'])))