~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
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
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
    )
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
33
from bzrlib.errors import (NotBranchError, NotVersionedError,
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
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
4523.1.4 by Martin Pool
Rename remaining *_implementations tests
37
from bzrlib.tests.per_workingtree import TestCaseWithWorkingTree
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
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):
4463.1.1 by Martin Pool
Update docstrings for recent progress changes
52
        """See progress.ProgressTask.clear()."""
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
53
54
    def clear_term(self):
4463.1.1 by Martin Pool
Update docstrings for recent progress changes
55
        """See progress.ProgressTask.clear_term()."""
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
56
57
    def finished(self):
4463.1.1 by Martin Pool
Update docstrings for recent progress changes
58
        """See progress.ProgressTask.finished()."""
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
59
        self.depth -= 1
60
61
    def note(self, fmt_string, *args, **kwargs):
4463.1.1 by Martin Pool
Update docstrings for recent progress changes
62
        """See progress.ProgressTask.note()."""
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
63
64
    def progress_bar(self):
65
        return self
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
66
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
67
    def nested_progress_bar(self):
68
        self.depth += 1
69
        return self
70
71
    def update(self, message, count=None, total=None):
4463.1.1 by Martin Pool
Update docstrings for recent progress changes
72
        """See progress.ProgressTask.update()."""
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
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
3602.1.2 by Robert Collins
Review feedback : test for PointlessCommit and that the example given in the help (excluding a subtree of a specified tree) does in fact work.
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
3602.1.1 by Robert Collins
Add support for -x or --exclude to bzr commit, fixing bug 3117. (Robert Collins)
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'])
3602.1.4 by Robert Collins
Andrew's review feedback.
224
        # If b was excluded it will still be 'added' in status.
3602.1.1 by Robert Collins
Add support for -x or --exclude to bzr commit, fixing bug 3117. (Robert Collins)
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
3602.1.2 by Robert Collins
Review feedback : test for PointlessCommit and that the example given in the help (excluding a subtree of a specified tree) does in fact work.
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(['.'])
3603.1.1 by Robert Collins
Further tweaks to tests and comments in the commit excludes feature.
236
        tree.commit('test', specific_files=['a'], exclude=['a/b'])
3602.1.4 by Robert Collins
Andrew's review feedback.
237
        # If a/b was excluded it will still be 'added' in status.
3602.1.2 by Robert Collins
Review feedback : test for PointlessCommit and that the example given in the help (excluding a subtree of a specified tree) does in fact work.
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
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
244
    def test_commit_sets_last_revision(self):
245
        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.
246
        committed_id = tree.commit('foo', rev_id='foo')
1908.7.6 by Robert Collins
Deprecate WorkingTree.last_revision.
247
        self.assertEqual(['foo'], tree.get_parent_ids())
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
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('.')
2825.5.2 by Robert Collins
Review feedback, and fix pointless commits with nested trees to raise PointlessCommit appropriately.
253
        committed_id = tree.commit('message')
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
254
        self.assertTrue(tree.branch.repository.has_revision(committed_id))
255
        self.assertNotEqual(None, committed_id)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
256
257
    def test_commit_local_unbound(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
258
        # using the library api to do a local commit on unbound branches is
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
259
        # also an error
260
        tree = self.make_branch_and_tree('tree')
261
        self.assertRaises(errors.LocalRequiresBoundBranch,
262
                          tree.commit,
263
                          'foo',
264
                          local=True)
2374.2.1 by John Arbash Meinel
(broken) merge a test case showing that commiting a merge of a kind change fails.
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
4536.3.1 by Robert Collins
Defer doing unversioning of file ids during commit to after completing branch operations. (Robert Collins, bug 282402)
283
    def test_commit_aborted_does_not_apply_automatic_changes_bug_282402(self):
284
        wt = self.make_branch_and_tree('.')
285
        wt.add(['a'], ['a-id'], ['file'])
286
        def fail_message(obj):
287
            raise errors.BzrCommandError("empty commit message")
288
        self.assertRaises(errors.BzrCommandError, wt.commit,
289
            message_callback=fail_message)
290
        self.assertEqual('a', wt.id2path('a-id'))
291
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
292
    def test_local_commit_ignores_master(self):
293
        # a --local commit does not require access to the master branch
294
        # at all, or even for it to exist.
295
        # we test this by setting up a bound branch and then corrupting
296
        # the master.
297
        master = self.make_branch('master')
298
        tree = self.make_branch_and_tree('tree')
299
        try:
300
            tree.branch.bind(master)
301
        except errors.UpgradeRequired:
302
            # older format.
303
            return
1955.3.14 by John Arbash Meinel
Correctly fix the workingtree put() test fixes
304
        master.bzrdir.transport.put_bytes('branch-format', 'garbage')
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
305
        del master
306
        # check its corrupted.
307
        self.assertRaises(errors.UnknownFormatError,
308
                          bzrdir.BzrDir.open,
309
                          'master')
310
        tree.commit('foo', rev_id='foo', local=True)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
311
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
312
    def test_local_commit_does_not_push_to_master(self):
313
        # a --local commit does not require access to the master branch
314
        # at all, or even for it to exist.
315
        # we test that even when its available it does not push to it.
316
        master = self.make_branch('master')
317
        tree = self.make_branch_and_tree('tree')
318
        try:
319
            tree.branch.bind(master)
320
        except errors.UpgradeRequired:
321
            # older format.
322
            return
323
        tree.commit('foo', rev_id='foo', local=True)
324
        self.failIf(master.repository.has_revision('foo'))
2598.5.7 by Aaron Bentley
Updates from review
325
        self.assertEqual(_mod_revision.NULL_REVISION,
326
                         (_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.
327
328
    def test_record_initial_ghost(self):
329
        """The working tree needs to record ghosts during commit."""
330
        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.
331
        wt.set_parent_ids(['non:existent@rev--ision--0--2'],
332
            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.
333
        rev_id = wt.commit('commit against a ghost first parent.')
334
        rev = wt.branch.repository.get_revision(rev_id)
335
        self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
336
        # parent_sha1s is not populated now, WTF. rbc 20051003
337
        self.assertEqual(len(rev.parent_sha1s), 0)
338
339
    def test_record_two_ghosts(self):
340
        """The working tree should preserve all the parents during commit."""
341
        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.
342
        wt.set_parent_ids([
343
                'foo@azkhazan-123123-abcabc',
344
                'wibble@fofof--20050401--1928390812',
345
            ],
346
            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.
347
        rev_id = wt.commit("commit from ghost base with one merge")
348
        # the revision should have been committed with two parents
349
        rev = wt.branch.repository.get_revision(rev_id)
350
        self.assertEqual(['foo@azkhazan-123123-abcabc',
351
            'wibble@fofof--20050401--1928390812'],
352
            rev.parent_ids)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
353
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.
354
    def test_commit_deleted_subtree_and_files_updates_workingtree(self):
355
        """The working trees inventory may be adjusted by commit."""
356
        wt = self.make_branch_and_tree('.')
357
        wt.lock_write()
358
        self.build_tree(['a', 'b/', 'b/c', 'd'])
359
        wt.add(['a', 'b', 'b/c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
360
        this_dir = self.get_transport()
361
        this_dir.delete_tree('b')
362
        this_dir.delete('d')
363
        # now we have a tree with a through d in the inventory, but only
364
        # a present on disk. After commit b-id, c-id and d-id should be
365
        # missing from the inventory, within the same tree transaction.
366
        wt.commit('commit stuff')
367
        self.assertTrue(wt.has_id('a-id'))
368
        self.assertFalse(wt.has_or_had_id('b-id'))
369
        self.assertFalse(wt.has_or_had_id('c-id'))
370
        self.assertFalse(wt.has_or_had_id('d-id'))
371
        self.assertTrue(wt.has_filename('a'))
372
        self.assertFalse(wt.has_filename('b'))
373
        self.assertFalse(wt.has_filename('b/c'))
374
        self.assertFalse(wt.has_filename('d'))
375
        wt.unlock()
376
        # the changes should have persisted to disk - reopen the workingtree
377
        # to be sure.
378
        wt = wt.bzrdir.open_workingtree()
379
        wt.lock_read()
380
        self.assertTrue(wt.has_id('a-id'))
381
        self.assertFalse(wt.has_or_had_id('b-id'))
382
        self.assertFalse(wt.has_or_had_id('c-id'))
383
        self.assertFalse(wt.has_or_had_id('d-id'))
384
        self.assertTrue(wt.has_filename('a'))
385
        self.assertFalse(wt.has_filename('b'))
386
        self.assertFalse(wt.has_filename('b/c'))
387
        self.assertFalse(wt.has_filename('d'))
388
        wt.unlock()
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
389
2363.2.2 by John Arbash Meinel
Simplify the test even further....
390
    def test_commit_deleted_subtree_with_removed(self):
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
391
        wt = self.make_branch_and_tree('.')
392
        self.build_tree(['a', 'b/', 'b/c', 'd'])
393
        wt.add(['a', 'b', 'b/c'], ['a-id', 'b-id', 'c-id'])
394
        wt.commit('first')
2363.2.2 by John Arbash Meinel
Simplify the test even further....
395
        wt.remove('b/c')
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
396
        this_dir = self.get_transport()
397
        this_dir.delete_tree('b')
398
        wt.lock_write()
399
        wt.commit('commit deleted rename')
400
        self.assertTrue(wt.has_id('a-id'))
401
        self.assertFalse(wt.has_or_had_id('b-id'))
402
        self.assertFalse(wt.has_or_had_id('c-id'))
403
        self.assertTrue(wt.has_filename('a'))
404
        self.assertFalse(wt.has_filename('b'))
405
        self.assertFalse(wt.has_filename('b/c'))
406
        wt.unlock()
407
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
408
    def test_commit_move_new(self):
409
        wt = self.make_branch_and_tree('first')
410
        wt.commit('first')
411
        wt2 = wt.bzrdir.sprout('second').open_workingtree()
412
        self.build_tree(['second/name1'])
413
        wt2.add('name1', 'name1-id')
414
        wt2.commit('second')
415
        wt.merge_from_branch(wt2.branch)
416
        wt.rename_one('name1', 'name2')
417
        wt.commit('third')
418
        wt.path2id('name1-id')
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
419
420
    def test_nested_commit(self):
421
        """Commit in multiply-nested trees"""
422
        tree = self.make_branch_and_tree('.')
423
        if not tree.supports_tree_reference():
424
            # inapplicable test.
425
            return
426
        subtree = self.make_branch_and_tree('subtree')
427
        subsubtree = self.make_branch_and_tree('subtree/subtree')
428
        subtree.add(['subtree'])
429
        tree.add(['subtree'])
430
        # use allow_pointless=False to ensure that the deepest tree, which
431
        # has no commits made to it, does not get a pointless commit.
432
        rev_id = tree.commit('added reference', allow_pointless=False)
433
        tree.lock_read()
434
        self.addCleanup(tree.unlock)
435
        # 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
436
        self.assertEqual('null:', subsubtree.last_revision())
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
437
        # the intermediate tree should have committed a pointer to the current
438
        # subtree revision.
439
        sub_basis = subtree.basis_tree()
440
        sub_basis.lock_read()
441
        self.addCleanup(sub_basis.unlock)
442
        self.assertEqual(subsubtree.last_revision(),
2255.2.227 by Robert Collins
Make all test_commit tests pass.
443
            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.
444
        # the intermediate tree has changed, so should have had a commit
445
        # take place.
446
        self.assertNotEqual(None, subtree.last_revision())
447
        # the outer tree should have committed a pointer to the current
448
        # subtree revision.
449
        basis = tree.basis_tree()
450
        basis.lock_read()
451
        self.addCleanup(basis.unlock)
452
        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.
453
            basis.get_reference_revision(basis.path2id('subtree')))
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
454
        # the outer tree must have have changed too.
455
        self.assertNotEqual(None, rev_id)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
456
2255.2.220 by Robert Collins
Fix failing detection of changes restricted to subtrees causing spurious pointless commit errors.
457
    def test_nested_commit_second_commit_detects_changes(self):
458
        """Commit with a nested tree picks up the correct child revid."""
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
        self.build_tree(['subtree/file'])
466
        subtree.add(['file'], ['file-id'])
467
        rev_id = tree.commit('added reference', allow_pointless=False)
4183.5.5 by Robert Collins
Enable record_iter_changes for cases where it can work.
468
        tree.get_reference_revision(tree.path2id('subtree'))
2255.2.220 by Robert Collins
Fix failing detection of changes restricted to subtrees causing spurious pointless commit errors.
469
        child_revid = subtree.last_revision()
470
        # now change the child tree
471
        self.build_tree_contents([('subtree/file', 'new-content')])
472
        # and commit in the parent should commit the child and grab its revid,
473
        # we test with allow_pointless=False here so that we are simulating
474
        # what users will see.
475
        rev_id2 = tree.commit('changed subtree only', allow_pointless=False)
476
        # the child tree has changed, so should have had a commit
477
        # take place.
478
        self.assertNotEqual(None, subtree.last_revision())
479
        self.assertNotEqual(child_revid, subtree.last_revision())
480
        # the outer tree should have committed a pointer to the current
481
        # subtree revision.
482
        basis = tree.basis_tree()
483
        basis.lock_read()
484
        self.addCleanup(basis.unlock)
485
        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.
486
            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.
487
        self.assertNotEqual(rev_id, rev_id2)
488
2825.5.2 by Robert Collins
Review feedback, and fix pointless commits with nested trees to raise PointlessCommit appropriately.
489
    def test_nested_pointless_commits_are_pointless(self):
490
        tree = self.make_branch_and_tree('.')
491
        if not tree.supports_tree_reference():
492
            # inapplicable test.
493
            return
494
        subtree = self.make_branch_and_tree('subtree')
495
        tree.add(['subtree'])
496
        # record the reference.
497
        rev_id = tree.commit('added reference')
498
        child_revid = subtree.last_revision()
499
        # now do a no-op commit with allow_pointless=False
500
        self.assertRaises(errors.PointlessCommit, tree.commit, '',
501
            allow_pointless=False)
502
        self.assertEqual(child_revid, subtree.last_revision())
503
        self.assertEqual(rev_id, tree.last_revision())
504
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.
505
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
506
class TestCommitProgress(TestCaseWithWorkingTree):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
507
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
508
    def restoreDefaults(self):
509
        ui.ui_factory = self.old_ui_factory
510
511
    def test_commit_progress_steps(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
512
        # during commit we one progress update for every entry in the
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
513
        # inventory, and then one for the inventory, and one for the
514
        # inventory, and one for the revision insertions.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
515
        # first we need a test commit to do. Lets setup a branch with
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
516
        # 3 files, and alter one in a selected-file commit. This exercises
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
517
        # a number of cases quickly. We should also test things like
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
518
        # selective commits which excludes newly added files.
519
        tree = self.make_branch_and_tree('.')
520
        self.build_tree(['a', 'b', 'c'])
521
        tree.add(['a', 'b', 'c'])
522
        tree.commit('first post')
523
        f = file('b', 'wt')
524
        f.write('new content')
525
        f.close()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
526
        # set a progress bar that captures the calls so we can see what is
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
527
        # emitted
528
        self.old_ui_factory = ui.ui_factory
529
        self.addCleanup(self.restoreDefaults)
530
        factory = CapturingUIFactory()
531
        ui.ui_factory = factory
532
        # TODO RBC 20060421 it would be nice to merge the reporter output
533
        # into the factory for this test - just make the test ui factory
534
        # pun as a reporter. Then we can check the ordering is right.
535
        tree.commit('second post', specific_files=['b'])
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
536
        # 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.
537
        self.assertEqual(
4183.5.5 by Robert Collins
Enable record_iter_changes for cases where it can work.
538
            [('update', 1, 5, 'Collecting changes [0] - Stage'),
539
             ('update', 1, 5, 'Collecting changes [1] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
540
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
541
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
542
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
543
             ('update', 5, 5, 'Running post_commit hooks - Stage')],
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
544
            factory._calls
545
           )
2553.1.2 by Robert Collins
Show hook names during commit.
546
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
547
    def test_commit_progress_shows_post_hook_names(self):
2553.1.2 by Robert Collins
Show hook names during commit.
548
        tree = self.make_branch_and_tree('.')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
549
        # set a progress bar that captures the calls so we can see what is
2553.1.2 by Robert Collins
Show hook names during commit.
550
        # emitted
551
        self.old_ui_factory = ui.ui_factory
552
        self.addCleanup(self.restoreDefaults)
553
        factory = CapturingUIFactory()
554
        ui.ui_factory = factory
555
        def a_hook(_, _2, _3, _4, _5, _6):
556
            pass
3256.2.19 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.workingtree_implementations.test_commit.
557
        branch.Branch.hooks.install_named_hook('post_commit', a_hook,
558
                                               'hook name')
2553.1.2 by Robert Collins
Show hook names during commit.
559
        tree.commit('first post')
560
        self.assertEqual(
4183.5.5 by Robert Collins
Enable record_iter_changes for cases where it can work.
561
            [('update', 1, 5, 'Collecting changes [0] - Stage'),
562
             ('update', 1, 5, 'Collecting changes [1] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
563
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
564
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
565
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
566
             ('update', 5, 5, 'Running post_commit hooks - Stage'),
567
             ('update', 5, 5, 'Running post_commit hooks [hook name] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
568
             ],
569
            factory._calls
570
           )
571
572
    def test_commit_progress_shows_pre_hook_names(self):
573
        tree = self.make_branch_and_tree('.')
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
574
        # set a progress bar that captures the calls so we can see what is
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
575
        # emitted
576
        self.old_ui_factory = ui.ui_factory
577
        self.addCleanup(self.restoreDefaults)
578
        factory = CapturingUIFactory()
579
        ui.ui_factory = factory
2659.3.3 by NamNguyen
Changed ``pre_commit`` hook signature.
580
        def a_hook(_, _2, _3, _4, _5, _6, _7, _8):
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
581
            pass
3256.2.19 by Daniel Watkins
Updated uses of Hooks.install_hook to Hooks.install_named_hook in tests.workingtree_implementations.test_commit.
582
        branch.Branch.hooks.install_named_hook('pre_commit', a_hook,
583
                                               'hook name')
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
584
        tree.commit('first post')
585
        self.assertEqual(
4183.5.5 by Robert Collins
Enable record_iter_changes for cases where it can work.
586
            [('update', 1, 5, 'Collecting changes [0] - Stage'),
587
             ('update', 1, 5, 'Collecting changes [1] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
588
             ('update', 2, 5, 'Saving data locally - Stage'),
2659.3.9 by NamNguyen
branch.py:
589
             ('update', 3, 5, 'Running pre_commit hooks - Stage'),
590
             ('update', 3, 5, 'Running pre_commit hooks [hook name] - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
591
             ('update', 4, 5, 'Updating the working tree - Stage'),
2659.3.9 by NamNguyen
branch.py:
592
             ('update', 5, 5, 'Running post_commit hooks - Stage'),
2659.3.1 by NamNguyen
``Branch.hooks`` now supports ``pre_commit`` hook.
593
             ],
594
            factory._calls
595
           )
3335.1.3 by Jelmer Vernooij
Add tests for start_commit hook.
596
597
    def test_start_commit_hook(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
598
        """Make sure a start commit hook can modify the tree that is
3335.1.3 by Jelmer Vernooij
Add tests for start_commit hook.
599
        committed."""
600
        def start_commit_hook_adds_file(tree):
601
            open(tree.abspath("newfile"), 'w').write("data")
602
            tree.add(["newfile"])
603
        def restoreDefaults():
604
            mutabletree.MutableTree.hooks['start_commit'] = []
605
        self.addCleanup(restoreDefaults)
606
        tree = self.make_branch_and_tree('.')
3256.2.26 by Daniel Watkins
Updated tests to use install_named_hook.
607
        mutabletree.MutableTree.hooks.install_named_hook(
608
            'start_commit',
609
            start_commit_hook_adds_file,
610
            None)
3335.1.3 by Jelmer Vernooij
Add tests for start_commit hook.
611
        revid = tree.commit('first post')
612
        committed_tree = tree.basis_tree()
613
        self.assertTrue(committed_tree.has_filename("newfile"))