~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/upgrade.py

Merge in bzrdir work to enable checkout improvements.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
"""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
 
18
21
 
19
22
# To make this properly useful
20
23
#
65
68
# the moment saves us having to worry about when files need new
66
69
# versions.
67
70
 
68
 
 
 
71
from cStringIO import StringIO
69
72
import os
70
73
import tempfile
71
74
import sys
72
 
import shutil
 
75
from stat import *
73
76
 
74
 
from bzrlib.branch import Branch, find_branch
75
 
from bzrlib.branch import BZR_BRANCH_FORMAT_5, BZR_BRANCH_FORMAT_6
 
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
76
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
77
93
from bzrlib.weave import Weave
78
94
from bzrlib.weavefile import read_weave, write_weave
79
 
from bzrlib.ui import ui_factory
80
 
from bzrlib.atomicfile import AtomicFile
81
95
from bzrlib.xml4 import serializer_v4
82
96
from bzrlib.xml5 import serializer_v5
83
 
from bzrlib.trace import mutter, note, warning
84
 
from bzrlib.osutils import sha_strings, sha_string, pathjoin, abspath
85
97
 
86
98
 
87
99
class Convert(object):
88
 
    def __init__(self, base_dir):
89
 
        self.base = base_dir
 
100
 
 
101
    def __init__(self, transport):
 
102
        self.base = transport.base
90
103
        self.converted_revs = set()
91
104
        self.absent_revisions = set()
92
105
        self.text_count = 0
93
106
        self.revisions = {}
94
 
        self.convert()
95
 
 
 
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()
96
122
 
97
123
    def convert(self):
98
124
        if not self._open_branch():
99
125
            return
100
 
        note('starting upgrade of %s', os.path.abspath(self.base))
 
126
        note('starting upgrade of %s', self.base)
101
127
        self._backup_control_dir()
102
128
        self.pb = ui_factory.progress_bar()
103
 
        if self.old_format == 4:
 
129
        if isinstance(self.old_format, BzrDirFormat4):
104
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')
105
133
            self._convert_to_weaves()
106
 
            self._open_branch()
107
 
        if self.old_format == 5:
 
134
        if isinstance(self.old_format, BzrDirFormat5):
108
135
            note('starting upgrade from format 5 to 6')
109
136
            self._convert_to_prefixed()
110
 
            self._open_branch()
111
 
        cache = hashcache.HashCache(abspath(self.base))
112
 
        cache.clear()
113
 
        cache.write()
114
137
        note("finished")
115
138
 
116
 
 
117
139
    def _convert_to_prefixed(self):
118
140
        from bzrlib.store import hash_prefix
 
141
        bzr_transport = self.transport.clone('.bzr')
 
142
        bzr_transport.delete('branch-format')
119
143
        for store_name in ["weaves", "revision-store"]:
120
144
            note("adding prefixes to %s" % store_name) 
121
 
            store_dir = pathjoin(self.base, ".bzr", store_name)
122
 
            for filename in os.listdir(store_dir):
123
 
                if filename.endswith(".weave") or filename.endswith(".gz"):
 
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")):
124
150
                    file_id = os.path.splitext(filename)[0]
125
151
                else:
126
152
                    file_id = filename
127
 
                prefix_dir = pathjoin(store_dir, hash_prefix(file_id))
128
 
                if not os.path.isdir(prefix_dir):
129
 
                    os.mkdir(prefix_dir)
130
 
                os.rename(pathjoin(store_dir, filename),
131
 
                          pathjoin(prefix_dir, filename))
132
 
        self._set_new_format(BZR_BRANCH_FORMAT_6)
133
 
 
 
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()
134
164
 
135
165
    def _convert_to_weaves(self):
136
166
        note('note: upgrade may be faster if all store files are ungzipped first')
137
 
        if not os.path.isdir(self.base + '/.bzr/weaves'):
138
 
            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')
139
176
        self.inv_weave = Weave('inventory')
140
177
        # holds in-memory weaves for all files
141
178
        self.text_weaves = {}
142
 
        os.remove(self.branch.controlfilename('branch-format'))
 
179
        bzr_transport.delete('branch-format')
143
180
        self._convert_working_inv()
144
181
        rev_history = self.branch.revision_history()
145
182
        # to_read is a stack holding the revisions we still need to process;
157
194
            self.pb.update('converting revision', i, len(to_import))
158
195
            self._convert_one_rev(rev_id)
159
196
        self.pb.clear()
 
197
        self._write_all_weaves()
 
198
        self._write_all_revs()
160
199
        note('upgraded to weaves:')
161
200
        note('  %6d revisions and inventories' % len(self.revisions))
162
201
        note('  %6d revisions not present' % len(self.absent_revisions))
163
202
        note('  %6d texts' % self.text_count)
164
 
        self._write_all_weaves()
165
 
        self._write_all_revs()
166
 
        self._cleanup_spare_files()
167
 
        self._set_new_format(BZR_BRANCH_FORMAT_5)
168
 
 
 
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()
169
208
 
170
209
    def _open_branch(self):
171
 
        self.branch = Branch.open_downlevel(self.base)
172
 
        self.old_format = self.branch._branch_format
173
 
        if self.old_format == 6:
174
 
            note('this branch is 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)
175
215
            return False
176
 
        if self.old_format not in (4, 5):
177
 
            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" %
178
220
                           self.branch._branch_format)
179
221
        return True
180
222
 
181
 
 
182
223
    def _set_new_format(self, format):
183
 
        self.branch.put_controlfile('branch-format', format)
184
 
 
185
 
 
186
 
    def _cleanup_spare_files(self):
 
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."
187
229
        for n in 'merged-patches', 'pending-merged-patches':
188
 
            p = self.branch.controlfilename(n)
189
 
            if not os.path.exists(p):
190
 
                continue
191
 
            ## assert os.path.getsize(p) == 0
192
 
            os.remove(p)
193
 
        shutil.rmtree(self.base + '/.bzr/inventory-store')
194
 
        shutil.rmtree(self.base + '/.bzr/text-store')
195
 
 
 
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')
196
237
 
197
238
    def _backup_control_dir(self):
198
 
        orig = self.base + '/.bzr'
199
 
        backup = orig + '.backup'
200
239
        note('making backup of tree history')
201
 
        shutil.copytree(orig, backup)
202
 
        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)
203
244
        note('if conversion fails, you can move this directory back to .bzr')
204
245
        note('if it succeeds, you can remove this directory if you wish')
205
246
 
206
 
 
207
247
    def _convert_working_inv(self):
208
248
        branch = self.branch
209
 
        inv = serializer_v4.read_inventory(branch.controlfile('inventory', 'rb'))
 
249
        inv = serializer_v4.read_inventory(branch.control_files.get('inventory'))
210
250
        new_inv_xml = serializer_v5.write_inventory_to_string(inv)
211
 
        branch.put_controlfile('inventory', new_inv_xml)
212
 
 
213
 
 
 
251
        print "fixme inventory is a working tree change."
 
252
        branch.control_files.put('inventory', new_inv_xml)
214
253
 
215
254
    def _write_all_weaves(self):
216
 
        write_a_weave(self.inv_weave, self.base + '/.bzr/inventory.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)
217
262
        i = 0
218
263
        try:
219
264
            for file_id, file_weave in self.text_weaves.items():
220
265
                self.pb.update('writing weave', i, len(self.text_weaves))
221
 
                write_a_weave(file_weave, self.base + '/.bzr/weaves/%s.weave' % file_id)
 
266
                weaves.put_weave(file_id, file_weave, transaction)
222
267
                i += 1
223
268
        finally:
224
269
            self.pb.clear()
225
270
 
226
 
 
227
271
    def _write_all_revs(self):
228
272
        """Write all revisions out in new form."""
229
 
        shutil.rmtree(self.base + '/.bzr/revision-store')
230
 
        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)
231
281
        try:
232
282
            for i, rev_id in enumerate(self.converted_revs):
233
283
                self.pb.update('write revision', i, len(self.converted_revs))
234
 
                f = file(self.base + '/.bzr/revision-store/%s' % rev_id, 'wb')
235
 
                try:
236
 
                    serializer_v5.write_revision(self.revisions[rev_id], f)
237
 
                finally:
238
 
                    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)
239
288
        finally:
240
289
            self.pb.clear()
241
290
 
248
297
        self.pb.update('loading revision',
249
298
                       len(self.revisions),
250
299
                       len(self.known_revisions))
251
 
        if not self.branch.revision_store.has_id(rev_id):
 
300
        if not self.branch.repository.revision_store.has_id(rev_id):
252
301
            self.pb.clear()
253
302
            note('revision {%s} not present in branch; '
254
303
                 'will be converted as a ghost',
255
304
                 rev_id)
256
305
            self.absent_revisions.add(rev_id)
257
306
        else:
258
 
            rev_xml = self.branch.revision_store.get(rev_id).read()
 
307
            rev_xml = self.branch.repository.revision_store.get(rev_id).read()
259
308
            rev = serializer_v4.read_revision_from_string(rev_xml)
260
309
            for parent_id in rev.parent_ids:
261
310
                self.known_revisions.add(parent_id)
265
314
 
266
315
    def _load_old_inventory(self, rev_id):
267
316
        assert rev_id not in self.converted_revs
268
 
        old_inv_xml = self.branch.inventory_store.get(rev_id).read()
 
317
        old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
269
318
        inv = serializer_v4.read_inventory_from_string(old_inv_xml)
270
319
        rev = self.revisions[rev_id]
271
320
        if rev.inventory_sha1:
360
409
                return
361
410
        parent_indexes = map(w.lookup, previous_revisions)
362
411
        if ie.has_text():
363
 
            file_lines = self.branch.text_store.get(ie.text_id).readlines()
 
412
            text = self.branch.repository.text_store.get(ie.text_id)
 
413
            file_lines = text.readlines()
364
414
            assert sha_strings(file_lines) == ie.text_sha1
365
415
            assert sum(map(len, file_lines)) == ie.text_size
366
416
            w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
394
444
        return o
395
445
 
396
446
 
397
 
def write_a_weave(weave, filename):
398
 
    inv_wf = file(filename, 'wb')
399
 
    try:
400
 
        write_weave(weave, inv_wf)
401
 
    finally:
402
 
        inv_wf.close()
403
 
 
404
 
 
405
 
def upgrade(base_dir):
406
 
    Convert(base_dir)
 
447
def upgrade(url):
 
448
    t = get_transport(url)
 
449
    Convert(t)