953
1164
copytree(self.base, base, symlinks=True)
954
1165
return ScratchDir(
955
1166
transport=bzrlib.transport.local.ScratchTransport(base))
1169
class Converter(object):
1170
"""Converts a disk format object from one format to another."""
1172
def convert(self, to_convert, pb):
1173
"""Perform the conversion of to_convert, giving feedback via pb.
1175
:param to_convert: The disk object to convert.
1176
:param pb: a progress bar to use for progress information.
1180
class ConvertBzrDir4To5(Converter):
1181
"""Converts format 4 bzr dirs to format 5."""
1184
super(ConvertBzrDir4To5, self).__init__()
1185
self.converted_revs = set()
1186
self.absent_revisions = set()
1190
def convert(self, to_convert, pb):
1191
"""See Converter.convert()."""
1192
self.bzrdir = to_convert
1194
self.pb.note('starting upgrade from format 4 to 5')
1195
if isinstance(self.bzrdir.transport, LocalTransport):
1196
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1197
self._convert_to_weaves()
1198
return BzrDir.open(self.bzrdir.root_transport.base)
1200
def _convert_to_weaves(self):
1201
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1204
stat = self.bzrdir.transport.stat('weaves')
1205
if not S_ISDIR(stat.st_mode):
1206
self.bzrdir.transport.delete('weaves')
1207
self.bzrdir.transport.mkdir('weaves')
1208
except errors.NoSuchFile:
1209
self.bzrdir.transport.mkdir('weaves')
1210
self.inv_weave = Weave('inventory')
1211
# holds in-memory weaves for all files
1212
self.text_weaves = {}
1213
self.bzrdir.transport.delete('branch-format')
1214
self.branch = self.bzrdir.open_branch()
1215
self._convert_working_inv()
1216
rev_history = self.branch.revision_history()
1217
# to_read is a stack holding the revisions we still need to process;
1218
# appending to it adds new highest-priority revisions
1219
self.known_revisions = set(rev_history)
1220
self.to_read = rev_history[-1:]
1222
rev_id = self.to_read.pop()
1223
if (rev_id not in self.revisions
1224
and rev_id not in self.absent_revisions):
1225
self._load_one_rev(rev_id)
1227
to_import = self._make_order()
1228
for i, rev_id in enumerate(to_import):
1229
self.pb.update('converting revision', i, len(to_import))
1230
self._convert_one_rev(rev_id)
1232
self._write_all_weaves()
1233
self._write_all_revs()
1234
self.pb.note('upgraded to weaves:')
1235
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1236
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1237
self.pb.note(' %6d texts', self.text_count)
1238
self._cleanup_spare_files_after_format4()
1239
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1241
def _cleanup_spare_files_after_format4(self):
1242
# FIXME working tree upgrade foo.
1243
for n in 'merged-patches', 'pending-merged-patches':
1245
## assert os.path.getsize(p) == 0
1246
self.bzrdir.transport.delete(n)
1247
except errors.NoSuchFile:
1249
self.bzrdir.transport.delete_tree('inventory-store')
1250
self.bzrdir.transport.delete_tree('text-store')
1252
def _convert_working_inv(self):
1253
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1254
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1255
# FIXME inventory is a working tree change.
1256
self.branch.control_files.put('inventory', new_inv_xml)
1258
def _write_all_weaves(self):
1259
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1260
weave_transport = self.bzrdir.transport.clone('weaves')
1261
weaves = WeaveStore(weave_transport, prefixed=False)
1262
transaction = PassThroughTransaction()
1264
controlweaves.put_weave('inventory', self.inv_weave, transaction)
1267
for file_id, file_weave in self.text_weaves.items():
1268
self.pb.update('writing weave', i, len(self.text_weaves))
1269
weaves.put_weave(file_id, file_weave, transaction)
1274
def _write_all_revs(self):
1275
"""Write all revisions out in new form."""
1276
self.bzrdir.transport.delete_tree('revision-store')
1277
self.bzrdir.transport.mkdir('revision-store')
1278
revision_transport = self.bzrdir.transport.clone('revision-store')
1280
revision_store = TextStore(revision_transport,
1284
for i, rev_id in enumerate(self.converted_revs):
1285
self.pb.update('write revision', i, len(self.converted_revs))
1286
rev_tmp = StringIO()
1287
serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
1289
revision_store.add(rev_tmp, rev_id)
1293
def _load_one_rev(self, rev_id):
1294
"""Load a revision object into memory.
1296
Any parents not either loaded or abandoned get queued to be
1298
self.pb.update('loading revision',
1299
len(self.revisions),
1300
len(self.known_revisions))
1301
if not self.branch.repository.revision_store.has_id(rev_id):
1303
self.pb.note('revision {%s} not present in branch; '
1304
'will be converted as a ghost',
1306
self.absent_revisions.add(rev_id)
1308
rev_xml = self.branch.repository.revision_store.get(rev_id).read()
1309
rev = serializer_v4.read_revision_from_string(rev_xml)
1310
for parent_id in rev.parent_ids:
1311
self.known_revisions.add(parent_id)
1312
self.to_read.append(parent_id)
1313
self.revisions[rev_id] = rev
1315
def _load_old_inventory(self, rev_id):
1316
assert rev_id not in self.converted_revs
1317
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1318
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1319
rev = self.revisions[rev_id]
1320
if rev.inventory_sha1:
1321
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1322
'inventory sha mismatch for {%s}' % rev_id
1325
def _load_updated_inventory(self, rev_id):
1326
assert rev_id in self.converted_revs
1327
inv_xml = self.inv_weave.get_text(rev_id)
1328
inv = serializer_v5.read_inventory_from_string(inv_xml)
1331
def _convert_one_rev(self, rev_id):
1332
"""Convert revision and all referenced objects to new format."""
1333
rev = self.revisions[rev_id]
1334
inv = self._load_old_inventory(rev_id)
1335
present_parents = [p for p in rev.parent_ids
1336
if p not in self.absent_revisions]
1337
self._convert_revision_contents(rev, inv, present_parents)
1338
self._store_new_weave(rev, inv, present_parents)
1339
self.converted_revs.add(rev_id)
1341
def _store_new_weave(self, rev, inv, present_parents):
1342
# the XML is now updated with text versions
1346
if ie.kind == 'root_directory':
1348
assert hasattr(ie, 'revision'), \
1349
'no revision on {%s} in {%s}' % \
1350
(file_id, rev.revision_id)
1351
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1352
new_inv_sha1 = sha_string(new_inv_xml)
1353
self.inv_weave.add(rev.revision_id,
1355
new_inv_xml.splitlines(True),
1357
rev.inventory_sha1 = new_inv_sha1
1359
def _convert_revision_contents(self, rev, inv, present_parents):
1360
"""Convert all the files within a revision.
1362
Also upgrade the inventory to refer to the text revision ids."""
1363
rev_id = rev.revision_id
1364
mutter('converting texts of revision {%s}',
1366
parent_invs = map(self._load_updated_inventory, present_parents)
1369
self._convert_file_version(rev, ie, parent_invs)
1371
def _convert_file_version(self, rev, ie, parent_invs):
1372
"""Convert one version of one file.
1374
The file needs to be added into the weave if it is a merge
1375
of >=2 parents or if it's changed from its parent.
1377
if ie.kind == 'root_directory':
1379
file_id = ie.file_id
1380
rev_id = rev.revision_id
1381
w = self.text_weaves.get(file_id)
1384
self.text_weaves[file_id] = w
1385
text_changed = False
1386
previous_entries = ie.find_previous_heads(parent_invs, w)
1387
for old_revision in previous_entries:
1388
# if this fails, its a ghost ?
1389
assert old_revision in self.converted_revs
1390
self.snapshot_ie(previous_entries, ie, w, rev_id)
1392
assert getattr(ie, 'revision', None) is not None
1394
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1395
# TODO: convert this logic, which is ~= snapshot to
1396
# a call to:. This needs the path figured out. rather than a work_tree
1397
# a v4 revision_tree can be given, or something that looks enough like
1398
# one to give the file content to the entry if it needs it.
1399
# and we need something that looks like a weave store for snapshot to
1401
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1402
if len(previous_revisions) == 1:
1403
previous_ie = previous_revisions.values()[0]
1404
if ie._unchanged(previous_ie):
1405
ie.revision = previous_ie.revision
1407
parent_indexes = map(w.lookup, previous_revisions)
1409
text = self.branch.repository.text_store.get(ie.text_id)
1410
file_lines = text.readlines()
1411
assert sha_strings(file_lines) == ie.text_sha1
1412
assert sum(map(len, file_lines)) == ie.text_size
1413
w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
1414
self.text_count += 1
1416
w.add(rev_id, parent_indexes, [], None)
1417
ie.revision = rev_id
1419
def _make_order(self):
1420
"""Return a suitable order for importing revisions.
1422
The order must be such that an revision is imported after all
1423
its (present) parents.
1425
todo = set(self.revisions.keys())
1426
done = self.absent_revisions.copy()
1429
# scan through looking for a revision whose parents
1431
for rev_id in sorted(list(todo)):
1432
rev = self.revisions[rev_id]
1433
parent_ids = set(rev.parent_ids)
1434
if parent_ids.issubset(done):
1435
# can take this one now
1436
order.append(rev_id)
1442
class ConvertBzrDir5To6(Converter):
1443
"""Converts format 5 bzr dirs to format 6."""
1445
def convert(self, to_convert, pb):
1446
"""See Converter.convert()."""
1447
self.bzrdir = to_convert
1449
self.pb.note('starting upgrade from format 5 to 6')
1450
self._convert_to_prefixed()
1451
return BzrDir.open(self.bzrdir.root_transport.base)
1453
def _convert_to_prefixed(self):
1454
from bzrlib.store import hash_prefix
1455
self.bzrdir.transport.delete('branch-format')
1456
for store_name in ["weaves", "revision-store"]:
1457
self.pb.note("adding prefixes to %s" % store_name)
1458
store_transport = self.bzrdir.transport.clone(store_name)
1459
for filename in store_transport.list_dir('.'):
1460
if (filename.endswith(".weave") or
1461
filename.endswith(".gz") or
1462
filename.endswith(".sig")):
1463
file_id = os.path.splitext(filename)[0]
1466
prefix_dir = hash_prefix(file_id)
1467
# FIXME keep track of the dirs made RBC 20060121
1469
store_transport.move(filename, prefix_dir + '/' + filename)
1470
except errors.NoSuchFile: # catches missing dirs strangely enough
1471
store_transport.mkdir(prefix_dir)
1472
store_transport.move(filename, prefix_dir + '/' + filename)
1473
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())
1476
class ConvertBzrDir6ToMeta(Converter):
1477
"""Converts format 6 bzr dirs to metadirs."""
1479
def convert(self, to_convert, pb):
1480
"""See Converter.convert()."""
1481
self.bzrdir = to_convert
1484
self.total = 20 # the steps we know about
1485
self.garbage_inventories = []
1487
self.pb.note('starting upgrade from format 6 to metadir')
1488
self.bzrdir._control_files.put_utf8('branch-format', "Converting to format 6")
1489
# its faster to move specific files around than to open and use the apis...
1490
# first off, nuke ancestry.weave, it was never used.
1492
self.step('Removing ancestry.weave')
1493
self.bzrdir.transport.delete('ancestry.weave')
1494
except errors.NoSuchFile:
1496
# find out whats there
1497
self.step('Finding branch files')
1498
last_revision = self.bzrdir.open_workingtree().last_revision()
1499
bzrcontents = self.bzrdir.transport.list_dir('.')
1500
for name in bzrcontents:
1501
if name.startswith('basis-inventory.'):
1502
self.garbage_inventories.append(name)
1503
# create new directories for repository, working tree and branch
1504
dir_mode = self.bzrdir._control_files._dir_mode
1505
self.file_mode = self.bzrdir._control_files._file_mode
1506
repository_names = [('inventory.weave', True),
1507
('revision-store', True),
1509
self.step('Upgrading repository ')
1510
self.bzrdir.transport.mkdir('repository', mode=dir_mode)
1511
self.make_lock('repository')
1512
# we hard code the formats here because we are converting into
1513
# the meta format. The meta format upgrader can take this to a
1514
# future format within each component.
1515
self.put_format('repository', bzrlib.repository.RepositoryFormat7())
1516
for entry in repository_names:
1517
self.move_entry('repository', entry)
1519
self.step('Upgrading branch ')
1520
self.bzrdir.transport.mkdir('branch', mode=dir_mode)
1521
self.make_lock('branch')
1522
self.put_format('branch', bzrlib.branch.BzrBranchFormat5())
1523
branch_files = [('revision-history', True),
1524
('branch-name', True),
1526
for entry in branch_files:
1527
self.move_entry('branch', entry)
1529
self.step('Upgrading working tree')
1530
self.bzrdir.transport.mkdir('checkout', mode=dir_mode)
1531
self.make_lock('checkout')
1532
self.put_format('checkout', bzrlib.workingtree.WorkingTreeFormat3())
1533
self.bzrdir.transport.delete_multi(self.garbage_inventories, self.pb)
1534
checkout_files = [('pending-merges', True),
1535
('inventory', True),
1536
('stat-cache', False)]
1537
for entry in checkout_files:
1538
self.move_entry('checkout', entry)
1539
if last_revision is not None:
1540
self.bzrdir._control_files.put_utf8('checkout/last-revision',
1542
self.bzrdir._control_files.put_utf8('branch-format', BzrDirMetaFormat1().get_format_string())
1543
return BzrDir.open(self.bzrdir.root_transport.base)
1545
def make_lock(self, name):
1546
"""Make a lock for the new control dir name."""
1547
self.step('Make %s lock' % name)
1548
self.bzrdir.transport.put('%s/lock' % name, StringIO(), mode=self.file_mode)
1550
def move_entry(self, new_dir, entry):
1551
"""Move then entry name into new_dir."""
1553
mandatory = entry[1]
1554
self.step('Moving %s' % name)
1556
self.bzrdir.transport.move(name, '%s/%s' % (new_dir, name))
1557
except errors.NoSuchFile:
1561
def put_format(self, dirname, format):
1562
self.bzrdir._control_files.put_utf8('%s/format' % dirname, format.get_format_string())
1564
def step(self, message):
1565
"""Update the pb by a step."""
1567
self.pb.update('Upgrading repository ', self.count, self.total)