~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/upgrade.py

  • Committer: Robert Collins
  • Date: 2006-02-11 11:58:06 UTC
  • mto: (1534.1.22 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060211115806-732dabc1e35714ed
Give format3 working trees their own last-revision marker.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
2
 
#
3
1
# Copyright (C) 2005 Canonical Ltd
4
2
#
5
3
# This program is free software; you can redistribute it and/or modify
16
14
# along with this program; if not, write to the Free Software
17
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
16
 
19
 
"""Experiment in converting existing bzr branches to weaves."""
 
17
"""bzr upgrade logic."""
 
18
 
 
19
# change upgrade from .bzr to create a '.bzr-new', then do a bait and switch.
 
20
 
20
21
 
21
22
# To make this properly useful
22
23
#
67
68
# the moment saves us having to worry about when files need new
68
69
# versions.
69
70
 
70
 
 
71
 
if False:
72
 
    try:
73
 
        import psyco
74
 
        psyco.full()
75
 
    except ImportError:
76
 
        pass
77
 
 
78
 
 
 
71
from cStringIO import StringIO
79
72
import os
80
73
import tempfile
81
74
import sys
82
 
import logging
83
 
import shutil
 
75
from stat import *
84
76
 
85
 
from bzrlib.branch import Branch, find_branch, BZR_BRANCH_FORMAT_5
86
 
from bzrlib.revfile import Revfile
 
77
import bzrlib
 
78
from bzrlib.branch import Branch
 
79
import bzrlib.bzrdir as bzrdir
 
80
from bzrlib.bzrdir import BzrDirFormat, BzrDirFormat4, BzrDirFormat5, BzrDirFormat6
 
81
import bzrlib.errors as errors
 
82
from bzrlib.errors import NoSuchFile, UpgradeReadonly
 
83
import bzrlib.hashcache as hashcache
 
84
from bzrlib.lockable_files import LockableFiles
 
85
from bzrlib.osutils import sha_strings, sha_string, pathjoin, abspath
 
86
from bzrlib.ui import ui_factory
 
87
from bzrlib.store.text import TextStore
 
88
from bzrlib.store.weave import WeaveStore
 
89
from bzrlib.trace import mutter, note, warning
 
90
from bzrlib.transactions import PassThroughTransaction
 
91
from bzrlib.transport import get_transport
 
92
from bzrlib.transport.local import LocalTransport
87
93
from bzrlib.weave import Weave
88
94
from bzrlib.weavefile import read_weave, write_weave
89
 
from bzrlib.progress import ProgressBar
90
 
from bzrlib.atomicfile import AtomicFile
91
95
from bzrlib.xml4 import serializer_v4
92
96
from bzrlib.xml5 import serializer_v5
93
 
from bzrlib.trace import mutter, note, warning, enable_default_logging
94
 
from bzrlib.osutils import sha_strings, sha_string
95
 
from bzrlib.commit import merge_ancestry_lines
96
97
 
97
98
 
98
99
class Convert(object):
99
 
    def __init__(self, base_dir):
100
 
        self.base = base_dir
 
100
 
 
101
    def __init__(self, transport):
 
102
        self.base = transport.base
101
103
        self.converted_revs = set()
102
104
        self.absent_revisions = set()
103
105
        self.text_count = 0
104
106
        self.revisions = {}
105
 
        self.convert()
106
 
 
 
107
        self.transport = transport
 
108
        if self.transport.is_readonly():
 
109
            raise UpgradeReadonly
 
110
        self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'branch-lock')
 
111
        # Lock the branch (soon to be meta dir) to prevent anyone racing with us
 
112
        # This is currently windows incompatible, it will deadlock. When the upgrade
 
113
        # logic becomes format specific, then we can have the format know how to pass this
 
114
        # on. Also note that we probably have an 'upgrade meta' which upgrades the constituent
 
115
        # parts.
 
116
        print "FIXME: control files reuse" 
 
117
        self.control_files.lock_write()
 
118
        try:
 
119
            self.convert()
 
120
        finally:
 
121
            self.control_files.unlock()
107
122
 
108
123
    def convert(self):
109
124
        if not self._open_branch():
110
125
            return
111
 
        note('starting upgrade of %s', os.path.abspath(self.base))
 
126
        note('starting upgrade of %s', self.base)
112
127
        self._backup_control_dir()
113
 
        note('starting upgrade')
 
128
        self.pb = ui_factory.progress_bar()
 
129
        if isinstance(self.old_format, BzrDirFormat4):
 
130
            note('starting upgrade from format 4 to 5')
 
131
            if isinstance(self.transport, LocalTransport):
 
132
                self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
133
            self._convert_to_weaves()
 
134
        if isinstance(self.old_format, BzrDirFormat5):
 
135
            note('starting upgrade from format 5 to 6')
 
136
            self._convert_to_prefixed()
 
137
        note("finished")
 
138
 
 
139
    def _convert_to_prefixed(self):
 
140
        from bzrlib.store import hash_prefix
 
141
        bzr_transport = self.transport.clone('.bzr')
 
142
        bzr_transport.delete('branch-format')
 
143
        for store_name in ["weaves", "revision-store"]:
 
144
            note("adding prefixes to %s" % store_name) 
 
145
            store_transport = bzr_transport.clone(store_name)
 
146
            for filename in store_transport.list_dir('.'):
 
147
                if (filename.endswith(".weave") or
 
148
                    filename.endswith(".gz") or
 
149
                    filename.endswith(".sig")):
 
150
                    file_id = os.path.splitext(filename)[0]
 
151
                else:
 
152
                    file_id = filename
 
153
                prefix_dir = hash_prefix(file_id)
 
154
                # FIXME keep track of the dirs made RBC 20060121
 
155
                try:
 
156
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
157
                except NoSuchFile: # catches missing dirs strangely enough
 
158
                    store_transport.mkdir(prefix_dir)
 
159
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
160
        self.old_format = BzrDirFormat6()
 
161
        self._set_new_format(self.old_format.get_format_string())
 
162
        self.bzrdir = self.old_format.open(self.transport)
 
163
        self.branch = self.bzrdir.open_branch()
 
164
 
 
165
    def _convert_to_weaves(self):
114
166
        note('note: upgrade may be faster if all store files are ungzipped first')
115
 
        self.pb = ProgressBar()
116
 
        if not os.path.isdir(self.base + '/.bzr/weaves'):
117
 
            os.mkdir(self.base + '/.bzr/weaves')
 
167
        bzr_transport = self.transport.clone('.bzr')
 
168
        try:
 
169
            # TODO permissions
 
170
            stat = bzr_transport.stat('weaves')
 
171
            if not S_ISDIR(stat.st_mode):
 
172
                bzr_transport.delete('weaves')
 
173
                bzr_transport.mkdir('weaves')
 
174
        except NoSuchFile:
 
175
            bzr_transport.mkdir('weaves')
118
176
        self.inv_weave = Weave('inventory')
119
 
        self.anc_weave = Weave('ancestry')
120
 
        self.ancestries = {}
121
177
        # holds in-memory weaves for all files
122
178
        self.text_weaves = {}
123
 
        os.remove(self.branch.controlfilename('branch-format'))
 
179
        bzr_transport.delete('branch-format')
124
180
        self._convert_working_inv()
125
181
        rev_history = self.branch.revision_history()
126
182
        # to_read is a stack holding the revisions we still need to process;
127
183
        # appending to it adds new highest-priority revisions
128
184
        self.known_revisions = set(rev_history)
129
 
        self.to_read = [rev_history[-1]]
 
185
        self.to_read = rev_history[-1:]
130
186
        while self.to_read:
131
187
            rev_id = self.to_read.pop()
132
188
            if (rev_id not in self.revisions
138
194
            self.pb.update('converting revision', i, len(to_import))
139
195
            self._convert_one_rev(rev_id)
140
196
        self.pb.clear()
 
197
        self._write_all_weaves()
 
198
        self._write_all_revs()
141
199
        note('upgraded to weaves:')
142
200
        note('  %6d revisions and inventories' % len(self.revisions))
143
 
        note('  %6d absent revisions removed' % len(self.absent_revisions))
 
201
        note('  %6d revisions not present' % len(self.absent_revisions))
144
202
        note('  %6d texts' % self.text_count)
145
 
        self._write_all_weaves()
146
 
        self._write_all_revs()
147
 
        self._set_new_format()
148
 
        self._cleanup_spare_files()
149
 
 
 
203
        self._cleanup_spare_files_after_format4()
 
204
        self.old_format = BzrDirFormat5()
 
205
        self._set_new_format(self.old_format.get_format_string())
 
206
        self.bzrdir = self.old_format.open(self.transport)
 
207
        self.branch = self.bzrdir.open_branch()
150
208
 
151
209
    def _open_branch(self):
152
 
        self.branch = Branch.open_downlevel(self.base)
153
 
        if self.branch._branch_format == 5:
154
 
            note('this branch is already in the most current format')
 
210
        self.old_format = BzrDirFormat.find_format(self.transport)
 
211
        self.bzrdir = self.old_format.open(self.transport)
 
212
        self.branch = self.bzrdir.open_branch()
 
213
        if isinstance(self.old_format, BzrDirFormat6):
 
214
            note('this branch is in the most current format (%s)', self.old_format)
155
215
            return False
156
 
        if self.branch._branch_format != 4:
157
 
            raise BzrError("cannot upgrade from branch format %r" %
 
216
        if (not isinstance(self.old_format, BzrDirFormat4) and
 
217
            not isinstance(self.old_format, BzrDirFormat5) and
 
218
            not isinstance(self.old_format, bzrdir.BzrDirMetaFormat1)):
 
219
            raise errors.BzrError("cannot upgrade from branch format %s" %
158
220
                           self.branch._branch_format)
159
221
        return True
160
222
 
161
 
 
162
 
    def _set_new_format(self):
163
 
        self.branch.put_controlfile('branch-format', BZR_BRANCH_FORMAT_5)
164
 
 
165
 
 
166
 
    def _cleanup_spare_files(self):
 
223
    def _set_new_format(self, format):
 
224
        self.branch.control_files.put_utf8('branch-format', format)
 
225
 
 
226
    def _cleanup_spare_files_after_format4(self):
 
227
        transport = self.transport.clone('.bzr')
 
228
        print "FIXME working tree upgrade foo."
167
229
        for n in 'merged-patches', 'pending-merged-patches':
168
 
            p = self.branch.controlfilename(n)
169
 
            if not os.path.exists(p):
170
 
                continue
171
 
            ## assert os.path.getsize(p) == 0
172
 
            os.remove(p)
173
 
        shutil.rmtree(self.base + '/.bzr/inventory-store')
174
 
        shutil.rmtree(self.base + '/.bzr/text-store')
175
 
 
 
230
            try:
 
231
                ## assert os.path.getsize(p) == 0
 
232
                transport.delete(n)
 
233
            except NoSuchFile:
 
234
                pass
 
235
        transport.delete_tree('inventory-store')
 
236
        transport.delete_tree('text-store')
176
237
 
177
238
    def _backup_control_dir(self):
178
 
        orig = self.base + '/.bzr'
179
 
        backup = orig + '.backup'
180
239
        note('making backup of tree history')
181
 
        shutil.copytree(orig, backup)
182
 
        note('%s has been backed up to %s', orig, backup)
 
240
        self.transport.copy_tree('.bzr', '.bzr.backup')
 
241
        note('%s.bzr has been backed up to %s.bzr.backup',
 
242
             self.transport.base,
 
243
             self.transport.base)
183
244
        note('if conversion fails, you can move this directory back to .bzr')
184
245
        note('if it succeeds, you can remove this directory if you wish')
185
246
 
186
 
 
187
247
    def _convert_working_inv(self):
188
248
        branch = self.branch
189
 
        inv = serializer_v4.read_inventory(branch.controlfile('inventory', 'rb'))
 
249
        inv = serializer_v4.read_inventory(branch.control_files.get('inventory'))
190
250
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
191
 
        branch.put_controlfile('inventory', new_inv_xml)
192
 
 
193
 
 
 
251
        print "fixme inventory is a working tree change."
 
252
        branch.control_files.put('inventory', new_inv_xml)
194
253
 
195
254
    def _write_all_weaves(self):
196
 
        write_a_weave(self.inv_weave, self.base + '/.bzr/inventory.weave')
197
 
        write_a_weave(self.anc_weave, self.base + '/.bzr/ancestry.weave')
 
255
        bzr_transport = self.transport.clone('.bzr')
 
256
        controlweaves = WeaveStore(bzr_transport, prefixed=False)
 
257
        weave_transport = bzr_transport.clone('weaves')
 
258
        weaves = WeaveStore(weave_transport, prefixed=False)
 
259
        transaction = PassThroughTransaction()
 
260
 
 
261
        controlweaves.put_weave('inventory', self.inv_weave, transaction)
198
262
        i = 0
199
263
        try:
200
264
            for file_id, file_weave in self.text_weaves.items():
201
265
                self.pb.update('writing weave', i, len(self.text_weaves))
202
 
                write_a_weave(file_weave, self.base + '/.bzr/weaves/%s.weave' % file_id)
 
266
                weaves.put_weave(file_id, file_weave, transaction)
203
267
                i += 1
204
268
        finally:
205
269
            self.pb.clear()
206
270
 
207
 
 
208
271
    def _write_all_revs(self):
209
272
        """Write all revisions out in new form."""
210
 
        shutil.rmtree(self.base + '/.bzr/revision-store')
211
 
        os.mkdir(self.base + '/.bzr/revision-store')
 
273
        transport = self.transport.clone('.bzr')
 
274
        transport.delete_tree('revision-store')
 
275
        transport.mkdir('revision-store')
 
276
        revision_transport = transport.clone('revision-store')
 
277
        # TODO permissions
 
278
        revision_store = TextStore(revision_transport,
 
279
                                   prefixed=False,
 
280
                                   compressed=True)
212
281
        try:
213
282
            for i, rev_id in enumerate(self.converted_revs):
214
283
                self.pb.update('write revision', i, len(self.converted_revs))
215
 
                f = file(self.base + '/.bzr/revision-store/%s' % rev_id, 'wb')
216
 
                try:
217
 
                    serializer_v5.write_revision(self.revisions[rev_id], f)
218
 
                finally:
219
 
                    f.close()
 
284
                rev_tmp = StringIO()
 
285
                serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
 
286
                rev_tmp.seek(0)
 
287
                revision_store.add(rev_tmp, rev_id)
220
288
        finally:
221
289
            self.pb.clear()
222
290
 
229
297
        self.pb.update('loading revision',
230
298
                       len(self.revisions),
231
299
                       len(self.known_revisions))
232
 
        if rev_id not in self.branch.revision_store:
 
300
        if not self.branch.repository.revision_store.has_id(rev_id):
233
301
            self.pb.clear()
234
302
            note('revision {%s} not present in branch; '
235
 
                 'will not be converted',
 
303
                 'will be converted as a ghost',
236
304
                 rev_id)
237
305
            self.absent_revisions.add(rev_id)
238
306
        else:
239
 
            rev_xml = self.branch.revision_store[rev_id].read()
 
307
            rev_xml = self.branch.repository.revision_store.get(rev_id).read()
240
308
            rev = serializer_v4.read_revision_from_string(rev_xml)
241
309
            for parent_id in rev.parent_ids:
242
310
                self.known_revisions.add(parent_id)
246
314
 
247
315
    def _load_old_inventory(self, rev_id):
248
316
        assert rev_id not in self.converted_revs
249
 
        old_inv_xml = self.branch.inventory_store[rev_id].read()
 
317
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
250
318
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
251
319
        rev = self.revisions[rev_id]
252
320
        if rev.inventory_sha1:
266
334
        """Convert revision and all referenced objects to new format."""
267
335
        rev = self.revisions[rev_id]
268
336
        inv = self._load_old_inventory(rev_id)
269
 
        for parent_id in rev.parent_ids[:]:
270
 
            if parent_id in self.absent_revisions:
271
 
                rev.parent_ids.remove(parent_id)
272
 
                self.pb.clear()
273
 
                note('remove {%s} as parent of {%s}', parent_id, rev_id)
274
 
        self._convert_revision_contents(rev, inv)
275
 
        self._store_new_weave(rev, inv)
276
 
        self._make_rev_ancestry(rev)
 
337
        present_parents = [p for p in rev.parent_ids
 
338
                           if p not in self.absent_revisions]
 
339
        self._convert_revision_contents(rev, inv, present_parents)
 
340
        self._store_new_weave(rev, inv, present_parents)
277
341
        self.converted_revs.add(rev_id)
278
342
 
279
343
 
280
 
    def _store_new_weave(self, rev, inv):
 
344
    def _store_new_weave(self, rev, inv, present_parents):
281
345
        # the XML is now updated with text versions
282
346
        if __debug__:
283
347
            for file_id in inv:
287
351
                assert hasattr(ie, 'revision'), \
288
352
                    'no revision on {%s} in {%s}' % \
289
353
                    (file_id, rev.revision_id)
290
 
 
291
354
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
292
355
        new_inv_sha1 = sha_string(new_inv_xml)
293
 
        self.inv_weave.add(rev.revision_id, rev.parent_ids,
 
356
        self.inv_weave.add(rev.revision_id, 
 
357
                           present_parents,
294
358
                           new_inv_xml.splitlines(True),
295
359
                           new_inv_sha1)
296
360
        rev.inventory_sha1 = new_inv_sha1
297
361
 
298
 
 
299
 
    def _make_rev_ancestry(self, rev):
300
 
        rev_id = rev.revision_id
301
 
        for parent_id in rev.parent_ids:
302
 
            assert parent_id in self.converted_revs
303
 
        if rev.parent_ids:
304
 
            lines = list(self.anc_weave.mash_iter(rev.parent_ids))
305
 
        else:
306
 
            lines = []
307
 
        lines.append(rev_id + '\n')
308
 
        if __debug__:
309
 
            parent_ancestries = [self.ancestries[p] for p in rev.parent_ids]
310
 
            new_lines = merge_ancestry_lines(rev_id, parent_ancestries)
311
 
            assert set(lines) == set(new_lines)
312
 
            self.ancestries[rev_id] = new_lines
313
 
        self.anc_weave.add(rev_id, rev.parent_ids, lines)
314
 
 
315
 
 
316
 
    def _convert_revision_contents(self, rev, inv):
 
362
    def _convert_revision_contents(self, rev, inv, present_parents):
317
363
        """Convert all the files within a revision.
318
364
 
319
365
        Also upgrade the inventory to refer to the text revision ids."""
320
366
        rev_id = rev.revision_id
321
367
        mutter('converting texts of revision {%s}',
322
368
               rev_id)
323
 
        parent_invs = map(self._load_updated_inventory, rev.parent_ids)
 
369
        parent_invs = map(self._load_updated_inventory, present_parents)
324
370
        for file_id in inv:
325
371
            ie = inv[file_id]
326
 
            self._set_revision(rev, ie, parent_invs)
327
 
            if not ie.has_text():
328
 
                continue
329
372
            self._convert_file_version(rev, ie, parent_invs)
330
373
 
331
 
 
332
 
    def _set_revision(self, rev, ie, parent_invs):
333
 
        """Set name version for a file.
334
 
 
335
 
        Done in a slightly lazy way: if the file is renamed or in a merge revision
336
 
        it gets a new version, otherwise the same as before.
337
 
        """
338
 
        file_id = ie.file_id
339
 
        if ie.kind == 'root_directory':
340
 
            return
341
 
        if len(parent_invs) != 1:
342
 
            ie.revision = rev.revision_id
343
 
        else:
344
 
            old_inv = parent_invs[0]
345
 
            if not old_inv.has_id(file_id):
346
 
                ie.revision = rev.revision_id
347
 
            else:
348
 
                old_ie = old_inv[file_id]
349
 
                if (old_ie.parent_id != ie.parent_id
350
 
                    or old_ie.name != ie.name):
351
 
                    ie.revision = rev.revision_id
352
 
                else:
353
 
                    ie.revision = old_ie.revision
354
 
 
355
 
 
356
 
 
357
374
    def _convert_file_version(self, rev, ie, parent_invs):
358
375
        """Convert one version of one file.
359
376
 
360
377
        The file needs to be added into the weave if it is a merge
361
378
        of >=2 parents or if it's changed from its parent.
362
379
        """
 
380
        if ie.kind == 'root_directory':
 
381
            return
363
382
        file_id = ie.file_id
364
383
        rev_id = rev.revision_id
365
384
        w = self.text_weaves.get(file_id)
366
385
        if w is None:
367
386
            w = Weave(file_id)
368
387
            self.text_weaves[file_id] = w
369
 
        file_parents = []
370
388
        text_changed = False
371
 
        for parent_inv in parent_invs:
372
 
            if parent_inv.has_id(file_id):
373
 
                parent_ie = parent_inv[file_id]
374
 
                old_revision = parent_ie.revision
 
389
        previous_entries = ie.find_previous_heads(parent_invs, w)
 
390
        for old_revision in previous_entries:
375
391
                # if this fails, its a ghost ?
376
392
                assert old_revision in self.converted_revs 
377
 
                if old_revision not in file_parents:
378
 
                    file_parents.append(old_revision)
379
 
                if parent_ie.text_sha1 != ie.text_sha1:
380
 
                    text_changed = True
381
 
        if len(file_parents) != 1 or text_changed:
382
 
            file_lines = self.branch.text_store[ie.text_id].readlines()
 
393
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
394
        del ie.text_id
 
395
        assert getattr(ie, 'revision', None) is not None
 
396
 
 
397
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
398
        # TODO: convert this logic, which is ~= snapshot to
 
399
        # a call to:. This needs the path figured out. rather than a work_tree
 
400
        # a v4 revision_tree can be given, or something that looks enough like
 
401
        # one to give the file content to the entry if it needs it.
 
402
        # and we need something that looks like a weave store for snapshot to 
 
403
        # save against.
 
404
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
405
        if len(previous_revisions) == 1:
 
406
            previous_ie = previous_revisions.values()[0]
 
407
            if ie._unchanged(previous_ie):
 
408
                ie.revision = previous_ie.revision
 
409
                return
 
410
        parent_indexes = map(w.lookup, previous_revisions)
 
411
        if ie.has_text():
 
412
            text = self.branch.repository.text_store.get(ie.text_id)
 
413
            file_lines = text.readlines()
383
414
            assert sha_strings(file_lines) == ie.text_sha1
384
415
            assert sum(map(len, file_lines)) == ie.text_size
385
 
            w.add(rev_id, file_parents, file_lines, ie.text_sha1)
386
 
            ie.revision = rev_id
 
416
            w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
387
417
            self.text_count += 1
388
 
            ##mutter('import text {%s} of {%s}',
389
 
            ##       ie.text_id, file_id)
390
418
        else:
391
 
            ##mutter('text of {%s} unchanged from parent', file_id)
392
 
            ie.revision = file_parents[0]
393
 
        del ie.text_id
394
 
 
395
 
 
 
419
            w.add(rev_id, parent_indexes, [], None)
 
420
        ie.revision = rev_id
 
421
        ##mutter('import text {%s} of {%s}',
 
422
        ##       ie.text_id, file_id)
396
423
 
397
424
    def _make_order(self):
398
425
        """Return a suitable order for importing revisions.
417
444
        return o
418
445
 
419
446
 
420
 
def write_a_weave(weave, filename):
421
 
    inv_wf = file(filename, 'wb')
422
 
    try:
423
 
        write_weave(weave, inv_wf)
424
 
    finally:
425
 
        inv_wf.close()
426
 
 
427
 
 
428
 
def upgrade(base_dir):
429
 
    Convert(base_dir)
 
447
def upgrade(url):
 
448
    t = get_transport(url)
 
449
    Convert(t)