75
86
pb2.update('foo', 0, 1)
78
self.assertEqual([("update", 0, 1)], factory._calls)
89
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)
81
92
class TestCommit(TestCaseWithWorkingTree):
94
def test_autodelete_renamed(self):
95
tree_a = self.make_branch_and_tree('a')
96
self.build_tree(['a/dir/', 'a/dir/f1', 'a/dir/f2'])
97
tree_a.add(['dir', 'dir/f1', 'dir/f2'], ['dir-id', 'f1-id', 'f2-id'])
98
rev_id1 = tree_a.commit('init')
99
# Start off by renaming entries,
100
# but then actually auto delete the whole tree
101
# https://bugs.launchpad.net/bzr/+bug/114615
102
tree_a.rename_one('dir/f1', 'dir/a')
103
tree_a.rename_one('dir/f2', 'dir/z')
104
osutils.rmtree('a/dir')
105
tree_a.commit('autoremoved')
109
root_id = tree_a.get_root_id()
110
paths = [(path, ie.file_id)
111
for path, ie in tree_a.iter_entries_by_dir()]
114
# The only paths left should be the root
115
self.assertEqual([('', root_id)], paths)
117
def test_no_autodelete_renamed_away(self):
118
tree_a = self.make_branch_and_tree('a')
119
self.build_tree(['a/dir/', 'a/dir/f1', 'a/dir/f2', 'a/dir2/'])
120
tree_a.add(['dir', 'dir/f1', 'dir/f2', 'dir2'],
121
['dir-id', 'f1-id', 'f2-id', 'dir2-id'])
122
rev_id1 = tree_a.commit('init')
123
# Rename one entry out of this directory
124
tree_a.rename_one('dir/f1', 'dir2/a')
125
osutils.rmtree('a/dir')
126
tree_a.commit('autoremoved')
130
root_id = tree_a.get_root_id()
131
paths = [(path, ie.file_id)
132
for path, ie in tree_a.iter_entries_by_dir()]
135
# The only paths left should be the root
136
self.assertEqual([('', root_id), ('dir2', 'dir2-id'),
140
def test_no_autodelete_alternate_renamed(self):
141
# Test for bug #114615
142
tree_a = self.make_branch_and_tree('A')
143
self.build_tree(['A/a/', 'A/a/m', 'A/a/n'])
144
tree_a.add(['a', 'a/m', 'a/n'], ['a-id', 'm-id', 'n-id'])
145
tree_a.commit('init')
149
root_id = tree_a.get_root_id()
153
tree_b = tree_a.bzrdir.sprout('B').open_workingtree()
154
self.build_tree(['B/xyz/'])
155
tree_b.add(['xyz'], ['xyz-id'])
156
tree_b.rename_one('a/m', 'xyz/m')
157
osutils.rmtree('B/a')
158
tree_b.commit('delete in B')
160
paths = [(path, ie.file_id)
161
for path, ie in tree_b.iter_entries_by_dir()]
162
self.assertEqual([('', root_id),
167
self.build_tree_contents([('A/a/n', 'new contents for n\n')])
168
tree_a.commit('change n in A')
170
# Merging from A should introduce conflicts because 'n' was modified
171
# and removed, so 'a' needs to be restored.
172
num_conflicts = tree_b.merge_from_branch(tree_a.branch)
173
self.assertEqual(3, num_conflicts)
174
paths = [(path, ie.file_id)
175
for path, ie in tree_b.iter_entries_by_dir()]
176
self.assertEqual([('', root_id),
179
('a/n.OTHER', 'n-id'),
182
osutils.rmtree('B/a')
185
tree_b.set_conflicts(conflicts.ConflictList())
186
except errors.UnsupportedOperation:
187
# On WT2, set_conflicts is unsupported, but the rmtree has the same
190
tree_b.commit('autoremove a, without touching xyz/m')
191
paths = [(path, ie.file_id)
192
for path, ie in tree_b.iter_entries_by_dir()]
193
self.assertEqual([('', root_id),
198
def test_commit_exclude_pending_merge_fails(self):
199
"""Excludes are a form of partial commit."""
200
wt = self.make_branch_and_tree('.')
201
self.build_tree(['foo'])
203
wt.commit('commit one')
204
wt2 = wt.bzrdir.sprout('to').open_workingtree()
205
wt2.commit('change_right')
206
wt.merge_from_branch(wt2.branch)
207
self.assertRaises(errors.CannotCommitSelectedFileMerge,
208
wt.commit, 'test', exclude=['foo'])
210
def test_commit_exclude_exclude_changed_is_pointless(self):
211
tree = self.make_branch_and_tree('.')
212
self.build_tree(['a'])
213
tree.smart_add(['.'])
214
tree.commit('setup test')
215
self.build_tree_contents([('a', 'new contents for "a"\n')])
216
self.assertRaises(errors.PointlessCommit, tree.commit, 'test',
217
exclude=['a'], allow_pointless=False)
219
def test_commit_exclude_excludes_modified_files(self):
220
tree = self.make_branch_and_tree('.')
221
self.build_tree(['a', 'b', 'c'])
222
tree.smart_add(['.'])
223
tree.commit('test', exclude=['b', 'c'])
224
# If b was excluded it will still be 'added' in status.
226
self.addCleanup(tree.unlock)
227
changes = list(tree.iter_changes(tree.basis_tree()))
228
self.assertEqual(2, len(changes))
229
self.assertEqual((None, 'b'), changes[0][1])
230
self.assertEqual((None, 'c'), changes[1][1])
232
def test_commit_exclude_subtree_of_selected(self):
233
tree = self.make_branch_and_tree('.')
234
self.build_tree(['a/', 'a/b'])
235
tree.smart_add(['.'])
236
tree.commit('test', specific_files=['a'], exclude=['a/b'])
237
# If a/b was excluded it will still be 'added' in status.
239
self.addCleanup(tree.unlock)
240
changes = list(tree.iter_changes(tree.basis_tree()))
241
self.assertEqual(1, len(changes))
242
self.assertEqual((None, 'a/b'), changes[0][1])
83
244
def test_commit_sets_last_revision(self):
84
245
tree = self.make_branch_and_tree('tree')
85
committed_id = tree.commit('foo', rev_id='foo', allow_pointless=True)
246
committed_id = tree.commit('foo', rev_id='foo')
86
247
self.assertEqual(['foo'], tree.get_parent_ids())
87
248
# the commit should have returned the same id we asked for.
88
249
self.assertEqual('foo', committed_id)
90
251
def test_commit_returns_revision_id(self):
91
252
tree = self.make_branch_and_tree('.')
92
committed_id = tree.commit('message', allow_pointless=True)
253
committed_id = tree.commit('message')
93
254
self.assertTrue(tree.branch.repository.has_revision(committed_id))
94
255
self.assertNotEqual(None, committed_id)
198
377
self.assertFalse(wt.has_filename('b/c'))
199
378
self.assertFalse(wt.has_filename('d'))
381
def test_commit_deleted_subtree_with_removed(self):
382
wt = self.make_branch_and_tree('.')
383
self.build_tree(['a', 'b/', 'b/c', 'd'])
384
wt.add(['a', 'b', 'b/c'], ['a-id', 'b-id', 'c-id'])
387
this_dir = self.get_transport()
388
this_dir.delete_tree('b')
390
wt.commit('commit deleted rename')
391
self.assertTrue(wt.has_id('a-id'))
392
self.assertFalse(wt.has_or_had_id('b-id'))
393
self.assertFalse(wt.has_or_had_id('c-id'))
394
self.assertTrue(wt.has_filename('a'))
395
self.assertFalse(wt.has_filename('b'))
396
self.assertFalse(wt.has_filename('b/c'))
399
def test_commit_move_new(self):
400
wt = self.make_branch_and_tree('first')
402
wt2 = wt.bzrdir.sprout('second').open_workingtree()
403
self.build_tree(['second/name1'])
404
wt2.add('name1', 'name1-id')
406
wt.merge_from_branch(wt2.branch)
407
wt.rename_one('name1', 'name2')
409
wt.path2id('name1-id')
411
def test_nested_commit(self):
412
"""Commit in multiply-nested trees"""
413
tree = self.make_branch_and_tree('.')
414
if not tree.supports_tree_reference():
417
subtree = self.make_branch_and_tree('subtree')
418
subsubtree = self.make_branch_and_tree('subtree/subtree')
419
subtree.add(['subtree'])
420
tree.add(['subtree'])
421
# use allow_pointless=False to ensure that the deepest tree, which
422
# has no commits made to it, does not get a pointless commit.
423
rev_id = tree.commit('added reference', allow_pointless=False)
425
self.addCleanup(tree.unlock)
426
# the deepest subtree has not changed, so no commit should take place.
427
self.assertEqual('null:', subsubtree.last_revision())
428
# the intermediate tree should have committed a pointer to the current
430
sub_basis = subtree.basis_tree()
431
sub_basis.lock_read()
432
self.addCleanup(sub_basis.unlock)
433
self.assertEqual(subsubtree.last_revision(),
434
sub_basis.get_reference_revision(sub_basis.path2id('subtree')))
435
# the intermediate tree has changed, so should have had a commit
437
self.assertNotEqual(None, subtree.last_revision())
438
# the outer tree should have committed a pointer to the current
440
basis = tree.basis_tree()
442
self.addCleanup(basis.unlock)
443
self.assertEqual(subtree.last_revision(),
444
basis.get_reference_revision(basis.path2id('subtree')))
445
# the outer tree must have have changed too.
446
self.assertNotEqual(None, rev_id)
448
def test_nested_commit_second_commit_detects_changes(self):
449
"""Commit with a nested tree picks up the correct child revid."""
450
tree = self.make_branch_and_tree('.')
451
if not tree.supports_tree_reference():
454
subtree = self.make_branch_and_tree('subtree')
455
tree.add(['subtree'])
456
self.build_tree(['subtree/file'])
457
subtree.add(['file'], ['file-id'])
458
rev_id = tree.commit('added reference', allow_pointless=False)
459
child_revid = subtree.last_revision()
460
# now change the child tree
461
self.build_tree_contents([('subtree/file', 'new-content')])
462
# and commit in the parent should commit the child and grab its revid,
463
# we test with allow_pointless=False here so that we are simulating
464
# what users will see.
465
rev_id2 = tree.commit('changed subtree only', allow_pointless=False)
466
# the child tree has changed, so should have had a commit
468
self.assertNotEqual(None, subtree.last_revision())
469
self.assertNotEqual(child_revid, subtree.last_revision())
470
# the outer tree should have committed a pointer to the current
472
basis = tree.basis_tree()
474
self.addCleanup(basis.unlock)
475
self.assertEqual(subtree.last_revision(),
476
basis.get_reference_revision(basis.path2id('subtree')))
477
self.assertNotEqual(rev_id, rev_id2)
479
def test_nested_pointless_commits_are_pointless(self):
480
tree = self.make_branch_and_tree('.')
481
if not tree.supports_tree_reference():
484
subtree = self.make_branch_and_tree('subtree')
485
tree.add(['subtree'])
486
# record the reference.
487
rev_id = tree.commit('added reference')
488
child_revid = subtree.last_revision()
489
# now do a no-op commit with allow_pointless=False
490
self.assertRaises(errors.PointlessCommit, tree.commit, '',
491
allow_pointless=False)
492
self.assertEqual(child_revid, subtree.last_revision())
493
self.assertEqual(rev_id, tree.last_revision())
203
496
class TestCommitProgress(TestCaseWithWorkingTree):
230
523
# into the factory for this test - just make the test ui factory
231
524
# pun as a reporter. Then we can check the ordering is right.
232
525
tree.commit('second post', specific_files=['b'])
233
# 9 steps: 1 for rev, 2 for inventory, 1 for finishing. 2 for root
234
# and 6 for inventory files.
235
# 2 steps don't trigger an update, as 'a' and 'c' are not
526
# 5 steps, the first of which is reported 2 times, once per dir
528
[('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
529
('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
530
('update', 2, 5, 'Saving data locally - Stage'),
531
('update', 3, 5, 'Running pre_commit hooks - Stage'),
532
('update', 4, 5, 'Updating the working tree - Stage'),
533
('update', 5, 5, 'Running post_commit hooks - Stage')],
537
def test_commit_progress_shows_post_hook_names(self):
538
tree = self.make_branch_and_tree('.')
539
# set a progress bar that captures the calls so we can see what is
541
self.old_ui_factory = ui.ui_factory
542
self.addCleanup(self.restoreDefaults)
543
factory = CapturingUIFactory()
544
ui.ui_factory = factory
545
def a_hook(_, _2, _3, _4, _5, _6):
547
branch.Branch.hooks.install_named_hook('post_commit', a_hook,
549
tree.commit('first post')
551
[('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
552
('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
553
('update', 2, 5, 'Saving data locally - Stage'),
554
('update', 3, 5, 'Running pre_commit hooks - Stage'),
555
('update', 4, 5, 'Updating the working tree - Stage'),
556
('update', 5, 5, 'Running post_commit hooks - Stage'),
557
('update', 5, 5, 'Running post_commit hooks [hook name] - Stage'),
562
def test_commit_progress_shows_pre_hook_names(self):
563
tree = self.make_branch_and_tree('.')
564
# set a progress bar that captures the calls so we can see what is
566
self.old_ui_factory = ui.ui_factory
567
self.addCleanup(self.restoreDefaults)
568
factory = CapturingUIFactory()
569
ui.ui_factory = factory
570
def a_hook(_, _2, _3, _4, _5, _6, _7, _8):
572
branch.Branch.hooks.install_named_hook('pre_commit', a_hook,
574
tree.commit('first post')
576
[('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
577
('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
578
('update', 2, 5, 'Saving data locally - Stage'),
579
('update', 3, 5, 'Running pre_commit hooks - Stage'),
580
('update', 3, 5, 'Running pre_commit hooks [hook name] - Stage'),
581
('update', 4, 5, 'Updating the working tree - Stage'),
582
('update', 5, 5, 'Running post_commit hooks - Stage'),
587
def test_start_commit_hook(self):
588
"""Make sure a start commit hook can modify the tree that is
590
def start_commit_hook_adds_file(tree):
591
open(tree.abspath("newfile"), 'w').write("data")
592
tree.add(["newfile"])
593
def restoreDefaults():
594
mutabletree.MutableTree.hooks['start_commit'] = []
595
self.addCleanup(restoreDefaults)
596
tree = self.make_branch_and_tree('.')
597
mutabletree.MutableTree.hooks.install_named_hook(
599
start_commit_hook_adds_file,
601
revid = tree.commit('first post')
602
committed_tree = tree.basis_tree()
603
self.assertTrue(committed_tree.has_filename("newfile"))