/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
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,
2922.2.4 by John Arbash Meinel
Fix bug #114615 by teaching unversion() to not touch renamed entries.
24
    conflicts,
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
25
    errors,
3335.1.3 by Jelmer Vernooij
Add tests for start_commit hook.
26
    mutabletree,
2922.2.1 by John Arbash Meinel
Add failing tests exposing part of bug #114615
27
    osutils,
2598.5.2 by Aaron Bentley
Got all tests passing with Branch returning 'null:' for null revision
28
    revision as _mod_revision,
29
    ui,
30
    uncommit,
31
    workingtree,
32
    )
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
33
from bzrlib.errors import (NotBranchError, NotVersionedError, 
34
                           UnsupportedOperation)
2949.5.1 by Alexander Belchenko
selftest: use SymlinkFeature instead of TestSkipped where appropriate
35
from bzrlib.osutils import pathjoin, getcwd
36
from bzrlib.tests import TestCase
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
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:
2531.1.3 by Ian Clatworthy
Fix whitespace and improve tests to cover actual progress messages
74
            self._calls.append(("update", count, total, message))
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
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()
2531.1.3 by Ian Clatworthy
Fix whitespace and improve tests to cover actual progress messages
89
        self.assertEqual([("update", 0, 1, 'foo')], factory._calls)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
90
91
92
class TestCommit(TestCaseWithWorkingTree):
93
2922.2.1 by John Arbash Meinel
Add failing tests exposing part of bug #114615
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:
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
109
            root_id = tree_a.get_root_id()
2922.2.1 by John Arbash Meinel
Add failing tests exposing part of bug #114615
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
2922.2.3 by John Arbash Meinel
Add a test which shows why the previous fix is broken.
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:
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
130
            root_id = tree_a.get_root_id()
2922.2.3 by John Arbash Meinel
Add a test which shows why the previous fix is broken.
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
2922.2.4 by John Arbash Meinel
Fix bug #114615 by teaching unversion() to not touch renamed entries.
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:
2946.3.3 by John Arbash Meinel
Prefer tree.get_root_id() as more explicit than tree.path2id('')
149
            root_id = tree_a.get_root_id()
2922.2.4 by John Arbash Meinel
Fix bug #114615 by teaching unversion() to not touch renamed entries.
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
3602.1.1 by Robert Collins
Add support for -x or --exclude to bzr commit, fixing bug 3117. (Robert Collins)
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_excludes_modified_files(self):
211
        tree = self.make_branch_and_tree('.')
212
        self.build_tree(['a', 'b', 'c'])
213
        tree.smart_add(['.'])
214
        tree.commit('test', exclude=['b', 'c'])
215
        # If b was ignored it will still be 'added' in status.
216
        tree.lock_read()
217
        self.addCleanup(tree.unlock)
218
        changes = list(tree.iter_changes(tree.basis_tree()))
219
        self.assertEqual(2, len(changes))
220
        self.assertEqual((None, 'b'), changes[0][1])
221
        self.assertEqual((None, 'c'), changes[1][1])
222
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
223
    def test_commit_sets_last_revision(self):
224
        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.
225
        committed_id = tree.commit('foo', rev_id='foo')
1908.7.6 by Robert Collins
Deprecate WorkingTree.last_revision.
226
        self.assertEqual(['foo'], tree.get_parent_ids())
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
227
        # the commit should have returned the same id we asked for.
228
        self.assertEqual('foo', committed_id)
229
230
    def test_commit_returns_revision_id(self):
231
        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.
232
        committed_id = tree.commit('message')
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
233
        self.assertTrue(tree.branch.repository.has_revision(committed_id))
234
        self.assertNotEqual(None, committed_id)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
235
236
    def test_commit_local_unbound(self):
237
        # using the library api to do a local commit on unbound branches is 
238
        # also an error
239
        tree = self.make_branch_and_tree('tree')
240
        self.assertRaises(errors.LocalRequiresBoundBranch,
241
                          tree.commit,
242
                          'foo',
243
                          local=True)
2374.2.1 by John Arbash Meinel
(broken) merge a test case showing that commiting a merge of a kind change fails.
244
245
    def test_commit_merged_kind_change(self):
246
        """Test merging a kind change.
247
248
        Test making a kind change in a working tree, and then merging that
249
        from another. When committed it should commit the new kind.
250
        """
251
        wt = self.make_branch_and_tree('.')
252
        self.build_tree(['a'])
253
        wt.add(['a'])
254
        wt.commit('commit one')
255
        wt2 = wt.bzrdir.sprout('to').open_workingtree()
256
        os.remove('a')
257
        os.mkdir('a')
258
        wt.commit('changed kind')
259
        wt2.merge_from_branch(wt.branch)
260
        wt2.commit('merged kind change')
261
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
262
    def test_local_commit_ignores_master(self):
263
        # a --local commit does not require access to the master branch
264
        # at all, or even for it to exist.
265
        # we test this by setting up a bound branch and then corrupting
266
        # the master.
267
        master = self.make_branch('master')
268
        tree = self.make_branch_and_tree('tree')
269
        try:
270
            tree.branch.bind(master)
271
        except errors.UpgradeRequired:
272
            # older format.
273
            return
1955.3.14 by John Arbash Meinel
Correctly fix the workingtree put() test fixes
274
        master.bzrdir.transport.put_bytes('branch-format', 'garbage')
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
275
        del master
276
        # check its corrupted.
277
        self.assertRaises(errors.UnknownFormatError,
278
                          bzrdir.BzrDir.open,
279
                          'master')
280
        tree.commit('foo', rev_id='foo', local=True)
281
 
282
    def test_local_commit_does_not_push_to_master(self):
283
        # a --local commit does not require access to the master branch
284
        # at all, or even for it to exist.
285
        # we test that even when its available it does not push to it.
286
        master = self.make_branch('master')
287
        tree = self.make_branch_and_tree('tree')
288
        try:
289
            tree.branch.bind(master)
290
        except errors.UpgradeRequired:
291
            # older format.
292
            return
293
        tree.commit('foo', rev_id='foo', local=True)
294
        self.failIf(master.repository.has_revision('foo'))
2598.5.7 by Aaron Bentley
Updates from review
295
        self.assertEqual(_mod_revision.NULL_REVISION,
296
                         (_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.
297
298
    def test_record_initial_ghost(self):
299
        """The working tree needs to record ghosts during commit."""
300
        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.
301
        wt.set_parent_ids(['non:existent@rev--ision--0--2'],
302
            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.
303
        rev_id = wt.commit('commit against a ghost first parent.')
304
        rev = wt.branch.repository.get_revision(rev_id)
305
        self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
306
        # parent_sha1s is not populated now, WTF. rbc 20051003
307
        self.assertEqual(len(rev.parent_sha1s), 0)
308
309
    def test_record_two_ghosts(self):
310
        """The working tree should preserve all the parents during commit."""
311
        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.
312
        wt.set_parent_ids([
313
                'foo@azkhazan-123123-abcabc',
314
                'wibble@fofof--20050401--1928390812',
315
            ],
316
            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.
317
        rev_id = wt.commit("commit from ghost base with one merge")
318
        # the revision should have been committed with two parents
319
        rev = wt.branch.repository.get_revision(rev_id)
320
        self.assertEqual(['foo@azkhazan-123123-abcabc',
321
            'wibble@fofof--20050401--1928390812'],
322
            rev.parent_ids)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
323
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.
324
    def test_commit_deleted_subtree_and_files_updates_workingtree(self):
325
        """The working trees inventory may be adjusted by commit."""
326
        wt = self.make_branch_and_tree('.')
327
        wt.lock_write()
328
        self.build_tree(['a', 'b/', 'b/c', 'd'])
329
        wt.add(['a', 'b', 'b/c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
330
        this_dir = self.get_transport()
331
        this_dir.delete_tree('b')
332
        this_dir.delete('d')
333
        # now we have a tree with a through d in the inventory, but only
334
        # a present on disk. After commit b-id, c-id and d-id should be
335
        # missing from the inventory, within the same tree transaction.
336
        wt.commit('commit stuff')
337
        self.assertTrue(wt.has_id('a-id'))
338
        self.assertFalse(wt.has_or_had_id('b-id'))
339
        self.assertFalse(wt.has_or_had_id('c-id'))
340
        self.assertFalse(wt.has_or_had_id('d-id'))
341
        self.assertTrue(wt.has_filename('a'))
342
        self.assertFalse(wt.has_filename('b'))
343
        self.assertFalse(wt.has_filename('b/c'))
344
        self.assertFalse(wt.has_filename('d'))
345
        wt.unlock()
346
        # the changes should have persisted to disk - reopen the workingtree
347
        # to be sure.
348
        wt = wt.bzrdir.open_workingtree()
349
        wt.lock_read()
350
        self.assertTrue(wt.has_id('a-id'))
351
        self.assertFalse(wt.has_or_had_id('b-id'))
352
        self.assertFalse(wt.has_or_had_id('c-id'))
353
        self.assertFalse(wt.has_or_had_id('d-id'))
354
        self.assertTrue(wt.has_filename('a'))
355
        self.assertFalse(wt.has_filename('b'))
356
        self.assertFalse(wt.has_filename('b/c'))
357
        self.assertFalse(wt.has_filename('d'))
358
        wt.unlock()
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
359
2363.2.2 by John Arbash Meinel
Simplify the test even further....
360
    def test_commit_deleted_subtree_with_removed(self):
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
361
        wt = self.make_branch_and_tree('.')
362
        self.build_tree(['a', 'b/', 'b/c', 'd'])
363
        wt.add(['a', 'b', 'b/c'], ['a-id', 'b-id', 'c-id'])
364
        wt.commit('first')
2363.2.2 by John Arbash Meinel
Simplify the test even further....
365
        wt.remove('b/c')
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
366
        this_dir = self.get_transport()
367
        this_dir.delete_tree('b')
368
        wt.lock_write()
369
        wt.commit('commit deleted rename')
370
        self.assertTrue(wt.has_id('a-id'))
371
        self.assertFalse(wt.has_or_had_id('b-id'))
372
        self.assertFalse(wt.has_or_had_id('c-id'))
373
        self.assertTrue(wt.has_filename('a'))
374
        self.assertFalse(wt.has_filename('b'))
375
        self.assertFalse(wt.has_filename('b/c'))
376
        wt.unlock()
377
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
378
    def test_commit_move_new(self):
379
        wt = self.make_branch_and_tree('first')
380
        wt.commit('first')
381
        wt2 = wt.bzrdir.sprout('second').open_workingtree()
382
        self.build_tree(['second/name1'])
383
        wt2.add('name1', 'name1-id')
384
        wt2.commit('second')
385
        wt.merge_from_branch(wt2.branch)
386
        wt.rename_one('name1', 'name2')
387
        wt.commit('third')
388
        wt.path2id('name1-id')
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
389
390
    def test_nested_commit(self):
391
        """Commit in multiply-nested trees"""
392
        tree = self.make_branch_and_tree('.')
393
        if not tree.supports_tree_reference():
394
            # inapplicable test.
395
            return
396
        subtree = self.make_branch_and_tree('subtree')
397
        subsubtree = self.make_branch_and_tree('subtree/subtree')
398
        subtree.add(['subtree'])
399
        tree.add(['subtree'])
400
        # use allow_pointless=False to ensure that the deepest tree, which
401
        # has no commits made to it, does not get a pointless commit.
402
        rev_id = tree.commit('added reference', allow_pointless=False)
403
        tree.lock_read()
404
        self.addCleanup(tree.unlock)
405
        # 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
406
        self.assertEqual('null:', subsubtree.last_revision())
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
407
        # the intermediate tree should have committed a pointer to the current
408
        # subtree revision.
409
        sub_basis = subtree.basis_tree()
410
        sub_basis.lock_read()
411
        self.addCleanup(sub_basis.unlock)
412
        self.assertEqual(subsubtree.last_revision(),
2255.2.227 by Robert Collins
Make all test_commit tests pass.
413
            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.
414
        # the intermediate tree has changed, so should have had a commit
415
        # take place.
416
        self.assertNotEqual(None, subtree.last_revision())
417
        # the outer tree should have committed a pointer to the current
418
        # subtree revision.
419
        basis = tree.basis_tree()
420
        basis.lock_read()
421
        self.addCleanup(basis.unlock)
422
        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.
423
            basis.get_reference_revision(basis.path2id('subtree')))
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
424
        # the outer tree must have have changed too.
425
        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.
426
        
2255.2.220 by Robert Collins
Fix failing detection of changes restricted to subtrees causing spurious pointless commit errors.
427
    def test_nested_commit_second_commit_detects_changes(self):
428
        """Commit with a nested tree picks up the correct child revid."""
429
        tree = self.make_branch_and_tree('.')
430
        if not tree.supports_tree_reference():
431
            # inapplicable test.
432
            return
433
        subtree = self.make_branch_and_tree('subtree')
434
        tree.add(['subtree'])
435
        self.build_tree(['subtree/file'])
436
        subtree.add(['file'], ['file-id'])
437
        rev_id = tree.commit('added reference', allow_pointless=False)
438
        child_revid = subtree.last_revision()
439
        # now change the child tree
440
        self.build_tree_contents([('subtree/file', 'new-content')])
441
        # and commit in the parent should commit the child and grab its revid,
442
        # we test with allow_pointless=False here so that we are simulating
443
        # what users will see.
444
        rev_id2 = tree.commit('changed subtree only', allow_pointless=False)
445
        # the child tree has changed, so should have had a commit
446
        # take place.
447
        self.assertNotEqual(None, subtree.last_revision())
448
        self.assertNotEqual(child_revid, subtree.last_revision())
449
        # the outer tree should have committed a pointer to the current
450
        # subtree revision.
451
        basis = tree.basis_tree()
452
        basis.lock_read()
453
        self.addCleanup(basis.unlock)
454
        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.
455
            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.
456
        self.assertNotEqual(rev_id, rev_id2)
457
2825.5.2 by Robert Collins
Review feedback, and fix pointless commits with nested trees to raise PointlessCommit appropriately.
458
    def test_nested_pointless_commits_are_pointless(self):
459
        tree = self.make_branch_and_tree('.')
460
        if not tree.supports_tree_reference():
461
            # inapplicable test.
462
            return
463
        subtree = self.make_branch_and_tree('subtree')
464
        tree.add(['subtree'])
465
        # record the reference.
466
        rev_id = tree.commit('added reference')
467
        child_revid = subtree.last_revision()
468
        # now do a no-op commit with allow_pointless=False
469
        self.assertRaises(errors.PointlessCommit, tree.commit, '',
470
            allow_pointless=False)
471
        self.assertEqual(child_revid, subtree.last_revision())
472
        self.assertEqual(rev_id, tree.last_revision())
473
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.
474
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
475
class TestCommitProgress(TestCaseWithWorkingTree):
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
476
    
477
    def restoreDefaults(self):
478
        ui.ui_factory = self.old_ui_factory
479
480
    def test_commit_progress_steps(self):
481
        # during commit we one progress update for every entry in the 
482
        # inventory, and then one for the inventory, and one for the
483
        # inventory, and one for the revision insertions.
484
        # first we need a test commit to do. Lets setup a branch with 
485
        # 3 files, and alter one in a selected-file commit. This exercises
486
        # a number of cases quickly. We should also test things like 
487
        # selective commits which excludes newly added files.
488
        tree = self.make_branch_and_tree('.')
489
        self.build_tree(['a', 'b', 'c'])
490
        tree.add(['a', 'b', 'c'])
491
        tree.commit('first post')
492
        f = file('b', 'wt')
493
        f.write('new content')
494
        f.close()
495
        # set a progress bar that captures the calls so we can see what is 
496
        # emitted
497
        self.old_ui_factory = ui.ui_factory
498
        self.addCleanup(self.restoreDefaults)
499
        factory = CapturingUIFactory()
500
        ui.ui_factory = factory
501
        # TODO RBC 20060421 it would be nice to merge the reporter output
502
        # into the factory for this test - just make the test ui factory
503
        # pun as a reporter. Then we can check the ordering is right.
504
        tree.commit('second post', specific_files=['b'])
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
505
        # 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.
506
        self.assertEqual(
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
507
            [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
508
             ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
509
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
510
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
511
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
512
             ('update', 5, 5, 'Running post_commit hooks - Stage')],
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
513
            factory._calls
514
           )
2553.1.2 by Robert Collins
Show hook names during commit.
515
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
516
    def test_commit_progress_shows_post_hook_names(self):
2553.1.2 by Robert Collins
Show hook names during commit.
517
        tree = self.make_branch_and_tree('.')
518
        # set a progress bar that captures the calls so we can see what is 
519
        # emitted
520
        self.old_ui_factory = ui.ui_factory
521
        self.addCleanup(self.restoreDefaults)
522
        factory = CapturingUIFactory()
523
        ui.ui_factory = factory
524
        def a_hook(_, _2, _3, _4, _5, _6):
525
            pass
3256.2.19 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.workingtree_implementations.test_commit.
526
        branch.Branch.hooks.install_named_hook('post_commit', a_hook,
527
                                               'hook name')
2553.1.2 by Robert Collins
Show hook names during commit.
528
        tree.commit('first post')
529
        self.assertEqual(
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
530
            [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
531
             ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
532
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
533
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
534
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
535
             ('update', 5, 5, 'Running post_commit hooks - Stage'),
536
             ('update', 5, 5, 'Running post_commit hooks [hook name] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
537
             ],
538
            factory._calls
539
           )
540
541
    def test_commit_progress_shows_pre_hook_names(self):
542
        tree = self.make_branch_and_tree('.')
543
        # set a progress bar that captures the calls so we can see what is 
544
        # emitted
545
        self.old_ui_factory = ui.ui_factory
546
        self.addCleanup(self.restoreDefaults)
547
        factory = CapturingUIFactory()
548
        ui.ui_factory = factory
2659.3.3 by NamNguyen
Changed ``pre_commit`` hook signature.
549
        def a_hook(_, _2, _3, _4, _5, _6, _7, _8):
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
550
            pass
3256.2.19 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.workingtree_implementations.test_commit.
551
        branch.Branch.hooks.install_named_hook('pre_commit', a_hook,
552
                                               'hook name')
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
553
        tree.commit('first post')
554
        self.assertEqual(
555
            [('update', 1, 5, 'Collecting changes [Directory 0] - Stage'),
556
             ('update', 1, 5, 'Collecting changes [Directory 1] - Stage'),
557
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
558
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
559
             ('update', 3, 5, 'Running pre_commit hooks [hook name] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
560
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
561
             ('update', 5, 5, 'Running post_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
562
             ],
563
            factory._calls
564
           )
3335.1.3 by Jelmer Vernooij
Add tests for start_commit hook.
565
566
    def test_start_commit_hook(self):
567
        """Make sure a start commit hook can modify the tree that is 
568
        committed."""
569
        def start_commit_hook_adds_file(tree):
570
            open(tree.abspath("newfile"), 'w').write("data")
571
            tree.add(["newfile"])
572
        def restoreDefaults():
573
            mutabletree.MutableTree.hooks['start_commit'] = []
574
        self.addCleanup(restoreDefaults)
575
        tree = self.make_branch_and_tree('.')
3256.2.26 by Daniel Watkins
Updated tests to use install_named_hook.
576
        mutabletree.MutableTree.hooks.install_named_hook(
577
            'start_commit',
578
            start_commit_hook_adds_file,
579
            None)
3335.1.3 by Jelmer Vernooij
Add tests for start_commit hook.
580
        revid = tree.commit('first post')
581
        committed_tree = tree.basis_tree()
582
        self.assertTrue(committed_tree.has_filename("newfile"))