~bzr-pqm/bzr/bzr.dev

1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
1
# Copyright (C) 2005, 2006 Canonical Ltd
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
2
# Authors:  Robert Collins <robert.collins@canonical.com>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17
18
from cStringIO import StringIO
19
import os
20
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
21
from bzrlib import (
22
    branch,
23
    bzrdir,
24
    errors,
25
    revision as _mod_revision,
26
    ui,
27
    uncommit,
28
    workingtree,
29
    )
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
30
from bzrlib.errors import (NotBranchError, NotVersionedError, 
31
                           UnsupportedOperation)
32
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
33
from bzrlib.tests import TestSkipped, TestCase
34
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
35
from bzrlib.trace import mutter
36
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
37
                                WorkingTree)
38
39
40
class CapturingUIFactory(ui.UIFactory):
41
    """A UI Factory for testing - capture the updates made through it."""
42
43
    def __init__(self):
44
        super(CapturingUIFactory, self).__init__()
45
        self._calls = []
46
        self.depth = 0
47
48
    def clear(self):
49
        """See progress.ProgressBar.clear()."""
50
51
    def clear_term(self):
52
        """See progress.ProgressBar.clear_term()."""
53
54
    def finished(self):
55
        """See progress.ProgressBar.finished()."""
56
        self.depth -= 1
57
58
    def note(self, fmt_string, *args, **kwargs):
59
        """See progress.ProgressBar.note()."""
60
61
    def progress_bar(self):
62
        return self
63
    
64
    def nested_progress_bar(self):
65
        self.depth += 1
66
        return self
67
68
    def update(self, message, count=None, total=None):
69
        """See progress.ProgressBar.update()."""
70
        if self.depth == 1:
2531.1.3 by Ian Clatworthy
Fix whitespace and improve tests to cover actual progress messages
71
            self._calls.append(("update", count, total, message))
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
72
73
74
class TestCapturingUI(TestCase):
75
76
    def test_nested_ignore_depth_beyond_one(self):
77
        # we only want to capture the first level out progress, not
78
        # want sub-components might do. So we have nested bars ignored.
79
        factory = CapturingUIFactory()
80
        pb1 = factory.nested_progress_bar()
81
        pb1.update('foo', 0, 1)
82
        pb2 = factory.nested_progress_bar()
83
        pb2.update('foo', 0, 1)
84
        pb2.finished()
85
        pb1.finished()
2531.1.3 by Ian Clatworthy
Fix whitespace and improve tests to cover actual progress messages
86
        self.assertEqual([("update", 0, 1, 'foo')], factory._calls)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
87
88
89
class TestCommit(TestCaseWithWorkingTree):
90
91
    def test_commit_sets_last_revision(self):
92
        tree = self.make_branch_and_tree('tree')
2825.5.2 by Robert Collins
Review feedback, and fix pointless commits with nested trees to raise PointlessCommit appropriately.
93
        committed_id = tree.commit('foo', rev_id='foo')
1908.7.6 by Robert Collins
Deprecate WorkingTree.last_revision.
94
        self.assertEqual(['foo'], tree.get_parent_ids())
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
95
        # the commit should have returned the same id we asked for.
96
        self.assertEqual('foo', committed_id)
97
98
    def test_commit_returns_revision_id(self):
99
        tree = self.make_branch_and_tree('.')
2825.5.2 by Robert Collins
Review feedback, and fix pointless commits with nested trees to raise PointlessCommit appropriately.
100
        committed_id = tree.commit('message')
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
101
        self.assertTrue(tree.branch.repository.has_revision(committed_id))
102
        self.assertNotEqual(None, committed_id)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
103
104
    def test_commit_local_unbound(self):
105
        # using the library api to do a local commit on unbound branches is 
106
        # also an error
107
        tree = self.make_branch_and_tree('tree')
108
        self.assertRaises(errors.LocalRequiresBoundBranch,
109
                          tree.commit,
110
                          'foo',
111
                          local=True)
2374.2.1 by John Arbash Meinel
(broken) merge a test case showing that commiting a merge of a kind change fails.
112
113
    def test_commit_merged_kind_change(self):
114
        """Test merging a kind change.
115
116
        Test making a kind change in a working tree, and then merging that
117
        from another. When committed it should commit the new kind.
118
        """
119
        wt = self.make_branch_and_tree('.')
120
        self.build_tree(['a'])
121
        wt.add(['a'])
122
        wt.commit('commit one')
123
        wt2 = wt.bzrdir.sprout('to').open_workingtree()
124
        os.remove('a')
125
        os.mkdir('a')
126
        wt.commit('changed kind')
127
        wt2.merge_from_branch(wt.branch)
128
        wt2.commit('merged kind change')
129
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
130
    def test_local_commit_ignores_master(self):
131
        # a --local commit does not require access to the master branch
132
        # at all, or even for it to exist.
133
        # we test this by setting up a bound branch and then corrupting
134
        # the master.
135
        master = self.make_branch('master')
136
        tree = self.make_branch_and_tree('tree')
137
        try:
138
            tree.branch.bind(master)
139
        except errors.UpgradeRequired:
140
            # older format.
141
            return
1955.3.14 by John Arbash Meinel
Correctly fix the workingtree put() test fixes
142
        master.bzrdir.transport.put_bytes('branch-format', 'garbage')
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
143
        del master
144
        # check its corrupted.
145
        self.assertRaises(errors.UnknownFormatError,
146
                          bzrdir.BzrDir.open,
147
                          'master')
148
        tree.commit('foo', rev_id='foo', local=True)
149
 
150
    def test_local_commit_does_not_push_to_master(self):
151
        # a --local commit does not require access to the master branch
152
        # at all, or even for it to exist.
153
        # we test that even when its available it does not push to it.
154
        master = self.make_branch('master')
155
        tree = self.make_branch_and_tree('tree')
156
        try:
157
            tree.branch.bind(master)
158
        except errors.UpgradeRequired:
159
            # older format.
160
            return
161
        tree.commit('foo', rev_id='foo', local=True)
162
        self.failIf(master.repository.has_revision('foo'))
2598.5.7 by Aaron Bentley
Updates from review
163
        self.assertEqual(_mod_revision.NULL_REVISION,
164
                         (_mod_revision.ensure_null(master.last_revision())))
1927.2.1 by Robert Collins
Alter set_pending_merges to shove the left most merge into the trees last-revision if that is not set. Related bugfixes include basis_tree handling ghosts, de-duping the merges with the last-revision and update changing where and how it adds its pending merge.
165
166
    def test_record_initial_ghost(self):
167
        """The working tree needs to record ghosts during commit."""
168
        wt = self.make_branch_and_tree('.')
1908.6.7 by Robert Collins
Remove all users of set_pending_merges and add_pending_merge except tests that they work correctly.
169
        wt.set_parent_ids(['non:existent@rev--ision--0--2'],
170
            allow_leftmost_as_ghost=True)
1927.2.1 by Robert Collins
Alter set_pending_merges to shove the left most merge into the trees last-revision if that is not set. Related bugfixes include basis_tree handling ghosts, de-duping the merges with the last-revision and update changing where and how it adds its pending merge.
171
        rev_id = wt.commit('commit against a ghost first parent.')
172
        rev = wt.branch.repository.get_revision(rev_id)
173
        self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
174
        # parent_sha1s is not populated now, WTF. rbc 20051003
175
        self.assertEqual(len(rev.parent_sha1s), 0)
176
177
    def test_record_two_ghosts(self):
178
        """The working tree should preserve all the parents during commit."""
179
        wt = self.make_branch_and_tree('.')
1908.6.7 by Robert Collins
Remove all users of set_pending_merges and add_pending_merge except tests that they work correctly.
180
        wt.set_parent_ids([
181
                'foo@azkhazan-123123-abcabc',
182
                'wibble@fofof--20050401--1928390812',
183
            ],
184
            allow_leftmost_as_ghost=True)
1927.2.1 by Robert Collins
Alter set_pending_merges to shove the left most merge into the trees last-revision if that is not set. Related bugfixes include basis_tree handling ghosts, de-duping the merges with the last-revision and update changing where and how it adds its pending merge.
185
        rev_id = wt.commit("commit from ghost base with one merge")
186
        # the revision should have been committed with two parents
187
        rev = wt.branch.repository.get_revision(rev_id)
188
        self.assertEqual(['foo@azkhazan-123123-abcabc',
189
            'wibble@fofof--20050401--1928390812'],
190
            rev.parent_ids)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
191
1988.3.1 by Robert Collins
Add test case to ensure that the working tree inventory and disk state is correctly update when commit is removing directory entries.
192
    def test_commit_deleted_subtree_and_files_updates_workingtree(self):
193
        """The working trees inventory may be adjusted by commit."""
194
        wt = self.make_branch_and_tree('.')
195
        wt.lock_write()
196
        self.build_tree(['a', 'b/', 'b/c', 'd'])
197
        wt.add(['a', 'b', 'b/c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
198
        this_dir = self.get_transport()
199
        this_dir.delete_tree('b')
200
        this_dir.delete('d')
201
        # now we have a tree with a through d in the inventory, but only
202
        # a present on disk. After commit b-id, c-id and d-id should be
203
        # missing from the inventory, within the same tree transaction.
204
        wt.commit('commit stuff')
205
        self.assertTrue(wt.has_id('a-id'))
206
        self.assertFalse(wt.has_or_had_id('b-id'))
207
        self.assertFalse(wt.has_or_had_id('c-id'))
208
        self.assertFalse(wt.has_or_had_id('d-id'))
209
        self.assertTrue(wt.has_filename('a'))
210
        self.assertFalse(wt.has_filename('b'))
211
        self.assertFalse(wt.has_filename('b/c'))
212
        self.assertFalse(wt.has_filename('d'))
213
        wt.unlock()
214
        # the changes should have persisted to disk - reopen the workingtree
215
        # to be sure.
216
        wt = wt.bzrdir.open_workingtree()
217
        wt.lock_read()
218
        self.assertTrue(wt.has_id('a-id'))
219
        self.assertFalse(wt.has_or_had_id('b-id'))
220
        self.assertFalse(wt.has_or_had_id('c-id'))
221
        self.assertFalse(wt.has_or_had_id('d-id'))
222
        self.assertTrue(wt.has_filename('a'))
223
        self.assertFalse(wt.has_filename('b'))
224
        self.assertFalse(wt.has_filename('b/c'))
225
        self.assertFalse(wt.has_filename('d'))
226
        wt.unlock()
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
227
2363.2.2 by John Arbash Meinel
Simplify the test even further....
228
    def test_commit_deleted_subtree_with_removed(self):
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
229
        wt = self.make_branch_and_tree('.')
230
        self.build_tree(['a', 'b/', 'b/c', 'd'])
231
        wt.add(['a', 'b', 'b/c'], ['a-id', 'b-id', 'c-id'])
232
        wt.commit('first')
2363.2.2 by John Arbash Meinel
Simplify the test even further....
233
        wt.remove('b/c')
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
234
        this_dir = self.get_transport()
235
        this_dir.delete_tree('b')
236
        wt.lock_write()
237
        wt.commit('commit deleted rename')
238
        self.assertTrue(wt.has_id('a-id'))
239
        self.assertFalse(wt.has_or_had_id('b-id'))
240
        self.assertFalse(wt.has_or_had_id('c-id'))
241
        self.assertTrue(wt.has_filename('a'))
242
        self.assertFalse(wt.has_filename('b'))
243
        self.assertFalse(wt.has_filename('b/c'))
244
        wt.unlock()
245
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
246
    def test_commit_move_new(self):
247
        wt = self.make_branch_and_tree('first')
248
        wt.commit('first')
249
        wt2 = wt.bzrdir.sprout('second').open_workingtree()
250
        self.build_tree(['second/name1'])
251
        wt2.add('name1', 'name1-id')
252
        wt2.commit('second')
253
        wt.merge_from_branch(wt2.branch)
254
        wt.rename_one('name1', 'name2')
255
        wt.commit('third')
256
        wt.path2id('name1-id')
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
257
258
    def test_nested_commit(self):
259
        """Commit in multiply-nested trees"""
260
        tree = self.make_branch_and_tree('.')
261
        if not tree.supports_tree_reference():
262
            # inapplicable test.
263
            return
264
        subtree = self.make_branch_and_tree('subtree')
265
        subsubtree = self.make_branch_and_tree('subtree/subtree')
266
        subtree.add(['subtree'])
267
        tree.add(['subtree'])
268
        # use allow_pointless=False to ensure that the deepest tree, which
269
        # has no commits made to it, does not get a pointless commit.
270
        rev_id = tree.commit('added reference', allow_pointless=False)
271
        tree.lock_read()
272
        self.addCleanup(tree.unlock)
273
        # the deepest subtree has not changed, so no commit should take place.
2598.5.10 by Aaron Bentley
Return NULL_REVISION instead of None for the null revision
274
        self.assertEqual('null:', subsubtree.last_revision())
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
275
        # the intermediate tree should have committed a pointer to the current
276
        # subtree revision.
277
        sub_basis = subtree.basis_tree()
278
        sub_basis.lock_read()
279
        self.addCleanup(sub_basis.unlock)
280
        self.assertEqual(subsubtree.last_revision(),
2255.2.227 by Robert Collins
Make all test_commit tests pass.
281
            sub_basis.get_reference_revision(sub_basis.path2id('subtree')))
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
282
        # the intermediate tree has changed, so should have had a commit
283
        # take place.
284
        self.assertNotEqual(None, subtree.last_revision())
285
        # the outer tree should have committed a pointer to the current
286
        # subtree revision.
287
        basis = tree.basis_tree()
288
        basis.lock_read()
289
        self.addCleanup(basis.unlock)
290
        self.assertEqual(subtree.last_revision(),
2255.2.226 by Robert Collins
Get merge_nested finally working: change nested tree iterators to take file_ids, and ensure the right branch is connected to in the merge logic. May not be suitable for shared repositories yet.
291
            basis.get_reference_revision(basis.path2id('subtree')))
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
292
        # the outer tree must have have changed too.
293
        self.assertNotEqual(None, rev_id)
1988.3.1 by Robert Collins
Add test case to ensure that the working tree inventory and disk state is correctly update when commit is removing directory entries.
294
        
2255.2.220 by Robert Collins
Fix failing detection of changes restricted to subtrees causing spurious pointless commit errors.
295
    def test_nested_commit_second_commit_detects_changes(self):
296
        """Commit with a nested tree picks up the correct child revid."""
297
        tree = self.make_branch_and_tree('.')
298
        if not tree.supports_tree_reference():
299
            # inapplicable test.
300
            return
301
        subtree = self.make_branch_and_tree('subtree')
302
        tree.add(['subtree'])
303
        self.build_tree(['subtree/file'])
304
        subtree.add(['file'], ['file-id'])
305
        rev_id = tree.commit('added reference', allow_pointless=False)
306
        child_revid = subtree.last_revision()
307
        # now change the child tree
308
        self.build_tree_contents([('subtree/file', 'new-content')])
309
        # and commit in the parent should commit the child and grab its revid,
310
        # we test with allow_pointless=False here so that we are simulating
311
        # what users will see.
312
        rev_id2 = tree.commit('changed subtree only', allow_pointless=False)
313
        # the child tree has changed, so should have had a commit
314
        # take place.
315
        self.assertNotEqual(None, subtree.last_revision())
316
        self.assertNotEqual(child_revid, subtree.last_revision())
317
        # the outer tree should have committed a pointer to the current
318
        # subtree revision.
319
        basis = tree.basis_tree()
320
        basis.lock_read()
321
        self.addCleanup(basis.unlock)
322
        self.assertEqual(subtree.last_revision(),
2255.2.226 by Robert Collins
Get merge_nested finally working: change nested tree iterators to take file_ids, and ensure the right branch is connected to in the merge logic. May not be suitable for shared repositories yet.
323
            basis.get_reference_revision(basis.path2id('subtree')))
2255.2.220 by Robert Collins
Fix failing detection of changes restricted to subtrees causing spurious pointless commit errors.
324
        self.assertNotEqual(rev_id, rev_id2)
325
2825.5.2 by Robert Collins
Review feedback, and fix pointless commits with nested trees to raise PointlessCommit appropriately.
326
    def test_nested_pointless_commits_are_pointless(self):
327
        tree = self.make_branch_and_tree('.')
328
        if not tree.supports_tree_reference():
329
            # inapplicable test.
330
            return
331
        subtree = self.make_branch_and_tree('subtree')
332
        tree.add(['subtree'])
333
        # record the reference.
334
        rev_id = tree.commit('added reference')
335
        child_revid = subtree.last_revision()
336
        # now do a no-op commit with allow_pointless=False
337
        self.assertRaises(errors.PointlessCommit, tree.commit, '',
338
            allow_pointless=False)
339
        self.assertEqual(child_revid, subtree.last_revision())
340
        self.assertEqual(rev_id, tree.last_revision())
341
1988.3.1 by Robert Collins
Add test case to ensure that the working tree inventory and disk state is correctly update when commit is removing directory entries.
342
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
343
class TestCommitProgress(TestCaseWithWorkingTree):
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
344
    
345
    def restoreDefaults(self):
346
        ui.ui_factory = self.old_ui_factory
347
348
    def test_commit_progress_steps(self):
349
        # during commit we one progress update for every entry in the 
350
        # inventory, and then one for the inventory, and one for the
351
        # inventory, and one for the revision insertions.
352
        # first we need a test commit to do. Lets setup a branch with 
353
        # 3 files, and alter one in a selected-file commit. This exercises
354
        # a number of cases quickly. We should also test things like 
355
        # selective commits which excludes newly added files.
356
        tree = self.make_branch_and_tree('.')
357
        self.build_tree(['a', 'b', 'c'])
358
        tree.add(['a', 'b', 'c'])
359
        tree.commit('first post')
360
        f = file('b', 'wt')
361
        f.write('new content')
362
        f.close()
363
        # set a progress bar that captures the calls so we can see what is 
364
        # emitted
365
        self.old_ui_factory = ui.ui_factory
366
        self.addCleanup(self.restoreDefaults)
367
        factory = CapturingUIFactory()
368
        ui.ui_factory = factory
369
        # TODO RBC 20060421 it would be nice to merge the reporter output
370
        # into the factory for this test - just make the test ui factory
371
        # pun as a reporter. Then we can check the ordering is right.
372
        tree.commit('second post', specific_files=['b'])
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
373
        # 5 steps, the first of which is reported 2 times, once per dir
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
374
        self.assertEqual(
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
375
            [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
376
             ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
377
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
378
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
379
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
380
             ('update', 5, 5, 'Running post_commit hooks - Stage')],
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
381
            factory._calls
382
           )
2553.1.2 by Robert Collins
Show hook names during commit.
383
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
384
    def test_commit_progress_shows_post_hook_names(self):
2553.1.2 by Robert Collins
Show hook names during commit.
385
        tree = self.make_branch_and_tree('.')
386
        # set a progress bar that captures the calls so we can see what is 
387
        # emitted
388
        self.old_ui_factory = ui.ui_factory
389
        self.addCleanup(self.restoreDefaults)
390
        factory = CapturingUIFactory()
391
        ui.ui_factory = factory
392
        def a_hook(_, _2, _3, _4, _5, _6):
393
            pass
394
        branch.Branch.hooks.install_hook('post_commit', a_hook)
395
        branch.Branch.hooks.name_hook(a_hook, 'hook name')
396
        tree.commit('first post')
397
        self.assertEqual(
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
398
            [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
399
             ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
400
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
401
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
402
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
403
             ('update', 5, 5, 'Running post_commit hooks - Stage'),
404
             ('update', 5, 5, 'Running post_commit hooks [hook name] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
405
             ],
406
            factory._calls
407
           )
408
409
    def test_commit_progress_shows_pre_hook_names(self):
410
        tree = self.make_branch_and_tree('.')
411
        # set a progress bar that captures the calls so we can see what is 
412
        # emitted
413
        self.old_ui_factory = ui.ui_factory
414
        self.addCleanup(self.restoreDefaults)
415
        factory = CapturingUIFactory()
416
        ui.ui_factory = factory
2659.3.3 by NamNguyen
Changed ``pre_commit`` hook signature.
417
        def a_hook(_, _2, _3, _4, _5, _6, _7, _8):
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
418
            pass
419
        branch.Branch.hooks.install_hook('pre_commit', a_hook)
420
        branch.Branch.hooks.name_hook(a_hook, 'hook name')
421
        tree.commit('first post')
422
        self.assertEqual(
423
            [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
424
             ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
425
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
426
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
427
             ('update', 3, 5, 'Running pre_commit hooks [hook name] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
428
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
429
             ('update', 5, 5, 'Running post_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
430
             ],
431
            factory._calls
432
           )