/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/upgrade.py

Move working tree initialisation out from  Branch.initialize, deprecated Branch.initialize to Branch.create.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2008, 2009, 2010 Canonical Ltd
 
1
# Copyright (C) 2005 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
 
 
17
 
"""bzr upgrade logic."""
18
 
 
19
 
 
20
 
from bzrlib.bzrdir import BzrDir, format_registry
21
 
import bzrlib.errors as errors
22
 
from bzrlib.remote import RemoteBzrDir
23
 
import bzrlib.ui as ui
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
16
 
 
17
"""Experiment in converting existing bzr branches to weaves."""
 
18
 
 
19
# To make this properly useful
 
20
#
 
21
# 1. assign text version ids, and put those text versions into
 
22
#    the inventory as they're converted.
 
23
#
 
24
# 2. keep track of the previous version of each file, rather than
 
25
#    just using the last one imported
 
26
#
 
27
# 3. assign entry versions when files are added, renamed or moved.
 
28
#
 
29
# 4. when merged-in versions are observed, walk down through them
 
30
#    to discover everything, then commit bottom-up
 
31
#
 
32
# 5. track ancestry as things are merged in, and commit that in each
 
33
#    revision
 
34
#
 
35
# Perhaps it's best to first walk the whole graph and make a plan for
 
36
# what should be imported in what order?  Need a kind of topological
 
37
# sort of all revisions.  (Or do we, can we just before doing a revision
 
38
# see that all its parents have either been converted or abandoned?)
 
39
 
 
40
 
 
41
# Cannot import a revision until all its parents have been
 
42
# imported.  in other words, we can only import revisions whose
 
43
# parents have all been imported.  the first step must be to
 
44
# import a revision with no parents, of which there must be at
 
45
# least one.  (So perhaps it's useful to store forward pointers
 
46
# from a list of parents to their children?)
 
47
#
 
48
# Another (equivalent?) approach is to build up the ordered
 
49
# ancestry list for the last revision, and walk through that.  We
 
50
# are going to need that.
 
51
#
 
52
# We don't want to have to recurse all the way back down the list.
 
53
#
 
54
# Suppose we keep a queue of the revisions able to be processed at
 
55
# any point.  This starts out with all the revisions having no
 
56
# parents.
 
57
#
 
58
# This seems like a generally useful algorithm...
 
59
#
 
60
# The current algorithm is dumb (O(n**2)?) but will do the job, and
 
61
# takes less than a second on the bzr.dev branch.
 
62
 
 
63
# This currently does a kind of lazy conversion of file texts, where a
 
64
# new text is written in every version.  That's unnecessary but for
 
65
# the moment saves us having to worry about when files need new
 
66
# versions.
 
67
 
 
68
from cStringIO import StringIO
 
69
import os
 
70
import tempfile
 
71
import sys
 
72
from stat import *
 
73
 
 
74
from bzrlib.branch import Branch, find_branch
 
75
from bzrlib.branch import BZR_BRANCH_FORMAT_5, BZR_BRANCH_FORMAT_6
 
76
from bzrlib.branch import BzrBranchFormat, BzrBranchFormat4, BzrBranchFormat5, BzrBranchFormat6
 
77
from bzrlib.errors import NoSuchFile, UpgradeReadonly
 
78
import bzrlib.hashcache as hashcache
 
79
from bzrlib.osutils import sha_strings, sha_string, pathjoin, abspath
 
80
from bzrlib.ui import ui_factory
 
81
from bzrlib.store.text import TextStore
 
82
from bzrlib.store.weave import WeaveStore
 
83
from bzrlib.trace import mutter, note, warning
 
84
from bzrlib.transactions import PassThroughTransaction
 
85
from bzrlib.transport import get_transport
 
86
from bzrlib.transport.local import LocalTransport
 
87
from bzrlib.weave import Weave
 
88
from bzrlib.weavefile import read_weave, write_weave
 
89
from bzrlib.xml4 import serializer_v4
 
90
from bzrlib.xml5 import serializer_v5
24
91
 
25
92
 
26
93
class Convert(object):
27
94
 
28
 
    def __init__(self, url, format=None):
29
 
        self.format = format
30
 
        self.bzrdir = BzrDir.open_unsupported(url)
31
 
        # XXX: Change to cleanup
32
 
        warning_id = 'cross_format_fetch'
33
 
        saved_warning = warning_id in ui.ui_factory.suppressed_warnings
34
 
        if isinstance(self.bzrdir, RemoteBzrDir):
35
 
            self.bzrdir._ensure_real()
36
 
            self.bzrdir = self.bzrdir._real_bzrdir
37
 
        if self.bzrdir.root_transport.is_readonly():
38
 
            raise errors.UpgradeReadonly
39
 
        self.transport = self.bzrdir.root_transport
40
 
        ui.ui_factory.suppressed_warnings.add(warning_id)
41
 
        try:
42
 
            self.convert()
43
 
        finally:
44
 
            if not saved_warning:
45
 
                ui.ui_factory.suppressed_warnings.remove(warning_id)
 
95
    def __init__(self, transport):
 
96
        self.base = transport.base
 
97
        self.converted_revs = set()
 
98
        self.absent_revisions = set()
 
99
        self.text_count = 0
 
100
        self.revisions = {}
 
101
        self.transport = transport
 
102
        self.convert()
46
103
 
47
104
    def convert(self):
 
105
        if self.transport.is_readonly():
 
106
            raise UpgradeReadonly
 
107
        if not self._open_branch():
 
108
            return
 
109
        note('starting upgrade of %s', self.base)
 
110
        self._backup_control_dir()
 
111
        self.pb = ui_factory.progress_bar()
 
112
        if isinstance(self.old_format, BzrBranchFormat4):
 
113
            note('starting upgrade from format 4 to 5')
 
114
            self._convert_to_weaves()
 
115
        if isinstance(self.old_format, BzrBranchFormat5):
 
116
            note('starting upgrade from format 5 to 6')
 
117
            self._convert_to_prefixed()
 
118
        if isinstance(self.transport, LocalTransport):
 
119
            cache = hashcache.HashCache(abspath(self.base))
 
120
            cache.clear()
 
121
            cache.write()
 
122
        note("finished")
 
123
 
 
124
    def _convert_to_prefixed(self):
 
125
        from bzrlib.store import hash_prefix
 
126
        bzr_transport = self.transport.clone('.bzr')
 
127
        bzr_transport.delete('branch-format')
 
128
        for store_name in ["weaves", "revision-store"]:
 
129
            note("adding prefixes to %s" % store_name) 
 
130
            store_transport = bzr_transport.clone(store_name)
 
131
            for filename in store_transport.list_dir('.'):
 
132
                if filename.endswith(".weave") or filename.endswith(".gz"):
 
133
                    file_id = os.path.splitext(filename)[0]
 
134
                else:
 
135
                    file_id = filename
 
136
                prefix_dir = hash_prefix(file_id)
 
137
                # FIXME keep track of the dirs made RBC 20060121
 
138
                try:
 
139
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
140
                except NoSuchFile: # catches missing dirs strangely enough
 
141
                    store_transport.mkdir(prefix_dir)
 
142
                    store_transport.move(filename, prefix_dir + '/' + filename)
 
143
        self._set_new_format(BZR_BRANCH_FORMAT_6)
 
144
        self.branch = BzrBranchFormat6().open(self.transport)
 
145
        self.old_format = self.branch._branch_format
 
146
 
 
147
    def _convert_to_weaves(self):
 
148
        note('note: upgrade may be faster if all store files are ungzipped first')
 
149
        bzr_transport = self.transport.clone('.bzr')
48
150
        try:
49
 
            branch = self.bzrdir.open_branch()
50
 
            if branch.user_url != self.bzrdir.user_url:
51
 
                ui.ui_factory.note("This is a checkout. The branch (%s) needs to be "
52
 
                             "upgraded separately." %
53
 
                             branch.user_url)
54
 
            del branch
55
 
        except (errors.NotBranchError, errors.IncompatibleRepositories):
56
 
            # might not be a format we can open without upgrading; see e.g.
57
 
            # https://bugs.launchpad.net/bzr/+bug/253891
58
 
            pass
59
 
        if self.format is None:
 
151
            # TODO permissions
 
152
            stat = bzr_transport.stat('weaves')
 
153
            if not S_ISDIR(stat.st_mode):
 
154
                bzr_transport.delete('weaves')
 
155
                bzr_transport.mkdir('weaves')
 
156
        except NoSuchFile:
 
157
            bzr_transport.mkdir('weaves')
 
158
        self.inv_weave = Weave('inventory')
 
159
        # holds in-memory weaves for all files
 
160
        self.text_weaves = {}
 
161
        bzr_transport.delete('branch-format')
 
162
        self._convert_working_inv()
 
163
        rev_history = self.branch.revision_history()
 
164
        # to_read is a stack holding the revisions we still need to process;
 
165
        # appending to it adds new highest-priority revisions
 
166
        self.known_revisions = set(rev_history)
 
167
        self.to_read = rev_history[-1:]
 
168
        while self.to_read:
 
169
            rev_id = self.to_read.pop()
 
170
            if (rev_id not in self.revisions
 
171
                and rev_id not in self.absent_revisions):
 
172
                self._load_one_rev(rev_id)
 
173
        self.pb.clear()
 
174
        to_import = self._make_order()
 
175
        for i, rev_id in enumerate(to_import):
 
176
            self.pb.update('converting revision', i, len(to_import))
 
177
            self._convert_one_rev(rev_id)
 
178
        self.pb.clear()
 
179
        self._write_all_weaves()
 
180
        self._write_all_revs()
 
181
        note('upgraded to weaves:')
 
182
        note('  %6d revisions and inventories' % len(self.revisions))
 
183
        note('  %6d revisions not present' % len(self.absent_revisions))
 
184
        note('  %6d texts' % self.text_count)
 
185
        self._cleanup_spare_files_after_format4()
 
186
        self._set_new_format(BZR_BRANCH_FORMAT_5)
 
187
        self.branch = BzrBranchFormat5().open(self.transport)
 
188
        self.old_format = self.branch._branch_format
 
189
 
 
190
    def _open_branch(self):
 
191
        self.old_format = BzrBranchFormat.find_format(self.transport)
 
192
        self.branch = self.old_format.open(self.transport)
 
193
        if isinstance(self.old_format, BzrBranchFormat6):
 
194
            note('this branch is in the most current format (%s)', self.old_format)
 
195
            return False
 
196
        if (not isinstance(self.old_format, BzrBranchFormat4) and
 
197
            not isinstance(self.old_format, BzrBranchFormat5)):
 
198
            raise BzrError("cannot upgrade from branch format %s" %
 
199
                           self.branch._branch_format)
 
200
        return True
 
201
 
 
202
    def _set_new_format(self, format):
 
203
        self.branch.put_controlfile('branch-format', format)
 
204
 
 
205
    def _cleanup_spare_files_after_format4(self):
 
206
        transport = self.transport.clone('.bzr')
 
207
        for n in 'merged-patches', 'pending-merged-patches':
60
208
            try:
61
 
                rich_root = self.bzrdir.find_repository()._format.rich_root_data
62
 
            except errors.NoRepositoryPresent:
63
 
                rich_root = False # assume no rich roots
64
 
            if rich_root:
65
 
                format_name = "default-rich-root"
66
 
            else:
67
 
                format_name = "default"
68
 
            format = format_registry.make_bzrdir(format_name)
69
 
        else:
70
 
            format = self.format
71
 
        if not self.bzrdir.needs_format_conversion(format):
72
 
            raise errors.UpToDateFormat(self.bzrdir._format)
73
 
        if not self.bzrdir.can_convert_format():
74
 
            raise errors.BzrError("cannot upgrade from bzrdir format %s" %
75
 
                           self.bzrdir._format)
76
 
        self.bzrdir.check_conversion_target(format)
77
 
        ui.ui_factory.note('starting upgrade of %s' % self.transport.base)
78
 
 
79
 
        self.bzrdir.backup_bzrdir()
80
 
        while self.bzrdir.needs_format_conversion(format):
81
 
            converter = self.bzrdir._format.get_converter(format)
82
 
            self.bzrdir = converter.convert(self.bzrdir, None)
83
 
        ui.ui_factory.note("finished")
84
 
 
85
 
 
86
 
def upgrade(url, format=None):
87
 
    """Upgrade to format, or the default bzrdir format if not supplied."""
88
 
    Convert(url, format)
 
209
                ## assert os.path.getsize(p) == 0
 
210
                transport.delete(n)
 
211
            except NoSuchFile:
 
212
                pass
 
213
        transport.delete_tree('inventory-store')
 
214
        transport.delete_tree('text-store')
 
215
 
 
216
    def _backup_control_dir(self):
 
217
        note('making backup of tree history')
 
218
        self.transport.copy_tree('.bzr', '.bzr.backup')
 
219
        note('%s.bzr has been backed up to %s.bzr.backup',
 
220
             self.transport.base,
 
221
             self.transport.base)
 
222
        note('if conversion fails, you can move this directory back to .bzr')
 
223
        note('if it succeeds, you can remove this directory if you wish')
 
224
 
 
225
    def _convert_working_inv(self):
 
226
        branch = self.branch
 
227
        inv = serializer_v4.read_inventory(branch.controlfile('inventory', 'rb'))
 
228
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
 
229
        branch.put_controlfile('inventory', new_inv_xml)
 
230
 
 
231
    def _write_all_weaves(self):
 
232
        bzr_transport = self.transport.clone('.bzr')
 
233
        controlweaves = WeaveStore(bzr_transport, prefixed=False)
 
234
        weave_transport = bzr_transport.clone('weaves')
 
235
        weaves = WeaveStore(weave_transport, prefixed=False)
 
236
        transaction = PassThroughTransaction()
 
237
 
 
238
        controlweaves.put_weave('inventory', self.inv_weave, transaction)
 
239
        i = 0
 
240
        try:
 
241
            for file_id, file_weave in self.text_weaves.items():
 
242
                self.pb.update('writing weave', i, len(self.text_weaves))
 
243
                weaves.put_weave(file_id, file_weave, transaction)
 
244
                i += 1
 
245
        finally:
 
246
            self.pb.clear()
 
247
 
 
248
    def _write_all_revs(self):
 
249
        """Write all revisions out in new form."""
 
250
        transport = self.transport.clone('.bzr')
 
251
        transport.delete_tree('revision-store')
 
252
        transport.mkdir('revision-store')
 
253
        revision_transport = transport.clone('revision-store')
 
254
        # TODO permissions
 
255
        revision_store = TextStore(revision_transport,
 
256
                                   prefixed=False,
 
257
                                   compressed=True)
 
258
        try:
 
259
            for i, rev_id in enumerate(self.converted_revs):
 
260
                self.pb.update('write revision', i, len(self.converted_revs))
 
261
                rev_tmp = StringIO()
 
262
                serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
 
263
                rev_tmp.seek(0)
 
264
                revision_store.add(rev_tmp, rev_id)
 
265
        finally:
 
266
            self.pb.clear()
 
267
 
 
268
            
 
269
    def _load_one_rev(self, rev_id):
 
270
        """Load a revision object into memory.
 
271
 
 
272
        Any parents not either loaded or abandoned get queued to be
 
273
        loaded."""
 
274
        self.pb.update('loading revision',
 
275
                       len(self.revisions),
 
276
                       len(self.known_revisions))
 
277
        if not self.branch.revision_store.has_id(rev_id):
 
278
            self.pb.clear()
 
279
            note('revision {%s} not present in branch; '
 
280
                 'will be converted as a ghost',
 
281
                 rev_id)
 
282
            self.absent_revisions.add(rev_id)
 
283
        else:
 
284
            rev_xml = self.branch.revision_store.get(rev_id).read()
 
285
            rev = serializer_v4.read_revision_from_string(rev_xml)
 
286
            for parent_id in rev.parent_ids:
 
287
                self.known_revisions.add(parent_id)
 
288
                self.to_read.append(parent_id)
 
289
            self.revisions[rev_id] = rev
 
290
 
 
291
 
 
292
    def _load_old_inventory(self, rev_id):
 
293
        assert rev_id not in self.converted_revs
 
294
        old_inv_xml = self.branch.inventory_store.get(rev_id).read()
 
295
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
 
296
        rev = self.revisions[rev_id]
 
297
        if rev.inventory_sha1:
 
298
            assert rev.inventory_sha1 == sha_string(old_inv_xml), \
 
299
                'inventory sha mismatch for {%s}' % rev_id
 
300
        return inv
 
301
        
 
302
 
 
303
    def _load_updated_inventory(self, rev_id):
 
304
        assert rev_id in self.converted_revs
 
305
        inv_xml = self.inv_weave.get_text(rev_id)
 
306
        inv = serializer_v5.read_inventory_from_string(inv_xml)
 
307
        return inv
 
308
 
 
309
 
 
310
    def _convert_one_rev(self, rev_id):
 
311
        """Convert revision and all referenced objects to new format."""
 
312
        rev = self.revisions[rev_id]
 
313
        inv = self._load_old_inventory(rev_id)
 
314
        present_parents = [p for p in rev.parent_ids
 
315
                           if p not in self.absent_revisions]
 
316
        self._convert_revision_contents(rev, inv, present_parents)
 
317
        self._store_new_weave(rev, inv, present_parents)
 
318
        self.converted_revs.add(rev_id)
 
319
 
 
320
 
 
321
    def _store_new_weave(self, rev, inv, present_parents):
 
322
        # the XML is now updated with text versions
 
323
        if __debug__:
 
324
            for file_id in inv:
 
325
                ie = inv[file_id]
 
326
                if ie.kind == 'root_directory':
 
327
                    continue
 
328
                assert hasattr(ie, 'revision'), \
 
329
                    'no revision on {%s} in {%s}' % \
 
330
                    (file_id, rev.revision_id)
 
331
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
 
332
        new_inv_sha1 = sha_string(new_inv_xml)
 
333
        self.inv_weave.add(rev.revision_id, 
 
334
                           present_parents,
 
335
                           new_inv_xml.splitlines(True),
 
336
                           new_inv_sha1)
 
337
        rev.inventory_sha1 = new_inv_sha1
 
338
 
 
339
    def _convert_revision_contents(self, rev, inv, present_parents):
 
340
        """Convert all the files within a revision.
 
341
 
 
342
        Also upgrade the inventory to refer to the text revision ids."""
 
343
        rev_id = rev.revision_id
 
344
        mutter('converting texts of revision {%s}',
 
345
               rev_id)
 
346
        parent_invs = map(self._load_updated_inventory, present_parents)
 
347
        for file_id in inv:
 
348
            ie = inv[file_id]
 
349
            self._convert_file_version(rev, ie, parent_invs)
 
350
 
 
351
    def _convert_file_version(self, rev, ie, parent_invs):
 
352
        """Convert one version of one file.
 
353
 
 
354
        The file needs to be added into the weave if it is a merge
 
355
        of >=2 parents or if it's changed from its parent.
 
356
        """
 
357
        if ie.kind == 'root_directory':
 
358
            return
 
359
        file_id = ie.file_id
 
360
        rev_id = rev.revision_id
 
361
        w = self.text_weaves.get(file_id)
 
362
        if w is None:
 
363
            w = Weave(file_id)
 
364
            self.text_weaves[file_id] = w
 
365
        text_changed = False
 
366
        previous_entries = ie.find_previous_heads(parent_invs, w)
 
367
        for old_revision in previous_entries:
 
368
                # if this fails, its a ghost ?
 
369
                assert old_revision in self.converted_revs 
 
370
        self.snapshot_ie(previous_entries, ie, w, rev_id)
 
371
        del ie.text_id
 
372
        assert getattr(ie, 'revision', None) is not None
 
373
 
 
374
    def snapshot_ie(self, previous_revisions, ie, w, rev_id):
 
375
        # TODO: convert this logic, which is ~= snapshot to
 
376
        # a call to:. This needs the path figured out. rather than a work_tree
 
377
        # a v4 revision_tree can be given, or something that looks enough like
 
378
        # one to give the file content to the entry if it needs it.
 
379
        # and we need something that looks like a weave store for snapshot to 
 
380
        # save against.
 
381
        #ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
 
382
        if len(previous_revisions) == 1:
 
383
            previous_ie = previous_revisions.values()[0]
 
384
            if ie._unchanged(previous_ie):
 
385
                ie.revision = previous_ie.revision
 
386
                return
 
387
        parent_indexes = map(w.lookup, previous_revisions)
 
388
        if ie.has_text():
 
389
            file_lines = self.branch.text_store.get(ie.text_id).readlines()
 
390
            assert sha_strings(file_lines) == ie.text_sha1
 
391
            assert sum(map(len, file_lines)) == ie.text_size
 
392
            w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
 
393
            self.text_count += 1
 
394
        else:
 
395
            w.add(rev_id, parent_indexes, [], None)
 
396
        ie.revision = rev_id
 
397
        ##mutter('import text {%s} of {%s}',
 
398
        ##       ie.text_id, file_id)
 
399
 
 
400
    def _make_order(self):
 
401
        """Return a suitable order for importing revisions.
 
402
 
 
403
        The order must be such that an revision is imported after all
 
404
        its (present) parents.
 
405
        """
 
406
        todo = set(self.revisions.keys())
 
407
        done = self.absent_revisions.copy()
 
408
        o = []
 
409
        while todo:
 
410
            # scan through looking for a revision whose parents
 
411
            # are all done
 
412
            for rev_id in sorted(list(todo)):
 
413
                rev = self.revisions[rev_id]
 
414
                parent_ids = set(rev.parent_ids)
 
415
                if parent_ids.issubset(done):
 
416
                    # can take this one now
 
417
                    o.append(rev_id)
 
418
                    todo.remove(rev_id)
 
419
                    done.add(rev_id)
 
420
        return o
 
421
 
 
422
 
 
423
def upgrade(url):
 
424
    t = get_transport(url)
 
425
    Convert(t)