/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
1773.4.1 by Martin Pool
Add pyflakes makefile target; fix many warnings
21
from bzrlib import branch, bzrdir, errors, ui, workingtree
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
22
from bzrlib.errors import (NotBranchError, NotVersionedError, 
23
                           UnsupportedOperation)
24
from bzrlib.osutils import pathjoin, getcwd, has_symlinks
25
from bzrlib.tests import TestSkipped, TestCase
26
from bzrlib.tests.workingtree_implementations import TestCaseWithWorkingTree
27
from bzrlib.trace import mutter
28
from bzrlib.workingtree import (TreeEntry, TreeDirectory, TreeFile, TreeLink,
29
                                WorkingTree)
30
31
32
class CapturingUIFactory(ui.UIFactory):
33
    """A UI Factory for testing - capture the updates made through it."""
34
35
    def __init__(self):
36
        super(CapturingUIFactory, self).__init__()
37
        self._calls = []
38
        self.depth = 0
39
40
    def clear(self):
41
        """See progress.ProgressBar.clear()."""
42
43
    def clear_term(self):
44
        """See progress.ProgressBar.clear_term()."""
45
46
    def finished(self):
47
        """See progress.ProgressBar.finished()."""
48
        self.depth -= 1
49
50
    def note(self, fmt_string, *args, **kwargs):
51
        """See progress.ProgressBar.note()."""
52
53
    def progress_bar(self):
54
        return self
55
    
56
    def nested_progress_bar(self):
57
        self.depth += 1
58
        return self
59
60
    def update(self, message, count=None, total=None):
61
        """See progress.ProgressBar.update()."""
62
        if self.depth == 1:
63
            self._calls.append(("update", count, total))
64
65
66
class TestCapturingUI(TestCase):
67
68
    def test_nested_ignore_depth_beyond_one(self):
69
        # we only want to capture the first level out progress, not
70
        # want sub-components might do. So we have nested bars ignored.
71
        factory = CapturingUIFactory()
72
        pb1 = factory.nested_progress_bar()
73
        pb1.update('foo', 0, 1)
74
        pb2 = factory.nested_progress_bar()
75
        pb2.update('foo', 0, 1)
76
        pb2.finished()
77
        pb1.finished()
78
        self.assertEqual([("update", 0, 1)], factory._calls)
79
80
81
class TestCommit(TestCaseWithWorkingTree):
82
83
    def test_commit_sets_last_revision(self):
84
        tree = self.make_branch_and_tree('tree')
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
85
        committed_id = tree.commit('foo', rev_id='foo', allow_pointless=True)
1908.7.6 by Robert Collins
Deprecate WorkingTree.last_revision.
86
        self.assertEqual(['foo'], tree.get_parent_ids())
1773.1.1 by Robert Collins
Teach WorkingTree.commit to return the committed revision id.
87
        # the commit should have returned the same id we asked for.
88
        self.assertEqual('foo', committed_id)
89
90
    def test_commit_returns_revision_id(self):
91
        tree = self.make_branch_and_tree('.')
92
        committed_id = tree.commit('message', allow_pointless=True)
93
        self.assertTrue(tree.branch.repository.has_revision(committed_id))
94
        self.assertNotEqual(None, committed_id)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
95
96
    def test_commit_local_unbound(self):
97
        # using the library api to do a local commit on unbound branches is 
98
        # also an error
99
        tree = self.make_branch_and_tree('tree')
100
        self.assertRaises(errors.LocalRequiresBoundBranch,
101
                          tree.commit,
102
                          'foo',
103
                          local=True)
104
 
105
    def test_local_commit_ignores_master(self):
106
        # a --local commit does not require access to the master branch
107
        # at all, or even for it to exist.
108
        # we test this by setting up a bound branch and then corrupting
109
        # the master.
110
        master = self.make_branch('master')
111
        tree = self.make_branch_and_tree('tree')
112
        try:
113
            tree.branch.bind(master)
114
        except errors.UpgradeRequired:
115
            # older format.
116
            return
1955.3.14 by John Arbash Meinel
Correctly fix the workingtree put() test fixes
117
        master.bzrdir.transport.put_bytes('branch-format', 'garbage')
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
118
        del master
119
        # check its corrupted.
120
        self.assertRaises(errors.UnknownFormatError,
121
                          bzrdir.BzrDir.open,
122
                          'master')
123
        tree.commit('foo', rev_id='foo', local=True)
124
 
125
    def test_local_commit_does_not_push_to_master(self):
126
        # a --local commit does not require access to the master branch
127
        # at all, or even for it to exist.
128
        # we test that even when its available it does not push to it.
129
        master = self.make_branch('master')
130
        tree = self.make_branch_and_tree('tree')
131
        try:
132
            tree.branch.bind(master)
133
        except errors.UpgradeRequired:
134
            # older format.
135
            return
136
        tree.commit('foo', rev_id='foo', local=True)
137
        self.failIf(master.repository.has_revision('foo'))
138
        self.assertEqual(None, 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.
139
140
    def test_record_initial_ghost(self):
141
        """The working tree needs to record ghosts during commit."""
142
        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.
143
        wt.set_parent_ids(['non:existent@rev--ision--0--2'],
144
            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.
145
        rev_id = wt.commit('commit against a ghost first parent.')
146
        rev = wt.branch.repository.get_revision(rev_id)
147
        self.assertEqual(rev.parent_ids, ['non:existent@rev--ision--0--2'])
148
        # parent_sha1s is not populated now, WTF. rbc 20051003
149
        self.assertEqual(len(rev.parent_sha1s), 0)
150
151
    def test_record_two_ghosts(self):
152
        """The working tree should preserve all the parents during commit."""
153
        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.
154
        wt.set_parent_ids([
155
                'foo@azkhazan-123123-abcabc',
156
                'wibble@fofof--20050401--1928390812',
157
            ],
158
            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.
159
        rev_id = wt.commit("commit from ghost base with one merge")
160
        # the revision should have been committed with two parents
161
        rev = wt.branch.repository.get_revision(rev_id)
162
        self.assertEqual(['foo@azkhazan-123123-abcabc',
163
            'wibble@fofof--20050401--1928390812'],
164
            rev.parent_ids)
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
165
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.
166
    def test_commit_deleted_subtree_and_files_updates_workingtree(self):
167
        """The working trees inventory may be adjusted by commit."""
168
        wt = self.make_branch_and_tree('.')
169
        wt.lock_write()
170
        self.build_tree(['a', 'b/', 'b/c', 'd'])
171
        wt.add(['a', 'b', 'b/c', 'd'], ['a-id', 'b-id', 'c-id', 'd-id'])
172
        this_dir = self.get_transport()
173
        this_dir.delete_tree('b')
174
        this_dir.delete('d')
175
        # now we have a tree with a through d in the inventory, but only
176
        # a present on disk. After commit b-id, c-id and d-id should be
177
        # missing from the inventory, within the same tree transaction.
178
        wt.commit('commit stuff')
179
        self.assertTrue(wt.has_id('a-id'))
180
        self.assertFalse(wt.has_or_had_id('b-id'))
181
        self.assertFalse(wt.has_or_had_id('c-id'))
182
        self.assertFalse(wt.has_or_had_id('d-id'))
183
        self.assertTrue(wt.has_filename('a'))
184
        self.assertFalse(wt.has_filename('b'))
185
        self.assertFalse(wt.has_filename('b/c'))
186
        self.assertFalse(wt.has_filename('d'))
187
        wt.unlock()
188
        # the changes should have persisted to disk - reopen the workingtree
189
        # to be sure.
190
        wt = wt.bzrdir.open_workingtree()
191
        wt.lock_read()
192
        self.assertTrue(wt.has_id('a-id'))
193
        self.assertFalse(wt.has_or_had_id('b-id'))
194
        self.assertFalse(wt.has_or_had_id('c-id'))
195
        self.assertFalse(wt.has_or_had_id('d-id'))
196
        self.assertTrue(wt.has_filename('a'))
197
        self.assertFalse(wt.has_filename('b'))
198
        self.assertFalse(wt.has_filename('b/c'))
199
        self.assertFalse(wt.has_filename('d'))
200
        wt.unlock()
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
201
2363.2.2 by John Arbash Meinel
Simplify the test even further....
202
    def test_commit_deleted_subtree_with_removed(self):
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
203
        wt = self.make_branch_and_tree('.')
204
        self.build_tree(['a', 'b/', 'b/c', 'd'])
205
        wt.add(['a', 'b', 'b/c'], ['a-id', 'b-id', 'c-id'])
206
        wt.commit('first')
2363.2.2 by John Arbash Meinel
Simplify the test even further....
207
        wt.remove('b/c')
2363.2.1 by John Arbash Meinel
(broken) Add a simplified test which exposes the bug.
208
        this_dir = self.get_transport()
209
        this_dir.delete_tree('b')
210
        wt.lock_write()
211
        wt.commit('commit deleted rename')
212
        self.assertTrue(wt.has_id('a-id'))
213
        self.assertFalse(wt.has_or_had_id('b-id'))
214
        self.assertFalse(wt.has_or_had_id('c-id'))
215
        self.assertTrue(wt.has_filename('a'))
216
        self.assertFalse(wt.has_filename('b'))
217
        self.assertFalse(wt.has_filename('b/c'))
218
        wt.unlock()
219
1731.2.4 by Aaron Bentley
Ensure subsume works with Knit2 repos
220
    def test_commit_move_new(self):
221
        wt = self.make_branch_and_tree('first')
222
        wt.commit('first')
223
        wt2 = wt.bzrdir.sprout('second').open_workingtree()
224
        self.build_tree(['second/name1'])
225
        wt2.add('name1', 'name1-id')
226
        wt2.commit('second')
227
        wt.merge_from_branch(wt2.branch)
228
        wt.rename_one('name1', 'name2')
229
        wt.commit('third')
230
        wt.path2id('name1-id')
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
231
232
    def test_nested_commit(self):
233
        """Commit in multiply-nested trees"""
234
        tree = self.make_branch_and_tree('.')
235
        if not tree.supports_tree_reference():
236
            # inapplicable test.
237
            return
238
        subtree = self.make_branch_and_tree('subtree')
239
        subsubtree = self.make_branch_and_tree('subtree/subtree')
240
        subtree.add(['subtree'])
241
        tree.add(['subtree'])
242
        # use allow_pointless=False to ensure that the deepest tree, which
243
        # has no commits made to it, does not get a pointless commit.
244
        rev_id = tree.commit('added reference', allow_pointless=False)
245
        tree.lock_read()
246
        self.addCleanup(tree.unlock)
247
        # the deepest subtree has not changed, so no commit should take place.
248
        self.assertEqual(None, subsubtree.last_revision())
249
        # the intermediate tree should have committed a pointer to the current
250
        # subtree revision.
251
        sub_basis = subtree.basis_tree()
252
        sub_basis.lock_read()
253
        self.addCleanup(sub_basis.unlock)
254
        self.assertEqual(subsubtree.last_revision(),
2255.2.227 by Robert Collins
Make all test_commit tests pass.
255
            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.
256
        # the intermediate tree has changed, so should have had a commit
257
        # take place.
258
        self.assertNotEqual(None, subtree.last_revision())
259
        # the outer tree should have committed a pointer to the current
260
        # subtree revision.
261
        basis = tree.basis_tree()
262
        basis.lock_read()
263
        self.addCleanup(basis.unlock)
264
        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.
265
            basis.get_reference_revision(basis.path2id('subtree')))
2255.2.218 by Robert Collins
Make the nested tree commit smoke test be more rigourous.
266
        # the outer tree must have have changed too.
267
        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.
268
        
2255.2.220 by Robert Collins
Fix failing detection of changes restricted to subtrees causing spurious pointless commit errors.
269
    def test_nested_commit_second_commit_detects_changes(self):
270
        """Commit with a nested tree picks up the correct child revid."""
271
        tree = self.make_branch_and_tree('.')
272
        if not tree.supports_tree_reference():
273
            # inapplicable test.
274
            return
275
        subtree = self.make_branch_and_tree('subtree')
276
        tree.add(['subtree'])
277
        self.build_tree(['subtree/file'])
278
        subtree.add(['file'], ['file-id'])
279
        rev_id = tree.commit('added reference', allow_pointless=False)
280
        child_revid = subtree.last_revision()
281
        # now change the child tree
282
        self.build_tree_contents([('subtree/file', 'new-content')])
283
        # and commit in the parent should commit the child and grab its revid,
284
        # we test with allow_pointless=False here so that we are simulating
285
        # what users will see.
286
        rev_id2 = tree.commit('changed subtree only', allow_pointless=False)
287
        # the child tree has changed, so should have had a commit
288
        # take place.
289
        self.assertNotEqual(None, subtree.last_revision())
290
        self.assertNotEqual(child_revid, subtree.last_revision())
291
        # the outer tree should have committed a pointer to the current
292
        # subtree revision.
293
        basis = tree.basis_tree()
294
        basis.lock_read()
295
        self.addCleanup(basis.unlock)
296
        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.
297
            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.
298
        self.assertNotEqual(rev_id, rev_id2)
299
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.
300
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
301
class TestCommitProgress(TestCaseWithWorkingTree):
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
302
    
303
    def restoreDefaults(self):
304
        ui.ui_factory = self.old_ui_factory
305
306
    def test_commit_progress_steps(self):
307
        # during commit we one progress update for every entry in the 
308
        # inventory, and then one for the inventory, and one for the
309
        # inventory, and one for the revision insertions.
310
        # first we need a test commit to do. Lets setup a branch with 
311
        # 3 files, and alter one in a selected-file commit. This exercises
312
        # a number of cases quickly. We should also test things like 
313
        # selective commits which excludes newly added files.
314
        tree = self.make_branch_and_tree('.')
315
        self.build_tree(['a', 'b', 'c'])
316
        tree.add(['a', 'b', 'c'])
317
        tree.commit('first post')
318
        f = file('b', 'wt')
319
        f.write('new content')
320
        f.close()
321
        # set a progress bar that captures the calls so we can see what is 
322
        # emitted
323
        self.old_ui_factory = ui.ui_factory
324
        self.addCleanup(self.restoreDefaults)
325
        factory = CapturingUIFactory()
326
        ui.ui_factory = factory
327
        # TODO RBC 20060421 it would be nice to merge the reporter output
328
        # into the factory for this test - just make the test ui factory
329
        # pun as a reporter. Then we can check the ordering is right.
330
        tree.commit('second post', specific_files=['b'])
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
331
        # 9 steps: 1 for rev, 2 for inventory, 1 for finishing. 2 for root
332
        # and 6 for inventory files.
333
        # 2 steps don't trigger an update, as 'a' and 'c' are not 
334
        # committed.
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
335
        self.assertEqual(
1740.3.10 by Jelmer Vernooij
Fix some minor issues pointed out by j-a-m.
336
            [("update", 0, 9),
337
             ("update", 1, 9),
338
             ("update", 2, 9),
339
             ("update", 3, 9),
340
             ("update", 4, 9),
341
             ("update", 5, 9),
342
             ("update", 6, 9),
343
             ("update", 7, 9)],
1666.1.19 by Robert Collins
Introduce a progress bar during commit.
344
            factory._calls
345
           )