1088
1120
copytree(self.base, base, symlinks=True)
1089
1121
return ScratchDir(
1090
1122
transport=bzrlib.transport.local.ScratchTransport(base))
1125
class Converter(object):
1126
"""Converts a disk format object from one format to another."""
1128
def __init__(self, pb):
1129
"""Create a converter.
1131
:param pb: a progress bar to use for progress information.
1136
class ConvertBzrDir4To5(Converter):
1137
"""Converts format 4 bzr dirs to format 5."""
1139
def __init__(self, to_convert, pb):
1140
"""Create a converter.
1142
:param to_convert: The disk object to convert.
1143
:param pb: a progress bar to use for progress information.
1145
super(ConvertBzrDir4To5, self).__init__(pb)
1146
self.bzrdir = to_convert
1147
self.converted_revs = set()
1148
self.absent_revisions = set()
1153
"""See Converter.convert()."""
1154
self.pb.note('starting upgrade from format 4 to 5')
1155
if isinstance(self.bzrdir.transport, LocalTransport):
1156
self.bzrdir.get_workingtree_transport(None).delete('stat-cache')
1157
self._convert_to_weaves()
1158
return BzrDir.open(self.bzrdir.root_transport.base)
1160
def _convert_to_weaves(self):
1161
self.pb.note('note: upgrade may be faster if all store files are ungzipped first')
1164
stat = self.bzrdir.transport.stat('weaves')
1165
if not S_ISDIR(stat.st_mode):
1166
self.bzrdir.transport.delete('weaves')
1167
self.bzrdir.transport.mkdir('weaves')
1168
except errors.NoSuchFile:
1169
self.bzrdir.transport.mkdir('weaves')
1170
self.inv_weave = Weave('inventory')
1171
# holds in-memory weaves for all files
1172
self.text_weaves = {}
1173
self.bzrdir.transport.delete('branch-format')
1174
self.branch = self.bzrdir.open_branch()
1175
self._convert_working_inv()
1176
rev_history = self.branch.revision_history()
1177
# to_read is a stack holding the revisions we still need to process;
1178
# appending to it adds new highest-priority revisions
1179
self.known_revisions = set(rev_history)
1180
self.to_read = rev_history[-1:]
1182
rev_id = self.to_read.pop()
1183
if (rev_id not in self.revisions
1184
and rev_id not in self.absent_revisions):
1185
self._load_one_rev(rev_id)
1187
to_import = self._make_order()
1188
for i, rev_id in enumerate(to_import):
1189
self.pb.update('converting revision', i, len(to_import))
1190
self._convert_one_rev(rev_id)
1192
self._write_all_weaves()
1193
self._write_all_revs()
1194
self.pb.note('upgraded to weaves:')
1195
self.pb.note(' %6d revisions and inventories', len(self.revisions))
1196
self.pb.note(' %6d revisions not present', len(self.absent_revisions))
1197
self.pb.note(' %6d texts', self.text_count)
1198
self._cleanup_spare_files_after_format4()
1199
self.branch.control_files.put_utf8('branch-format', BzrDirFormat5().get_format_string())
1201
def _cleanup_spare_files_after_format4(self):
1202
# FIXME working tree upgrade foo.
1203
for n in 'merged-patches', 'pending-merged-patches':
1205
## assert os.path.getsize(p) == 0
1206
self.bzrdir.transport.delete(n)
1207
except errors.NoSuchFile:
1209
self.bzrdir.transport.delete_tree('inventory-store')
1210
self.bzrdir.transport.delete_tree('text-store')
1212
def _convert_working_inv(self):
1213
inv = serializer_v4.read_inventory(self.branch.control_files.get('inventory'))
1214
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1215
# FIXME inventory is a working tree change.
1216
self.branch.control_files.put('inventory', new_inv_xml)
1218
def _write_all_weaves(self):
1219
controlweaves = WeaveStore(self.bzrdir.transport, prefixed=False)
1220
weave_transport = self.bzrdir.transport.clone('weaves')
1221
weaves = WeaveStore(weave_transport, prefixed=False)
1222
transaction = PassThroughTransaction()
1224
controlweaves.put_weave('inventory', self.inv_weave, transaction)
1227
for file_id, file_weave in self.text_weaves.items():
1228
self.pb.update('writing weave', i, len(self.text_weaves))
1229
weaves.put_weave(file_id, file_weave, transaction)
1234
def _write_all_revs(self):
1235
"""Write all revisions out in new form."""
1236
self.bzrdir.transport.delete_tree('revision-store')
1237
self.bzrdir.transport.mkdir('revision-store')
1238
revision_transport = self.bzrdir.transport.clone('revision-store')
1240
revision_store = TextStore(revision_transport,
1244
for i, rev_id in enumerate(self.converted_revs):
1245
self.pb.update('write revision', i, len(self.converted_revs))
1246
rev_tmp = StringIO()
1247
serializer_v5.write_revision(self.revisions[rev_id], rev_tmp)
1249
revision_store.add(rev_tmp, rev_id)
1254
def _load_one_rev(self, rev_id):
1255
"""Load a revision object into memory.
1257
Any parents not either loaded or abandoned get queued to be
1259
self.pb.update('loading revision',
1260
len(self.revisions),
1261
len(self.known_revisions))
1262
if not self.branch.repository.revision_store.has_id(rev_id):
1264
self.pb.note('revision {%s} not present in branch; '
1265
'will be converted as a ghost',
1267
self.absent_revisions.add(rev_id)
1269
rev_xml = self.branch.repository.revision_store.get(rev_id).read()
1270
rev = serializer_v4.read_revision_from_string(rev_xml)
1271
for parent_id in rev.parent_ids:
1272
self.known_revisions.add(parent_id)
1273
self.to_read.append(parent_id)
1274
self.revisions[rev_id] = rev
1277
def _load_old_inventory(self, rev_id):
1278
assert rev_id not in self.converted_revs
1279
old_inv_xml = self.branch.repository.inventory_store.get(rev_id).read()
1280
inv = serializer_v4.read_inventory_from_string(old_inv_xml)
1281
rev = self.revisions[rev_id]
1282
if rev.inventory_sha1:
1283
assert rev.inventory_sha1 == sha_string(old_inv_xml), \
1284
'inventory sha mismatch for {%s}' % rev_id
1288
def _load_updated_inventory(self, rev_id):
1289
assert rev_id in self.converted_revs
1290
inv_xml = self.inv_weave.get_text(rev_id)
1291
inv = serializer_v5.read_inventory_from_string(inv_xml)
1295
def _convert_one_rev(self, rev_id):
1296
"""Convert revision and all referenced objects to new format."""
1297
rev = self.revisions[rev_id]
1298
inv = self._load_old_inventory(rev_id)
1299
present_parents = [p for p in rev.parent_ids
1300
if p not in self.absent_revisions]
1301
self._convert_revision_contents(rev, inv, present_parents)
1302
self._store_new_weave(rev, inv, present_parents)
1303
self.converted_revs.add(rev_id)
1306
def _store_new_weave(self, rev, inv, present_parents):
1307
# the XML is now updated with text versions
1311
if ie.kind == 'root_directory':
1313
assert hasattr(ie, 'revision'), \
1314
'no revision on {%s} in {%s}' % \
1315
(file_id, rev.revision_id)
1316
new_inv_xml = serializer_v5.write_inventory_to_string(inv)
1317
new_inv_sha1 = sha_string(new_inv_xml)
1318
self.inv_weave.add(rev.revision_id,
1320
new_inv_xml.splitlines(True),
1322
rev.inventory_sha1 = new_inv_sha1
1324
def _convert_revision_contents(self, rev, inv, present_parents):
1325
"""Convert all the files within a revision.
1327
Also upgrade the inventory to refer to the text revision ids."""
1328
rev_id = rev.revision_id
1329
mutter('converting texts of revision {%s}',
1331
parent_invs = map(self._load_updated_inventory, present_parents)
1334
self._convert_file_version(rev, ie, parent_invs)
1336
def _convert_file_version(self, rev, ie, parent_invs):
1337
"""Convert one version of one file.
1339
The file needs to be added into the weave if it is a merge
1340
of >=2 parents or if it's changed from its parent.
1342
if ie.kind == 'root_directory':
1344
file_id = ie.file_id
1345
rev_id = rev.revision_id
1346
w = self.text_weaves.get(file_id)
1349
self.text_weaves[file_id] = w
1350
text_changed = False
1351
previous_entries = ie.find_previous_heads(parent_invs, w)
1352
for old_revision in previous_entries:
1353
# if this fails, its a ghost ?
1354
assert old_revision in self.converted_revs
1355
self.snapshot_ie(previous_entries, ie, w, rev_id)
1357
assert getattr(ie, 'revision', None) is not None
1359
def snapshot_ie(self, previous_revisions, ie, w, rev_id):
1360
# TODO: convert this logic, which is ~= snapshot to
1361
# a call to:. This needs the path figured out. rather than a work_tree
1362
# a v4 revision_tree can be given, or something that looks enough like
1363
# one to give the file content to the entry if it needs it.
1364
# and we need something that looks like a weave store for snapshot to
1366
#ie.snapshot(rev, PATH, previous_revisions, REVISION_TREE, InMemoryWeaveStore(self.text_weaves))
1367
if len(previous_revisions) == 1:
1368
previous_ie = previous_revisions.values()[0]
1369
if ie._unchanged(previous_ie):
1370
ie.revision = previous_ie.revision
1372
parent_indexes = map(w.lookup, previous_revisions)
1374
text = self.branch.repository.text_store.get(ie.text_id)
1375
file_lines = text.readlines()
1376
assert sha_strings(file_lines) == ie.text_sha1
1377
assert sum(map(len, file_lines)) == ie.text_size
1378
w.add(rev_id, parent_indexes, file_lines, ie.text_sha1)
1379
self.text_count += 1
1381
w.add(rev_id, parent_indexes, [], None)
1382
ie.revision = rev_id
1383
##mutter('import text {%s} of {%s}',
1384
## ie.text_id, file_id)
1386
def _make_order(self):
1387
"""Return a suitable order for importing revisions.
1389
The order must be such that an revision is imported after all
1390
its (present) parents.
1392
todo = set(self.revisions.keys())
1393
done = self.absent_revisions.copy()
1396
# scan through looking for a revision whose parents
1398
for rev_id in sorted(list(todo)):
1399
rev = self.revisions[rev_id]
1400
parent_ids = set(rev.parent_ids)
1401
if parent_ids.issubset(done):
1402
# can take this one now
1409
class ConvertBzrDir5To6(Converter):
1410
"""Converts format 5 bzr dirs to format 6."""
1412
def __init__(self, to_convert, pb):
1413
"""Create a converter.
1415
:param to_convert: The disk object to convert.
1416
:param pb: a progress bar to use for progress information.
1418
super(ConvertBzrDir5To6, self).__init__(pb)
1419
self.bzrdir = to_convert
1422
"""See Converter.convert()."""
1423
self.pb.note('starting upgrade from format 5 to 6')
1424
self._convert_to_prefixed()
1425
return BzrDir.open(self.bzrdir.root_transport.base)
1427
def _convert_to_prefixed(self):
1428
from bzrlib.store import hash_prefix
1429
self.bzrdir.transport.delete('branch-format')
1430
for store_name in ["weaves", "revision-store"]:
1431
self.pb.note("adding prefixes to %s" % store_name)
1432
store_transport = self.bzrdir.transport.clone(store_name)
1433
for filename in store_transport.list_dir('.'):
1434
if (filename.endswith(".weave") or
1435
filename.endswith(".gz") or
1436
filename.endswith(".sig")):
1437
file_id = os.path.splitext(filename)[0]
1440
prefix_dir = hash_prefix(file_id)
1441
# FIXME keep track of the dirs made RBC 20060121
1443
store_transport.move(filename, prefix_dir + '/' + filename)
1444
except errors.NoSuchFile: # catches missing dirs strangely enough
1445
store_transport.mkdir(prefix_dir)
1446
store_transport.move(filename, prefix_dir + '/' + filename)
1447
self.bzrdir._control_files.put_utf8('branch-format', BzrDirFormat6().get_format_string())