~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

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

  • Committer: Robert Collins
  • Date: 2008-08-20 02:07:36 UTC
  • mfrom: (3640 +trunk)
  • mto: This revision was merged to the branch mainline in revision 3682.
  • Revision ID: robertc@robertcollins.net-20080820020736-g2xe4921zzxtymle
Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# (C) 2005,2006 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
18
18
from cStringIO import StringIO
19
19
import os
20
20
 
21
 
import bzrlib
22
 
import bzrlib.branch
23
 
from bzrlib.branch import Branch
24
 
import bzrlib.bzrdir as bzrdir
25
 
from bzrlib.bzrdir import BzrDir
26
 
import bzrlib.errors as errors
 
21
from bzrlib import (
 
22
    branch,
 
23
    bzrdir,
 
24
    conflicts,
 
25
    errors,
 
26
    mutabletree,
 
27
    osutils,
 
28
    revision as _mod_revision,
 
29
    ui,
 
30
    uncommit,
 
31
    workingtree,
 
32
    )
27
33
from bzrlib.errors import (NotBranchError, NotVersionedError, 
28
34
                           UnsupportedOperation)
29
 
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
30
 
from bzrlib.tests import TestSkipped, TestCase
 
35
from bzrlib.osutils import pathjoin, getcwd
 
36
from bzrlib.tests import TestCase
31
37
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
32
38
from bzrlib.trace import mutter
33
 
import bzrlib.ui as ui
34
 
import bzrlib.workingtree as workingtree
35
39
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
36
40
                                WorkingTree)
37
41
 
67
71
    def update(self, message, count=None, total=None):
68
72
        """See progress.ProgressBar.update()."""
69
73
        if self.depth == 1:
70
 
            self._calls.append(("update", count, total))
 
74
            self._calls.append(("update", count, total, message))
71
75
 
72
76
 
73
77
class TestCapturingUI(TestCase):
82
86
        pb2.update('foo', 0, 1)
83
87
        pb2.finished()
84
88
        pb1.finished()
85
 
        self.assertEqual([("update", 0, 1)], factory._calls)
 
89
        self.assertEqual([("update", 0, 1, 'foo')], factory._calls)
86
90
 
87
91
 
88
92
class TestCommit(TestCaseWithWorkingTree):
89
93
 
 
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')
 
106
 
 
107
        tree_a.lock_read()
 
108
        try:
 
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()]
 
112
        finally:
 
113
            tree_a.unlock()
 
114
        # The only paths left should be the root
 
115
        self.assertEqual([('', root_id)], paths)
 
116
 
 
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')
 
127
 
 
128
        tree_a.lock_read()
 
129
        try:
 
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()]
 
133
        finally:
 
134
            tree_a.unlock()
 
135
        # The only paths left should be the root
 
136
        self.assertEqual([('', root_id), ('dir2', 'dir2-id'),
 
137
                          ('dir2/a', 'f1-id'),
 
138
                         ], paths)
 
139
 
 
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')
 
146
 
 
147
        tree_a.lock_read()
 
148
        try:
 
149
            root_id = tree_a.get_root_id()
 
150
        finally:
 
151
            tree_a.unlock()
 
152
 
 
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')
 
159
 
 
160
        paths = [(path, ie.file_id)
 
161
                 for path, ie in tree_b.iter_entries_by_dir()]
 
162
        self.assertEqual([('', root_id),
 
163
                          ('xyz', 'xyz-id'),
 
164
                          ('xyz/m', 'm-id'),
 
165
                         ], paths)
 
166
 
 
167
        self.build_tree_contents([('A/a/n', 'new contents for n\n')])
 
168
        tree_a.commit('change n in A')
 
169
 
 
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),
 
177
                          ('a', 'a-id'),
 
178
                          ('xyz', 'xyz-id'),
 
179
                          ('a/n.OTHER', 'n-id'),
 
180
                          ('xyz/m', 'm-id'),
 
181
                         ], paths)
 
182
        osutils.rmtree('B/a')
 
183
        try:
 
184
            # bzr resolve --all
 
185
            tree_b.set_conflicts(conflicts.ConflictList())
 
186
        except errors.UnsupportedOperation:
 
187
            # On WT2, set_conflicts is unsupported, but the rmtree has the same
 
188
            # effect.
 
189
            pass
 
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),
 
194
                          ('xyz', 'xyz-id'),
 
195
                          ('xyz/m', 'm-id'),
 
196
                         ], paths)
 
197
 
 
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'])
 
202
        wt.add('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'])
 
209
 
 
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)
 
218
 
 
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.
 
225
        tree.lock_read()
 
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])
 
231
 
 
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.
 
238
        tree.lock_read()
 
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])
 
243
 
90
244
    def test_commit_sets_last_revision(self):
91
245
        tree = self.make_branch_and_tree('tree')
92
 
        tree.commit('foo', rev_id='foo', allow_pointless=True)
93
 
        self.assertEqual('foo', tree.last_revision())
 
246
        committed_id = tree.commit('foo', rev_id='foo')
 
247
        self.assertEqual(['foo'], tree.get_parent_ids())
 
248
        # the commit should have returned the same id we asked for.
 
249
        self.assertEqual('foo', committed_id)
 
250
 
 
251
    def test_commit_returns_revision_id(self):
 
252
        tree = self.make_branch_and_tree('.')
 
253
        committed_id = tree.commit('message')
 
254
        self.assertTrue(tree.branch.repository.has_revision(committed_id))
 
255
        self.assertNotEqual(None, committed_id)
94
256
 
95
257
    def test_commit_local_unbound(self):
96
258
        # using the library api to do a local commit on unbound branches is 
100
262
                          tree.commit,
101
263
                          'foo',
102
264
                          local=True)
103
 
 
 
265
 
 
266
    def test_commit_merged_kind_change(self):
 
267
        """Test merging a kind change.
 
268
 
 
269
        Test making a kind change in a working tree, and then merging that
 
270
        from another. When committed it should commit the new kind.
 
271
        """
 
272
        wt = self.make_branch_and_tree('.')
 
273
        self.build_tree(['a'])
 
274
        wt.add(['a'])
 
275
        wt.commit('commit one')
 
276
        wt2 = wt.bzrdir.sprout('to').open_workingtree()
 
277
        os.remove('a')
 
278
        os.mkdir('a')
 
279
        wt.commit('changed kind')
 
280
        wt2.merge_from_branch(wt.branch)
 
281
        wt2.commit('merged kind change')
 
282
 
104
283
    def test_local_commit_ignores_master(self):
105
284
        # a --local commit does not require access to the master branch
106
285
        # at all, or even for it to exist.
113
292
        except errors.UpgradeRequired:
114
293
            # older format.
115
294
            return
116
 
        master.bzrdir.transport.put('branch-format', StringIO('garbage'))
 
295
        master.bzrdir.transport.put_bytes('branch-format', 'garbage')
117
296
        del master
118
297
        # check its corrupted.
119
298
        self.assertRaises(errors.UnknownFormatError,
134
313
            return
135
314
        tree.commit('foo', rev_id='foo', local=True)
136
315
        self.failIf(master.repository.has_revision('foo'))
137
 
        self.assertEqual(None, master.last_revision())
 
316
        self.assertEqual(_mod_revision.NULL_REVISION,
 
317
                         (_mod_revision.ensure_null(master.last_revision())))
 
318
 
 
319
    def test_record_initial_ghost(self):
 
320
        """The working tree needs to record ghosts during commit."""
 
321
        wt = self.make_branch_and_tree('.')
 
322
        wt.set_parent_ids(['non:existent@rev--ision--0--2'],
 
323
            allow_leftmost_as_ghost=True)
 
324
        rev_id = wt.commit('commit against a ghost first parent.')
 
325
        rev = wt.branch.repository.get_revision(rev_id)
 
326
        self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
 
327
        # parent_sha1s is not populated now, WTF. rbc 20051003
 
328
        self.assertEqual(len(rev.parent_sha1s), 0)
 
329
 
 
330
    def test_record_two_ghosts(self):
 
331
        """The working tree should preserve all the parents during commit."""
 
332
        wt = self.make_branch_and_tree('.')
 
333
        wt.set_parent_ids([
 
334
                'foo@azkhazan-123123-abcabc',
 
335
                'wibble@fofof--20050401--1928390812',
 
336
            ],
 
337
            allow_leftmost_as_ghost=True)
 
338
        rev_id = wt.commit("commit from ghost base with one merge")
 
339
        # the revision should have been committed with two parents
 
340
        rev = wt.branch.repository.get_revision(rev_id)
 
341
        self.assertEqual(['foo@azkhazan-123123-abcabc',
 
342
            'wibble@fofof--20050401--1928390812'],
 
343
            rev.parent_ids)
 
344
 
 
345
    def test_commit_deleted_subtree_and_files_updates_workingtree(self):
 
346
        """The working trees inventory may be adjusted by commit."""
 
347
        wt = self.make_branch_and_tree('.')
 
348
        wt.lock_write()
 
349
        self.build_tree(['a', 'b/', 'b/c', 'd'])
 
350
        wt.add(['a', 'b', 'b/c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
 
351
        this_dir = self.get_transport()
 
352
        this_dir.delete_tree('b')
 
353
        this_dir.delete('d')
 
354
        # now we have a tree with a through d in the inventory, but only
 
355
        # a present on disk. After commit b-id, c-id and d-id should be
 
356
        # missing from the inventory, within the same tree transaction.
 
357
        wt.commit('commit stuff')
 
358
        self.assertTrue(wt.has_id('a-id'))
 
359
        self.assertFalse(wt.has_or_had_id('b-id'))
 
360
        self.assertFalse(wt.has_or_had_id('c-id'))
 
361
        self.assertFalse(wt.has_or_had_id('d-id'))
 
362
        self.assertTrue(wt.has_filename('a'))
 
363
        self.assertFalse(wt.has_filename('b'))
 
364
        self.assertFalse(wt.has_filename('b/c'))
 
365
        self.assertFalse(wt.has_filename('d'))
 
366
        wt.unlock()
 
367
        # the changes should have persisted to disk - reopen the workingtree
 
368
        # to be sure.
 
369
        wt = wt.bzrdir.open_workingtree()
 
370
        wt.lock_read()
 
371
        self.assertTrue(wt.has_id('a-id'))
 
372
        self.assertFalse(wt.has_or_had_id('b-id'))
 
373
        self.assertFalse(wt.has_or_had_id('c-id'))
 
374
        self.assertFalse(wt.has_or_had_id('d-id'))
 
375
        self.assertTrue(wt.has_filename('a'))
 
376
        self.assertFalse(wt.has_filename('b'))
 
377
        self.assertFalse(wt.has_filename('b/c'))
 
378
        self.assertFalse(wt.has_filename('d'))
 
379
        wt.unlock()
 
380
 
 
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'])
 
385
        wt.commit('first')
 
386
        wt.remove('b/c')
 
387
        this_dir = self.get_transport()
 
388
        this_dir.delete_tree('b')
 
389
        wt.lock_write()
 
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'))
 
397
        wt.unlock()
 
398
 
 
399
    def test_commit_move_new(self):
 
400
        wt = self.make_branch_and_tree('first')
 
401
        wt.commit('first')
 
402
        wt2 = wt.bzrdir.sprout('second').open_workingtree()
 
403
        self.build_tree(['second/name1'])
 
404
        wt2.add('name1', 'name1-id')
 
405
        wt2.commit('second')
 
406
        wt.merge_from_branch(wt2.branch)
 
407
        wt.rename_one('name1', 'name2')
 
408
        wt.commit('third')
 
409
        wt.path2id('name1-id')
 
410
 
 
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():
 
415
            # inapplicable test.
 
416
            return
 
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)
 
424
        tree.lock_read()
 
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
 
429
        # subtree revision.
 
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
 
436
        # take place.
 
437
        self.assertNotEqual(None, subtree.last_revision())
 
438
        # the outer tree should have committed a pointer to the current
 
439
        # subtree revision.
 
440
        basis = tree.basis_tree()
 
441
        basis.lock_read()
 
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)
138
447
        
 
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():
 
452
            # inapplicable test.
 
453
            return
 
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
 
467
        # take place.
 
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
 
471
        # subtree revision.
 
472
        basis = tree.basis_tree()
 
473
        basis.lock_read()
 
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)
 
478
 
 
479
    def test_nested_pointless_commits_are_pointless(self):
 
480
        tree = self.make_branch_and_tree('.')
 
481
        if not tree.supports_tree_reference():
 
482
            # inapplicable test.
 
483
            return
 
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())
 
494
 
139
495
 
140
496
class TestCommitProgress(TestCaseWithWorkingTree):
141
497
    
167
523
        # into the factory for this test - just make the test ui factory
168
524
        # pun as a reporter. Then we can check the ordering is right.
169
525
        tree.commit('second post', specific_files=['b'])
170
 
        # 9 steps: 1 for rev, 2 for inventory, 1 for finishing. 2 for root
171
 
        # and 6 for inventory files.
172
 
        # 2 steps don't trigger an update, as 'a' and 'c' are not 
173
 
        # committed.
174
 
        self.assertEqual(
175
 
            [("update", 0, 9),
176
 
             ("update", 1, 9),
177
 
             ("update", 2, 9),
178
 
             ("update", 3, 9),
179
 
             ("update", 4, 9),
180
 
             ("update", 5, 9),
181
 
             ("update", 6, 9),
182
 
             ("update", 7, 9)],
183
 
            factory._calls
184
 
           )
 
526
        # 5 steps, the first of which is reported 2 times, once per dir
 
527
        self.assertEqual(
 
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')],
 
534
            factory._calls
 
535
           )
 
536
 
 
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 
 
540
        # emitted
 
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):
 
546
            pass
 
547
        branch.Branch.hooks.install_named_hook('post_commit', a_hook,
 
548
                                               'hook name')
 
549
        tree.commit('first post')
 
550
        self.assertEqual(
 
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'),
 
558
             ],
 
559
            factory._calls
 
560
           )
 
561
 
 
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 
 
565
        # emitted
 
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):
 
571
            pass
 
572
        branch.Branch.hooks.install_named_hook('pre_commit', a_hook,
 
573
                                               'hook name')
 
574
        tree.commit('first post')
 
575
        self.assertEqual(
 
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'),
 
583
             ],
 
584
            factory._calls
 
585
           )
 
586
 
 
587
    def test_start_commit_hook(self):
 
588
        """Make sure a start commit hook can modify the tree that is 
 
589
        committed."""
 
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(
 
598
            'start_commit',
 
599
            start_commit_hook_adds_file,
 
600
            None)
 
601
        revid = tree.commit('first post')
 
602
        committed_tree = tree.basis_tree()
 
603
        self.assertTrue(committed_tree.has_filename("newfile"))