~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/workingtree_implementations/test_commit.py

  • Committer: John Arbash Meinel
  • Author(s): Mark Hammond
  • Date: 2008-09-09 17:02:21 UTC
  • mto: This revision was merged to the branch mainline in revision 3697.
  • Revision ID: john@arbash-meinel.com-20080909170221-svim3jw2mrz0amp3
An updated transparent icon for bzr.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2005, 2006 Canonical Ltd
2
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
3
#
4
4
# This program is free software; you can redistribute it and/or modify
13
13
#
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
17
17
 
 
18
from cStringIO import StringIO
18
19
import os
19
20
 
20
21
from bzrlib import (
21
22
    branch,
 
23
    bzrdir,
22
24
    conflicts,
23
 
    controldir,
24
25
    errors,
25
26
    mutabletree,
26
27
    osutils,
27
28
    revision as _mod_revision,
28
 
    tests,
29
 
    transport as _mod_transport,
30
29
    ui,
 
30
    uncommit,
 
31
    workingtree,
31
32
    )
32
 
from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
33
 
from bzrlib.tests.testui import ProgressRecordingUIFactory
 
33
from bzrlib.errors import (NotBranchError, NotVersionedError, 
 
34
                           UnsupportedOperation)
 
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,
 
40
                                WorkingTree)
 
41
 
 
42
 
 
43
class CapturingUIFactory(ui.UIFactory):
 
44
    """A UI Factory for testing - capture the updates made through it."""
 
45
 
 
46
    def __init__(self):
 
47
        super(CapturingUIFactory, self).__init__()
 
48
        self._calls = []
 
49
        self.depth = 0
 
50
 
 
51
    def clear(self):
 
52
        """See progress.ProgressBar.clear()."""
 
53
 
 
54
    def clear_term(self):
 
55
        """See progress.ProgressBar.clear_term()."""
 
56
 
 
57
    def finished(self):
 
58
        """See progress.ProgressBar.finished()."""
 
59
        self.depth -= 1
 
60
 
 
61
    def note(self, fmt_string, *args, **kwargs):
 
62
        """See progress.ProgressBar.note()."""
 
63
 
 
64
    def progress_bar(self):
 
65
        return self
 
66
    
 
67
    def nested_progress_bar(self):
 
68
        self.depth += 1
 
69
        return self
 
70
 
 
71
    def update(self, message, count=None, total=None):
 
72
        """See progress.ProgressBar.update()."""
 
73
        if self.depth == 1:
 
74
            self._calls.append(("update", count, total, message))
 
75
 
 
76
 
 
77
class TestCapturingUI(TestCase):
 
78
 
 
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)
 
87
        pb2.finished()
 
88
        pb1.finished()
 
89
        self.assertEqual([("update", 0, 1, 'foo')], factory._calls)
34
90
 
35
91
 
36
92
class TestCommit(TestCaseWithWorkingTree):
112
168
        tree_a.commit('change n in A')
113
169
 
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)
151
 
        try:
152
 
            self.assertRaises(errors.CannotCommitSelectedFileMerge,
153
 
                wt.commit, 'test', exclude=['foo'])
154
 
        except errors.ExcludesUnsupported:
155
 
            raise tests.TestNotApplicable("excludes not supported by this "
156
 
                "repository format")
 
207
        self.assertRaises(errors.CannotCommitSelectedFileMerge,
 
208
            wt.commit, 'test', exclude=['foo'])
157
209
 
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')])
164
 
        try:
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 "
169
 
                "repository format")
 
216
        self.assertRaises(errors.PointlessCommit, tree.commit, 'test',
 
217
            exclude=['a'], allow_pointless=False)
170
218
 
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(['.'])
175
 
        try:
176
 
            tree.commit('test', exclude=['b', 'c'])
177
 
        except errors.ExcludesUnsupported:
178
 
            raise tests.TestNotApplicable("excludes not supported by this "
179
 
                "repository format")
 
223
        tree.commit('test', exclude=['b', 'c'])
180
224
        # If b was excluded it will still be 'added' in status.
181
225
        tree.lock_read()
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(['.'])
192
 
        try:
193
 
            tree.commit('test', specific_files=['a'], exclude=['a/b'])
194
 
        except errors.ExcludesUnsupported:
195
 
            raise tests.TestNotApplicable("excludes not supported by this "
196
 
                "repository format")
 
236
        tree.commit('test', specific_files=['a'], exclude=['a/b'])
197
237
        # If a/b was excluded it will still be 'added' in status.
198
238
        tree.lock_read()
199
239
        self.addCleanup(tree.unlock)
215
255
        self.assertNotEqual(None, committed_id)
216
256
 
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 
219
259
        # also an error
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')
242
282
 
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'))
251
 
 
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.
265
296
        del master
266
297
        # check its corrupted.
267
298
        self.assertRaises(errors.UnknownFormatError,
268
 
                          controldir.ControlDir.open,
 
299
                          bzrdir.BzrDir.open,
269
300
                          'master')
270
301
        tree.commit('foo', rev_id='foo', local=True)
271
 
 
 
302
 
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.
281
312
            # older format.
282
313
            return
283
314
        tree.commit('foo', rev_id='foo', local=True)
284
 
        self.assertFalse(master.repository.has_revision('foo'))
 
315
        self.failIf(master.repository.has_revision('foo'))
285
316
        self.assertEqual(_mod_revision.NULL_REVISION,
286
317
                         (_mod_revision.ensure_null(master.last_revision())))
287
318
 
317
348
        wt.lock_write()
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
353
384
        wt.add(['a', 'b', 'b/c'], ['a-id', 'b-id', 'c-id'])
354
385
        wt.commit('first')
355
386
        wt.remove('b/c')
356
 
        this_dir = wt.bzrdir.root_transport
 
387
        this_dir = self.get_transport()
357
388
        this_dir.delete_tree('b')
358
389
        wt.lock_write()
359
390
        wt.commit('commit deleted rename')
413
444
            basis.get_reference_revision(basis.path2id('subtree')))
414
445
        # the outer tree must have have changed too.
415
446
        self.assertNotEqual(None, rev_id)
416
 
 
 
447
        
417
448
    def test_nested_commit_second_commit_detects_changes(self):
418
449
        """Commit with a nested tree picks up the correct child revid."""
419
450
        tree = self.make_branch_and_tree('.')
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')])
464
494
 
465
495
 
466
496
class TestCommitProgress(TestCaseWithWorkingTree):
467
 
 
468
 
    def setUp(self):
469
 
        super(TestCommitProgress, self).setUp()
470
 
        ui.ui_factory = ProgressRecordingUIFactory()
 
497
    
 
498
    def restoreDefaults(self):
 
499
        ui.ui_factory = self.old_ui_factory
471
500
 
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')
486
515
        f.close()
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 
488
517
        # emitted
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'),
505
536
 
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 
509
540
        # emitted
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):
513
546
            pass
515
548
                                               'hook name')
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'),
528
561
 
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 
532
565
        # emitted
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):
536
571
            pass
538
573
                                               'hook name')
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'),
550
585
           )
551
586
 
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 
554
589
        committed."""
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"))
569
 
 
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(
581
 
            'post_commit',
582
 
            post_commit_hook_test_params,
583
 
            None)
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"))