/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

  • Committer: Robert Collins
  • Date: 2005-10-16 22:31:25 UTC
  • mto: This revision was merged to the branch mainline in revision 1458.
  • Revision ID: robertc@lifelesslap.robertcollins.net-20051016223125-26d4401cb94b7b82
Branch.relpath has been moved to WorkingTree.relpath.

WorkingTree no no longer takes an inventory, rather it takes an optional branch
parameter, and if None is given will open the branch at basedir implicitly.

Show diffs side-by-side

added added

removed removed

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