~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/upgrade.py

  • Committer: Robert Collins
  • Date: 2006-02-16 05:54:02 UTC
  • mto: (1534.1.24 integration)
  • mto: This revision was merged to the branch mainline in revision 1554.
  • Revision ID: robertc@robertcollins.net-20060216055402-bb6afc4d15c715cd
split out converter logic into per-format objects.

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
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
23
 
#
24
 
# 1. assign text version ids, and put those text versions into
25
 
#    the inventory as they're converted.
26
 
#
27
 
# 2. keep track of the previous version of each file, rather than
28
 
#    just using the last one imported
29
 
#
30
 
# 3. assign entry versions when files are added, renamed or moved.
31
 
#
32
 
# 4. when merged-in versions are observed, walk down through them
33
 
#    to discover everything, then commit bottom-up
34
 
#
35
 
# 5. track ancestry as things are merged in, and commit that in each
36
 
#    revision
37
 
#
38
 
# Perhaps it's best to first walk the whole graph and make a plan for
39
 
# what should be imported in what order?  Need a kind of topological
40
 
# sort of all revisions.  (Or do we, can we just before doing a revision
41
 
# see that all its parents have either been converted or abandoned?)
42
 
 
43
 
 
44
 
# Cannot import a revision until all its parents have been
45
 
# imported.  in other words, we can only import revisions whose
46
 
# parents have all been imported.  the first step must be to
47
 
# import a revision with no parents, of which there must be at
48
 
# least one.  (So perhaps it's useful to store forward pointers
49
 
# from a list of parents to their children?)
50
 
#
51
 
# Another (equivalent?) approach is to build up the ordered
52
 
# ancestry list for the last revision, and walk through that.  We
53
 
# are going to need that.
54
 
#
55
 
# We don't want to have to recurse all the way back down the list.
56
 
#
57
 
# Suppose we keep a queue of the revisions able to be processed at
58
 
# any point.  This starts out with all the revisions having no
59
 
# parents.
60
 
#
61
 
# This seems like a generally useful algorithm...
62
 
#
63
 
# The current algorithm is dumb (O(n**2)?) but will do the job, and
64
 
# takes less than a second on the bzr.dev branch.
65
 
 
66
 
# This currently does a kind of lazy conversion of file texts, where a
67
 
# new text is written in every version.  That's unnecessary but for
68
 
# the moment saves us having to worry about when files need new
69
 
# versions.
70
 
 
71
22
from cStringIO import StringIO
72
23
import os
73
24
import tempfile
80
31
from bzrlib.bzrdir import BzrDirFormat, BzrDirFormat4, BzrDirFormat5, BzrDirFormat6
81
32
import bzrlib.errors as errors
82
33
from bzrlib.errors import NoSuchFile, UpgradeReadonly
83
 
import bzrlib.hashcache as hashcache
84
34
from bzrlib.lockable_files import LockableFiles
85
35
from bzrlib.osutils import sha_strings, sha_string, pathjoin, abspath
86
36
from bzrlib.ui import ui_factory
96
46
from bzrlib.xml5 import serializer_v5
97
47
 
98
48
 
99
 
class Convert(object):
100
 
 
101
 
    def __init__(self, transport):
102
 
        self.base = transport.base
 
49
class Converter(object):
 
50
    """Converts a disk format object from one format to another."""
 
51
 
 
52
    def __init__(self, pb):
 
53
        """Create a converter.
 
54
 
 
55
        :param pb: a progress bar to use for progress information.
 
56
        """
 
57
        self.pb = pb
 
58
 
 
59
 
 
60
class ConvertBzrDir4To5(Converter):
 
61
    """Converts format 4 bzr dirs to format 5."""
 
62
 
 
63
    def __init__(self, to_convert, pb):
 
64
        """Create a converter.
 
65
 
 
66
        :param to_convert: The disk object to convert.
 
67
        :param pb: a progress bar to use for progress information.
 
68
        """
 
69
        super(ConvertBzrDir4To5, self).__init__(pb)
 
70
        self.bzrdir = to_convert
103
71
        self.converted_revs = set()
104
72
        self.absent_revisions = set()
105
73
        self.text_count = 0
106
74
        self.revisions = {}
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
 
        # FIXME: control files reuse
117
 
        self.control_files.lock_write()
118
 
        try:
119
 
            self.convert()
120
 
        finally:
121
 
            self.control_files.unlock()
122
 
 
 
75
        
123
76
    def convert(self):
124
 
        if not self._open_branch():
125
 
            return
126
 
        note('starting upgrade of %s', self.base)
127
 
        self._backup_control_dir()
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()
 
77
        """See Converter.convert()."""
 
78
        self.pb.note('starting upgrade from format 4 to 5')
 
79
        if isinstance(self.bzrdir.transport, LocalTransport):
 
80
            self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
 
81
        self._convert_to_weaves()
 
82
        return bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
164
83
 
165
84
    def _convert_to_weaves(self):
166
 
        note('note: upgrade may be faster if all store files are ungzipped first')
167
 
        bzr_transport = self.transport.clone('.bzr')
 
85
        self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
168
86
        try:
169
87
            # TODO permissions
170
 
            stat = bzr_transport.stat('weaves')
 
88
            stat = self.bzrdir.transport.stat('weaves')
171
89
            if not S_ISDIR(stat.st_mode):
172
 
                bzr_transport.delete('weaves')
173
 
                bzr_transport.mkdir('weaves')
 
90
                self.bzrdir.transport.delete('weaves')
 
91
                self.bzrdir.transport.mkdir('weaves')
174
92
        except NoSuchFile:
175
 
            bzr_transport.mkdir('weaves')
 
93
            self.bzrdir.transport.mkdir('weaves')
176
94
        self.inv_weave = Weave('inventory')
177
95
        # holds in-memory weaves for all files
178
96
        self.text_weaves = {}
179
 
        bzr_transport.delete('branch-format')
 
97
        self.bzrdir.transport.delete('branch-format')
 
98
        self.branch = self.bzrdir.open_branch()
180
99
        self._convert_working_inv()
181
100
        rev_history = self.branch.revision_history()
182
101
        # to_read is a stack holding the revisions we still need to process;
196
115
        self.pb.clear()
197
116
        self._write_all_weaves()
198
117
        self._write_all_revs()
199
 
        note('upgraded to weaves:')
200
 
        note('  %6d revisions and inventories' % len(self.revisions))
201
 
        note('  %6d revisions not present' % len(self.absent_revisions))
202
 
        note('  %6d texts' % self.text_count)
 
118
        self.pb.note('upgraded to weaves:')
 
119
        self.pb.note('  %6d revisions and inventories', len(self.revisions))
 
120
        self.pb.note('  %6d revisions not present', len(self.absent_revisions))
 
121
        self.pb.note('  %6d texts', self.text_count)
203
122
        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()
208
 
 
209
 
    def _open_branch(self):
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)
215
 
            return False
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" %
220
 
                           self.branch._branch_format)
221
 
        return True
222
 
 
223
 
    def _set_new_format(self, format):
224
 
        self.branch.control_files.put_utf8('branch-format', format)
 
123
        self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
225
124
 
226
125
    def _cleanup_spare_files_after_format4(self):
227
 
        transport = self.transport.clone('.bzr')
228
126
        # FIXME working tree upgrade foo.
229
127
        for n in 'merged-patches', 'pending-merged-patches':
230
128
            try:
231
129
                ## assert os.path.getsize(p) == 0
232
 
                transport.delete(n)
 
130
                self.bzrdir.transport.delete(n)
233
131
            except NoSuchFile:
234
132
                pass
235
 
        transport.delete_tree('inventory-store')
236
 
        transport.delete_tree('text-store')
237
 
 
238
 
    def _backup_control_dir(self):
239
 
        note('making backup of tree history')
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)
244
 
        note('if conversion fails, you can move this directory back to .bzr')
245
 
        note('if it succeeds, you can remove this directory if you wish')
 
133
        self.bzrdir.transport.delete_tree('inventory-store')
 
134
        self.bzrdir.transport.delete_tree('text-store')
246
135
 
247
136
    def _convert_working_inv(self):
248
 
        branch = self.branch
249
 
        inv = serializer_v4.read_inventory(branch.control_files.get('inventory'))
 
137
        inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
250
138
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
251
139
        # FIXME inventory is a working tree change.
252
 
        branch.control_files.put('inventory', new_inv_xml)
 
140
        self.branch.control_files.put('inventory', new_inv_xml)
253
141
 
254
142
    def _write_all_weaves(self):
255
 
        bzr_transport = self.transport.clone('.bzr')
256
 
        controlweaves = WeaveStore(bzr_transport, prefixed=False)
257
 
        weave_transport = bzr_transport.clone('weaves')
 
143
        controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
 
144
        weave_transport = self.bzrdir.transport.clone('weaves')
258
145
        weaves = WeaveStore(weave_transport, prefixed=False)
259
146
        transaction = PassThroughTransaction()
260
147
 
270
157
 
271
158
    def _write_all_revs(self):
272
159
        """Write all revisions out in new form."""
273
 
        transport = self.transport.clone('.bzr')
274
 
        transport.delete_tree('revision-store')
275
 
        transport.mkdir('revision-store')
276
 
        revision_transport = transport.clone('revision-store')
 
160
        self.bzrdir.transport.delete_tree('revision-store')
 
161
        self.bzrdir.transport.mkdir('revision-store')
 
162
        revision_transport = self.bzrdir.transport.clone('revision-store')
277
163
        # TODO permissions
278
164
        revision_store = TextStore(revision_transport,
279
165
                                   prefixed=False,
444
330
        return o
445
331
 
446
332
 
 
333
class ConvertBzrDir5To6(Converter):
 
334
    """Converts format 5 bzr dirs to format 6."""
 
335
 
 
336
    def __init__(self, to_convert, pb):
 
337
        """Create a converter.
 
338
 
 
339
        :param to_convert: The disk object to convert.
 
340
        :param pb: a progress bar to use for progress information.
 
341
        """
 
342
        super(ConvertBzrDir5To6, self).__init__(pb)
 
343
        self.bzrdir = to_convert
 
344
        
 
345
    def convert(self):
 
346
        """See Converter.convert()."""
 
347
        self.pb.note('starting upgrade from format 5 to 6')
 
348
        self._convert_to_prefixed()
 
349
        return bzrdir.BzrDir.open(self.bzrdir.root_transport.base)
 
350
 
 
351
    def _convert_to_prefixed(self):
 
352
        from bzrlib.store import hash_prefix
 
353
        self.bzrdir.transport.delete('branch-format')
 
354
        for store_name in ["weaves", "revision-store"]:
 
355
            note("adding prefixes to %s" % store_name) 
 
356
            store_transport = self.bzrdir.transport.clone(store_name)
 
357
            for filename in store_transport.list_dir('.'):
 
358
                if (filename.endswith(".weave") or
 
359
                    filename.endswith(".gz") or
 
360
                    filename.endswith(".sig")):
 
361
                    file_id = os.path.splitext(filename)[0]
 
362
                else:
 
363
                    file_id = filename
 
364
                prefix_dir = hash_prefix(file_id)
 
365
                # FIXME keep track of the dirs made RBC 20060121
 
366
                try:
 
367
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
368
                except NoSuchFile: # catches missing dirs strangely enough
 
369
                    store_transport.mkdir(prefix_dir)
 
370
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
371
        self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
 
372
 
 
373
 
 
374
class Convert(object):
 
375
 
 
376
    def __init__(self, transport):
 
377
        self.base = transport.base
 
378
        self.transport = transport
 
379
        if self.transport.is_readonly():
 
380
            raise UpgradeReadonly
 
381
        #  self.control_files = LockableFiles(transport.clone(bzrlib.BZRDIR), 'branch-lock')
 
382
        # Lock the branch (soon to be meta dir) to prevent anyone racing with us
 
383
        # This is currently windows incompatible, it will deadlock. When the upgrade
 
384
        # logic becomes format specific, then we can have the format know how to pass this
 
385
        # on. Also note that we probably have an 'upgrade meta' which upgrades the constituent
 
386
        # parts.
 
387
        # FIXME: control files reuse
 
388
        # self.control_files.lock_write()
 
389
        #try:
 
390
        self.convert()
 
391
        #finally:
 
392
        #     self.control_files.unlock()
 
393
 
 
394
    def convert(self):
 
395
        self.old_format = BzrDirFormat.find_format(self.transport)
 
396
        self.bzrdir = self.old_format.open(self.transport)
 
397
        self.branch = self.bzrdir.open_branch()
 
398
        self.pb = ui_factory.progress_bar()
 
399
        if isinstance(self.old_format, BzrDirFormat6):
 
400
            self.pb.note('this branch is in the most current format (%s)', self.old_format)
 
401
            return
 
402
        if (not isinstance(self.old_format, BzrDirFormat4) and
 
403
            not isinstance(self.old_format, BzrDirFormat5) and
 
404
            not isinstance(self.old_format, bzrdir.BzrDirMetaFormat1)):
 
405
            raise errors.BzrError("cannot upgrade from branch format %s" %
 
406
                           self.bzrdir._format)
 
407
        # return self.bzrdir.upgrade(pb)
 
408
        self.pb.note('starting upgrade of %s', self.base)
 
409
        self._backup_control_dir()
 
410
        if isinstance(self.bzrdir._format, BzrDirFormat4):
 
411
            converter = ConvertBzrDir4To5(self.bzrdir, self.pb)
 
412
            self.bzrdir = converter.convert()
 
413
        if isinstance(self.bzrdir._format, BzrDirFormat5):
 
414
            converter = ConvertBzrDir5To6(self.bzrdir, self.pb)
 
415
            self.bzrdir = converter.convert()
 
416
        self.pb.note("finished")
 
417
 
 
418
    def _backup_control_dir(self):
 
419
        note('making backup of tree history')
 
420
        self.transport.copy_tree('.bzr', '.bzr.backup')
 
421
        note('%s.bzr has been backed up to %s.bzr.backup',
 
422
             self.transport.base,
 
423
             self.transport.base)
 
424
        note('if conversion fails, you can move this directory back to .bzr')
 
425
        note('if it succeeds, you can remove this directory if you wish')
 
426
 
447
427
def upgrade(url):
448
428
    t = get_transport(url)
449
429
    Convert(t)