~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/blackbox/test_push.py

merge merge tweaks from aaron, which includes latest .dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
2
 
#
3
 
# This program is free software; you can redistribute it and/or modify
4
 
# it under the terms of the GNU General Public License as published by
5
 
# the Free Software Foundation; either version 2 of the License, or
6
 
# (at your option) any later version.
7
 
#
8
 
# This program is distributed in the hope that it will be useful,
9
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
 
# GNU General Public License for more details.
12
 
#
13
 
# You should have received a copy of the GNU General Public License
14
 
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
 
 
17
 
 
18
 
"""Black-box tests for bzr push."""
19
 
 
20
 
import os
21
 
import re
22
 
 
23
 
from bzrlib import (
24
 
    errors,
25
 
    urlutils,
26
 
    )
27
 
from bzrlib.branch import Branch
28
 
from bzrlib.bzrdir import BzrDirMetaFormat1
29
 
from bzrlib.osutils import abspath
30
 
from bzrlib.repofmt.knitrepo import RepositoryFormatKnit1
31
 
from bzrlib.tests.blackbox import ExternalBase
32
 
from bzrlib.transport import register_transport, unregister_transport
33
 
from bzrlib.transport.memory import MemoryServer, MemoryTransport
34
 
from bzrlib.uncommit import uncommit
35
 
from bzrlib.urlutils import local_path_from_url
36
 
from bzrlib.workingtree import WorkingTree
37
 
 
38
 
 
39
 
class TestPush(ExternalBase):
40
 
 
41
 
    def test_push_remember(self):
42
 
        """Push changes from one branch to another and test push location."""
43
 
        transport = self.get_transport()
44
 
        tree_a = self.make_branch_and_tree('branch_a')
45
 
        branch_a = tree_a.branch
46
 
        self.build_tree(['branch_a/a'])
47
 
        tree_a.add('a')
48
 
        tree_a.commit('commit a')
49
 
        tree_b = branch_a.bzrdir.sprout('branch_b').open_workingtree()
50
 
        branch_b = tree_b.branch
51
 
        tree_c = branch_a.bzrdir.sprout('branch_c').open_workingtree()
52
 
        branch_c = tree_c.branch
53
 
        self.build_tree(['branch_a/b'])
54
 
        tree_a.add('b')
55
 
        tree_a.commit('commit b')
56
 
        self.build_tree(['branch_b/c'])
57
 
        tree_b.add('c')
58
 
        tree_b.commit('commit c')
59
 
        # initial push location must be empty
60
 
        self.assertEqual(None, branch_b.get_push_location())
61
 
 
62
 
        # test push for failure without push location set
63
 
        os.chdir('branch_a')
64
 
        out = self.run_bzr('push', retcode=3)
65
 
        self.assertEquals(out,
66
 
                ('','bzr: ERROR: No push location known or specified.\n'))
67
 
 
68
 
        # test not remembered if cannot actually push
69
 
        self.run_bzr('push ../path/which/doesnt/exist', retcode=3)
70
 
        out = self.run_bzr('push', retcode=3)
71
 
        self.assertEquals(
72
 
                ('', 'bzr: ERROR: No push location known or specified.\n'),
73
 
                out)
74
 
 
75
 
        # test implicit --remember when no push location set, push fails
76
 
        out = self.run_bzr('push ../branch_b', retcode=3)
77
 
        self.assertEquals(out,
78
 
                ('','bzr: ERROR: These branches have diverged.  '
79
 
                    'Try using "merge" and then "push".\n'))
80
 
        self.assertEquals(abspath(branch_a.get_push_location()),
81
 
                          abspath(branch_b.bzrdir.root_transport.base))
82
 
 
83
 
        # test implicit --remember after resolving previous failure
84
 
        uncommit(branch=branch_b, tree=tree_b)
85
 
        transport.delete('branch_b/c')
86
 
        out, err = self.run_bzr('push')
87
 
        path = branch_a.get_push_location()
88
 
        self.assertEquals(out,
89
 
                          'Using saved location: %s\n' 
90
 
                          'Pushed up to revision 2.\n'
91
 
                          % local_path_from_url(path))
92
 
        self.assertEqual(err,
93
 
                         'All changes applied successfully.\n')
94
 
        self.assertEqual(path,
95
 
                         branch_b.bzrdir.root_transport.base)
96
 
        # test explicit --remember
97
 
        self.run_bzr('push ../branch_c --remember')
98
 
        self.assertEquals(branch_a.get_push_location(),
99
 
                          branch_c.bzrdir.root_transport.base)
100
 
    
101
 
    def test_push_without_tree(self):
102
 
        # bzr push from a branch that does not have a checkout should work.
103
 
        b = self.make_branch('.')
104
 
        out, err = self.run_bzr('push pushed-location')
105
 
        self.assertEqual('', out)
106
 
        self.assertEqual('Created new branch.\n', err)
107
 
        b2 = Branch.open('pushed-location')
108
 
        self.assertEndsWith(b2.base, 'pushed-location/')
109
 
 
110
 
    def test_push_new_branch_revision_count(self):
111
 
        # bzr push of a branch with revisions to a new location 
112
 
        # should print the number of revisions equal to the length of the 
113
 
        # local branch.
114
 
        t = self.make_branch_and_tree('tree')
115
 
        self.build_tree(['tree/file'])
116
 
        t.add('file')
117
 
        t.commit('commit 1')
118
 
        os.chdir('tree')
119
 
        out, err = self.run_bzr('push pushed-to')
120
 
        os.chdir('..')
121
 
        self.assertEqual('', out)
122
 
        self.assertEqual('Created new branch.\n', err)
123
 
 
124
 
    def test_push_only_pushes_history(self):
125
 
        # Knit branches should only push the history for the current revision.
126
 
        format = BzrDirMetaFormat1()
127
 
        format.repository_format = RepositoryFormatKnit1()
128
 
        shared_repo = self.make_repository('repo', format=format, shared=True)
129
 
        shared_repo.set_make_working_trees(True)
130
 
 
131
 
        def make_shared_tree(path):
132
 
            shared_repo.bzrdir.root_transport.mkdir(path)
133
 
            shared_repo.bzrdir.create_branch_convenience('repo/' + path)
134
 
            return WorkingTree.open('repo/' + path)
135
 
        tree_a = make_shared_tree('a')
136
 
        self.build_tree(['repo/a/file'])
137
 
        tree_a.add('file')
138
 
        tree_a.commit('commit a-1', rev_id='a-1')
139
 
        f = open('repo/a/file', 'ab')
140
 
        f.write('more stuff\n')
141
 
        f.close()
142
 
        tree_a.commit('commit a-2', rev_id='a-2')
143
 
 
144
 
        tree_b = make_shared_tree('b')
145
 
        self.build_tree(['repo/b/file'])
146
 
        tree_b.add('file')
147
 
        tree_b.commit('commit b-1', rev_id='b-1')
148
 
 
149
 
        self.assertTrue(shared_repo.has_revision('a-1'))
150
 
        self.assertTrue(shared_repo.has_revision('a-2'))
151
 
        self.assertTrue(shared_repo.has_revision('b-1'))
152
 
 
153
 
        # Now that we have a repository with shared files, make sure
154
 
        # that things aren't copied out by a 'push'
155
 
        os.chdir('repo/b')
156
 
        self.run_bzr('push ../../push-b')
157
 
        pushed_tree = WorkingTree.open('../../push-b')
158
 
        pushed_repo = pushed_tree.branch.repository
159
 
        self.assertFalse(pushed_repo.has_revision('a-1'))
160
 
        self.assertFalse(pushed_repo.has_revision('a-2'))
161
 
        self.assertTrue(pushed_repo.has_revision('b-1'))
162
 
 
163
 
    def test_push_funky_id(self):
164
 
        t = self.make_branch_and_tree('tree')
165
 
        os.chdir('tree')
166
 
        self.build_tree(['filename'])
167
 
        t.add('filename', 'funky-chars<>%&;"\'')
168
 
        t.commit('commit filename')
169
 
        self.run_bzr('push ../new-tree')
170
 
 
171
 
    def test_push_dash_d(self):
172
 
        t = self.make_branch_and_tree('from')
173
 
        t.commit(allow_pointless=True,
174
 
                message='first commit')
175
 
        self.run_bzr('push -d from to-one')
176
 
        self.failUnlessExists('to-one')
177
 
        self.run_bzr('push -d %s %s' 
178
 
            % tuple(map(urlutils.local_path_to_url, ['from', 'to-two'])))
179
 
        self.failUnlessExists('to-two')
180
 
 
181
 
    def create_simple_tree(self):
182
 
        tree = self.make_branch_and_tree('tree')
183
 
        self.build_tree(['tree/a'])
184
 
        tree.add(['a'], ['a-id'])
185
 
        tree.commit('one', rev_id='r1')
186
 
        return tree
187
 
 
188
 
    def test_push_create_prefix(self):
189
 
        """'bzr push --create-prefix' will create leading directories."""
190
 
        tree = self.create_simple_tree()
191
 
 
192
 
        self.run_bzr_error(['Parent directory of ../new/tree does not exist'],
193
 
                           'push ../new/tree',
194
 
                           working_dir='tree')
195
 
        self.run_bzr('push ../new/tree --create-prefix',
196
 
                     working_dir='tree')
197
 
        new_tree = WorkingTree.open('new/tree')
198
 
        self.assertEqual(tree.last_revision(), new_tree.last_revision())
199
 
        self.failUnlessExists('new/tree/a')
200
 
 
201
 
    def test_push_use_existing(self):
202
 
        """'bzr push --use-existing-dir' can push into an existing dir.
203
 
 
204
 
        By default, 'bzr push' will not use an existing, non-versioned dir.
205
 
        """
206
 
        tree = self.create_simple_tree()
207
 
        self.build_tree(['target/'])
208
 
 
209
 
        self.run_bzr_error(['Target directory ../target already exists',
210
 
                            'Supply --use-existing-dir',
211
 
                           ],
212
 
                           'push ../target', working_dir='tree')
213
 
 
214
 
        self.run_bzr('push --use-existing-dir ../target',
215
 
                     working_dir='tree')
216
 
 
217
 
        new_tree = WorkingTree.open('target')
218
 
        self.assertEqual(tree.last_revision(), new_tree.last_revision())
219
 
        # The push should have created target/a
220
 
        self.failUnlessExists('target/a')
221
 
 
222
 
    def test_push_onto_repo(self):
223
 
        """We should be able to 'bzr push' into an existing bzrdir."""
224
 
        tree = self.create_simple_tree()
225
 
        repo = self.make_repository('repo', shared=True)
226
 
 
227
 
        self.run_bzr('push ../repo',
228
 
                     working_dir='tree')
229
 
 
230
 
        # Pushing onto an existing bzrdir will create a repository and
231
 
        # branch as needed, but will only create a working tree if there was
232
 
        # no BzrDir before.
233
 
        self.assertRaises(errors.NoWorkingTree, WorkingTree.open, 'repo')
234
 
        new_branch = Branch.open('repo')
235
 
        self.assertEqual(tree.last_revision(), new_branch.last_revision())
236
 
 
237
 
    def test_push_onto_just_bzrdir(self):
238
 
        """We don't handle when the target is just a bzrdir.
239
 
 
240
 
        Because you shouldn't be able to create *just* a bzrdir in the wild.
241
 
        """
242
 
        # TODO: jam 20070109 Maybe it would be better to create the repository
243
 
        #       if at this point
244
 
        tree = self.create_simple_tree()
245
 
        a_bzrdir = self.make_bzrdir('dir')
246
 
 
247
 
        self.run_bzr_error(['At ../dir you have a valid .bzr control'],
248
 
                'push ../dir',
249
 
                working_dir='tree')
250
 
 
251
 
    def test_push_with_revisionspec(self):
252
 
        """We should be able to push a revision older than the tip."""
253
 
        tree_from = self.make_branch_and_tree('from')
254
 
        tree_from.commit("One.", rev_id="from-1")
255
 
        tree_from.commit("Two.", rev_id="from-2")
256
 
 
257
 
        self.run_bzr('push -r1 ../to', working_dir='from')
258
 
 
259
 
        tree_to = WorkingTree.open('to')
260
 
        repo_to = tree_to.branch.repository
261
 
        self.assertTrue(repo_to.has_revision('from-1'))
262
 
        self.assertFalse(repo_to.has_revision('from-2'))
263
 
        self.assertEqual(tree_to.branch.last_revision_info()[1], 'from-1')
264
 
 
265
 
        self.run_bzr_error(
266
 
            "bzr: ERROR: bzr push --revision takes one value.\n",
267
 
            'push -r0..2 ../to', working_dir='from')
268
 
 
269
 
 
270
 
class RedirectingMemoryTransport(MemoryTransport):
271
 
 
272
 
    def mkdir(self, path, mode=None):
273
 
        path = self.abspath(path)[len(self._scheme):]
274
 
        if path == '/source':
275
 
            raise errors.RedirectRequested(
276
 
                path, self._scheme + '/target', is_permanent=True)
277
 
        elif path == '/infinite-loop':
278
 
            raise errors.RedirectRequested(
279
 
                path, self._scheme + '/infinite-loop', is_permanent=True)
280
 
        else:
281
 
            return super(RedirectingMemoryTransport, self).mkdir(
282
 
                path, mode)
283
 
 
284
 
 
285
 
class RedirectingMemoryServer(MemoryServer):
286
 
 
287
 
    def setUp(self):
288
 
        self._dirs = {'/': None}
289
 
        self._files = {}
290
 
        self._locks = {}
291
 
        self._scheme = 'redirecting-memory+%s:///' % id(self)
292
 
        register_transport(self._scheme, self._memory_factory)
293
 
 
294
 
    def _memory_factory(self, url):
295
 
        result = RedirectingMemoryTransport(url)
296
 
        result._dirs = self._dirs
297
 
        result._files = self._files
298
 
        result._locks = self._locks
299
 
        return result
300
 
 
301
 
    def tearDown(self):
302
 
        unregister_transport(self._scheme, self._memory_factory)
303
 
 
304
 
 
305
 
class TestPushRedirect(ExternalBase):
306
 
 
307
 
    def setUp(self):
308
 
        ExternalBase.setUp(self)
309
 
        self.memory_server = RedirectingMemoryServer()
310
 
        self.memory_server.setUp()
311
 
        self.addCleanup(self.memory_server.tearDown)
312
 
 
313
 
        # Make the branch and tree that we'll be pushing.
314
 
        t = self.make_branch_and_tree('tree')
315
 
        self.build_tree(['tree/file'])
316
 
        t.add('file')
317
 
        t.commit('commit 1')
318
 
 
319
 
    def test_push_redirects_on_mkdir(self):
320
 
        """If the push requires a mkdir, push respects redirect requests.
321
 
 
322
 
        This is added primarily to handle lp:/ URI support, so that users can
323
 
        push to new branches by specifying lp:/ URIs.
324
 
        """
325
 
        os.chdir('tree')
326
 
        destination_url = self.memory_server.get_url() + 'source'
327
 
        self.run_bzr('push %s' % destination_url)
328
 
        os.chdir('..')
329
 
 
330
 
        local_revision = Branch.open('tree').last_revision()
331
 
        remote_revision = Branch.open(
332
 
            self.memory_server.get_url() + 'target').last_revision()
333
 
        self.assertEqual(remote_revision, local_revision)
334
 
 
335
 
    def test_push_gracefully_handles_too_many_redirects(self):
336
 
        """Push fails gracefully if the mkdir generates a large number of
337
 
        redirects.
338
 
        """
339
 
        os.chdir('tree')
340
 
        destination_url = self.memory_server.get_url() + 'infinite-loop'
341
 
        out, err = self.run_bzr_error(
342
 
            ['Too many redirections trying to make %s\\.\n'
343
 
             % re.escape(destination_url)],
344
 
            'push %s' % destination_url, retcode=3)
345
 
        os.chdir('..')
346
 
        self.assertEqual('', out)