14
14
# You should have received a copy of the GNU General Public License
15
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from cStringIO import StringIO
20
21
from bzrlib import (
27
28
revision as _mod_revision,
29
transport as _mod_transport,
32
from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
33
from bzrlib.tests.testui import ProgressRecordingUIFactory
33
from bzrlib.errors import (NotBranchError, NotVersionedError,
35
from bzrlib.osutils import pathjoin, getcwd
36
from bzrlib.tests import TestCase
37
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
38
from bzrlib.trace import mutter
39
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
43
class CapturingUIFactory(ui.UIFactory):
44
"""A UI Factory for testing - capture the updates made through it."""
47
super(CapturingUIFactory, self).__init__()
52
"""See progress.ProgressBar.clear()."""
55
"""See progress.ProgressBar.clear_term()."""
58
"""See progress.ProgressBar.finished()."""
61
def note(self, fmt_string, *args, **kwargs):
62
"""See progress.ProgressBar.note()."""
64
def progress_bar(self):
67
def nested_progress_bar(self):
71
def update(self, message, count=None, total=None):
72
"""See progress.ProgressBar.update()."""
74
self._calls.append(("update", count, total, message))
77
class TestCapturingUI(TestCase):
79
def test_nested_ignore_depth_beyond_one(self):
80
# we only want to capture the first level out progress, not
81
# want sub-components might do. So we have nested bars ignored.
82
factory = CapturingUIFactory()
83
pb1 = factory.nested_progress_bar()
84
pb1.update('foo', 0, 1)
85
pb2 = factory.nested_progress_bar()
86
pb2.update('foo', 0, 1)
89
self.assertEqual([("update", 0, 1, 'foo')], factory._calls)
36
92
class TestCommit(TestCaseWithWorkingTree):
112
168
tree_a.commit('change n in A')
114
170
# Merging from A should introduce conflicts because 'n' was modified
115
# (in A) and removed (in B), so 'a' needs to be restored.
171
# and removed, so 'a' needs to be restored.
116
172
num_conflicts = tree_b.merge_from_branch(tree_a.branch)
117
173
self.assertEqual(3, num_conflicts)
118
174
paths = [(path, ie.file_id)
148
204
wt2 = wt.bzrdir.sprout('to').open_workingtree()
149
205
wt2.commit('change_right')
150
206
wt.merge_from_branch(wt2.branch)
152
self.assertRaises(errors.CannotCommitSelectedFileMerge,
153
wt.commit, 'test', exclude=['foo'])
154
except errors.ExcludesUnsupported:
155
raise tests.TestNotApplicable("excludes not supported by this "
207
self.assertRaises(errors.CannotCommitSelectedFileMerge,
208
wt.commit, 'test', exclude=['foo'])
158
210
def test_commit_exclude_exclude_changed_is_pointless(self):
159
211
tree = self.make_branch_and_tree('.')
161
213
tree.smart_add(['.'])
162
214
tree.commit('setup test')
163
215
self.build_tree_contents([('a', 'new contents for "a"\n')])
165
self.assertRaises(errors.PointlessCommit, tree.commit, 'test',
166
exclude=['a'], allow_pointless=False)
167
except errors.ExcludesUnsupported:
168
raise tests.TestNotApplicable("excludes not supported by this "
216
self.assertRaises(errors.PointlessCommit, tree.commit, 'test',
217
exclude=['a'], allow_pointless=False)
171
219
def test_commit_exclude_excludes_modified_files(self):
172
220
tree = self.make_branch_and_tree('.')
173
221
self.build_tree(['a', 'b', 'c'])
174
222
tree.smart_add(['.'])
176
tree.commit('test', exclude=['b', 'c'])
177
except errors.ExcludesUnsupported:
178
raise tests.TestNotApplicable("excludes not supported by this "
223
tree.commit('test', exclude=['b', 'c'])
180
224
# If b was excluded it will still be 'added' in status.
182
226
self.addCleanup(tree.unlock)
189
233
tree = self.make_branch_and_tree('.')
190
234
self.build_tree(['a/', 'a/b'])
191
235
tree.smart_add(['.'])
193
tree.commit('test', specific_files=['a'], exclude=['a/b'])
194
except errors.ExcludesUnsupported:
195
raise tests.TestNotApplicable("excludes not supported by this "
236
tree.commit('test', specific_files=['a'], exclude=['a/b'])
197
237
# If a/b was excluded it will still be 'added' in status.
199
239
self.addCleanup(tree.unlock)
215
255
self.assertNotEqual(None, committed_id)
217
257
def test_commit_local_unbound(self):
218
# using the library api to do a local commit on unbound branches is
258
# using the library api to do a local commit on unbound branches is
220
260
tree = self.make_branch_and_tree('tree')
221
261
self.assertRaises(errors.LocalRequiresBoundBranch,
240
280
wt2.merge_from_branch(wt.branch)
241
281
wt2.commit('merged kind change')
243
def test_commit_aborted_does_not_apply_automatic_changes_bug_282402(self):
244
wt = self.make_branch_and_tree('.')
245
wt.add(['a'], ['a-id'], ['file'])
246
def fail_message(obj):
247
raise errors.BzrCommandError("empty commit message")
248
self.assertRaises(errors.BzrCommandError, wt.commit,
249
message_callback=fail_message)
250
self.assertEqual('a', wt.id2path('a-id'))
252
283
def test_local_commit_ignores_master(self):
253
284
# a --local commit does not require access to the master branch
254
285
# at all, or even for it to exist.
266
297
# check its corrupted.
267
298
self.assertRaises(errors.UnknownFormatError,
268
controldir.ControlDir.open,
270
301
tree.commit('foo', rev_id='foo', local=True)
272
303
def test_local_commit_does_not_push_to_master(self):
273
304
# a --local commit does not require access to the master branch
274
305
# at all, or even for it to exist.
318
349
self.build_tree(['a', 'b/', 'b/c', 'd'])
319
350
wt.add(['a', 'b', 'b/c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
320
this_dir = wt.bzrdir.root_transport
351
this_dir = self.get_transport()
321
352
this_dir.delete_tree('b')
322
353
this_dir.delete('d')
323
354
# now we have a tree with a through d in the inventory, but only
425
456
self.build_tree(['subtree/file'])
426
457
subtree.add(['file'], ['file-id'])
427
458
rev_id = tree.commit('added reference', allow_pointless=False)
428
tree.get_reference_revision(tree.path2id('subtree'))
429
459
child_revid = subtree.last_revision()
430
460
# now change the child tree
431
461
self.build_tree_contents([('subtree/file', 'new-content')])
466
496
class TestCommitProgress(TestCaseWithWorkingTree):
469
super(TestCommitProgress, self).setUp()
470
ui.ui_factory = ProgressRecordingUIFactory()
498
def restoreDefaults(self):
499
ui.ui_factory = self.old_ui_factory
472
501
def test_commit_progress_steps(self):
473
# during commit we one progress update for every entry in the
502
# during commit we one progress update for every entry in the
474
503
# inventory, and then one for the inventory, and one for the
475
504
# inventory, and one for the revision insertions.
476
# first we need a test commit to do. Lets setup a branch with
505
# first we need a test commit to do. Lets setup a branch with
477
506
# 3 files, and alter one in a selected-file commit. This exercises
478
# a number of cases quickly. We should also test things like
507
# a number of cases quickly. We should also test things like
479
508
# selective commits which excludes newly added files.
480
509
tree = self.make_branch_and_tree('.')
481
510
self.build_tree(['a', 'b', 'c'])
484
513
f = file('b', 'wt')
485
514
f.write('new content')
487
# set a progress bar that captures the calls so we can see what is
516
# set a progress bar that captures the calls so we can see what is
489
factory = ProgressRecordingUIFactory()
518
self.old_ui_factory = ui.ui_factory
519
self.addCleanup(self.restoreDefaults)
520
factory = CapturingUIFactory()
490
521
ui.ui_factory = factory
491
522
# TODO RBC 20060421 it would be nice to merge the reporter output
492
523
# into the factory for this test - just make the test ui factory
494
525
tree.commit('second post', specific_files=['b'])
495
526
# 5 steps, the first of which is reported 2 times, once per dir
496
527
self.assertEqual(
497
[('update', 1, 5, 'Collecting changes [0] - Stage'),
498
('update', 1, 5, 'Collecting changes [1] - Stage'),
528
[('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
529
('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
499
530
('update', 2, 5, 'Saving data locally - Stage'),
500
531
('update', 3, 5, 'Running pre_commit hooks - Stage'),
501
532
('update', 4, 5, 'Updating the working tree - Stage'),
506
537
def test_commit_progress_shows_post_hook_names(self):
507
538
tree = self.make_branch_and_tree('.')
508
# set a progress bar that captures the calls so we can see what is
539
# set a progress bar that captures the calls so we can see what is
510
factory = ProgressRecordingUIFactory()
541
self.old_ui_factory = ui.ui_factory
542
self.addCleanup(self.restoreDefaults)
543
factory = CapturingUIFactory()
511
544
ui.ui_factory = factory
512
545
def a_hook(_, _2, _3, _4, _5, _6):
516
549
tree.commit('first post')
517
550
self.assertEqual(
518
[('update', 1, 5, 'Collecting changes [0] - Stage'),
519
('update', 1, 5, 'Collecting changes [1] - Stage'),
551
[('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
552
('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
520
553
('update', 2, 5, 'Saving data locally - Stage'),
521
554
('update', 3, 5, 'Running pre_commit hooks - Stage'),
522
555
('update', 4, 5, 'Updating the working tree - Stage'),
529
562
def test_commit_progress_shows_pre_hook_names(self):
530
563
tree = self.make_branch_and_tree('.')
531
# set a progress bar that captures the calls so we can see what is
564
# set a progress bar that captures the calls so we can see what is
533
factory = ProgressRecordingUIFactory()
566
self.old_ui_factory = ui.ui_factory
567
self.addCleanup(self.restoreDefaults)
568
factory = CapturingUIFactory()
534
569
ui.ui_factory = factory
535
570
def a_hook(_, _2, _3, _4, _5, _6, _7, _8):
539
574
tree.commit('first post')
540
575
self.assertEqual(
541
[('update', 1, 5, 'Collecting changes [0] - Stage'),
542
('update', 1, 5, 'Collecting changes [1] - Stage'),
576
[('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
577
('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
543
578
('update', 2, 5, 'Saving data locally - Stage'),
544
579
('update', 3, 5, 'Running pre_commit hooks - Stage'),
545
580
('update', 3, 5, 'Running pre_commit hooks [hook name] - Stage'),
552
587
def test_start_commit_hook(self):
553
"""Make sure a start commit hook can modify the tree that is
588
"""Make sure a start commit hook can modify the tree that is
555
590
def start_commit_hook_adds_file(tree):
556
with open(tree.abspath("newfile"), 'w') as f: f.write("data")
591
open(tree.abspath("newfile"), 'w').write("data")
557
592
tree.add(["newfile"])
558
593
def restoreDefaults():
559
594
mutabletree.MutableTree.hooks['start_commit'] = []
566
601
revid = tree.commit('first post')
567
602
committed_tree = tree.basis_tree()
568
603
self.assertTrue(committed_tree.has_filename("newfile"))
570
def test_post_commit_hook(self):
571
"""Make sure a post_commit hook is called after a commit."""
572
def post_commit_hook_test_params(params):
573
self.assertTrue(isinstance(params,
574
mutabletree.PostCommitHookParams))
575
self.assertTrue(isinstance(params.mutable_tree,
576
mutabletree.MutableTree))
577
with open(tree.abspath("newfile"), 'w') as f: f.write("data")
578
params.mutable_tree.add(["newfile"])
579
tree = self.make_branch_and_tree('.')
580
mutabletree.MutableTree.hooks.install_named_hook(
582
post_commit_hook_test_params,
584
self.assertFalse(tree.has_filename("newfile"))
585
revid = tree.commit('first post')
586
self.assertTrue(tree.has_filename("newfile"))
587
committed_tree = tree.basis_tree()
588
self.assertFalse(committed_tree.has_filename("newfile"))