1183
1183
# incremental algorithm:
1184
1184
# two iterators: current data and new data, both in dirblock order.
1185
1185
new_iterator = new_inv.iter_entries_by_dir()
1186
# we will be modifying the dirstate, so we need a stable iterator. In future we might write one, for now we just clone the state into a list
1186
# we will be modifying the dirstate, so we need a stable iterator. In
1187
# future we might write one, for now we just clone the state into a
1188
# list - which is a shallow copy, so each
1187
1189
old_iterator = iter(list(self._iter_entries()))
1188
1190
# both must have roots so this is safe:
1189
1191
current_new = new_iterator.next()
1190
1192
current_old = old_iterator.next()
1191
1193
def advance(iterator):
1193
return old_iterator.next()
1195
return iterator.next()
1194
1196
except StopIteration:
1196
1198
while current_new or current_old:
1197
1199
# skip entries in old that are not really there
1198
if current_old and current_old[1][0] in ('relocated', 'absent'):
1200
if current_old and current_old[1][0][0] in ('relocated', 'absent'):
1199
1201
current_old = advance(old_iterator)
1201
1203
if current_new:
1202
1204
# convert new into dirblock style
1203
new_dirname, new_basename = os.path.split(current_new[0].encode('utf8'))
1205
new_path_utf8 = current_new[0].encode('utf8')
1206
new_dirname, new_basename = os.path.split(new_path_utf8)
1204
1207
new_id = current_new[1].file_id.encode('utf8')
1205
1208
new_entry_key = (new_dirname, new_basename, new_id)
1210
# for safety disable variables
1211
new_path_utf8 = new_dirname = new_basename = new_id = new_entry_key = None
1206
1212
# 5 cases, we dont have a value that is strictly greater than everything, so
1207
1213
# we make both end conditions explicit
1208
1214
if not current_old:
1209
1215
# old is finished: insert current_new into the state.
1210
1216
self.update_minimal(new_entry_key, current_new[1].kind,
1211
executable=current_new[1].executable, id_index=id_index)
1217
num_present_parents, executable=current_new[1].executable,
1218
id_index=id_index, path_utf8=new_path_utf8)
1212
1219
current_new = advance(new_iterator)
1213
1220
elif not current_new:
1214
1221
# new is finished
1215
import pdb;pdb.set_trace()
1222
self._make_absent(num_present_parents, current_old, id_index)
1223
current_old = advance(old_iterator)
1216
1224
elif new_entry_key == current_old[0]:
1217
1225
# same - common case
1218
1226
# TODO: update the record if anything significant has changed.
1219
# the minimal required trigger is if the execute bit has
1221
if current_old[1][0][3] != current_new[1].executable:
1222
import pdb;pdb.set_trace()
1227
# the minimal required trigger is if the execute bit or cached
1229
if (current_old[1][0][3] != current_new[1].executable or
1230
current_old[1][0][0] != current_new[1].kind):
1231
self.update_minimal(current_old[0], current_new[1].kind,
1232
num_present_parents,
1233
executable=current_new[1].executable,
1234
id_index=id_index, path_utf8=new_path_utf8)
1223
1235
# both sides are dealt with, move on
1224
1236
current_old = advance(old_iterator)
1225
1237
current_new = advance(new_iterator)
1226
1238
elif new_entry_key < current_old[0]:
1228
import pdb;pdb.set_trace()
1240
# add a entry for this and advance new
1241
self.update_minimal(new_entry_key, current_new[1].kind,
1242
num_present_parents, executable=current_new[1].executable,
1243
id_index=id_index, path_utf8=new_path_utf8)
1244
current_new = advance(new_iterator)
1230
1246
# old comes before:
1231
# remove old from the state, advance old
1232
# to remove old, we have two conditions.
1233
# either its the last reference to this path that we are
1234
# removing, or its not. If its the last reference, we remove
1235
# the entire row and remove the path from the id mapping. If
1236
# its not the last reference, we just set it to absent.
1237
last_reference = True
1238
for lookup_index in xrange(1, num_present_parents + 1):
1239
if current_old[1][lookup_index] not in ('absent', 'relocated'):
1240
last_reference = False
1242
if not last_reference:
1243
import pdb;pdb.set_trace()
1244
# common case, theres a parent at this path
1245
current_old[1][0] = DirState.NULL_PARENT_DETAILS
1247
# there are no more references at this path
1248
id_index[current_old[0][2]].remove(current_old[0])
1249
# are there others (which will need to be changed
1250
# from relocated to absent for index 0)?
1251
if len(id_index[current_old[0][2]]):
1252
import pdb;pdb.set_trace()
1253
block = self._find_block(current_old[0])
1254
entry_index, present = self._find_entry_index(key, block)
1256
block.pop(entry_index)
1247
self._make_absent(num_present_parents, current_old, id_index)
1257
1248
current_old = advance(old_iterator)
1258
1249
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1260
def update_minimal(self, key, kind, executable=False, fingerprint='',
1261
packed_stat=None, size=0, id_index=None):
1251
def _make_absent(self, num_present_parents, current_old, id_index):
1252
# remove old from the state, advance old
1253
# to remove old, we have two conditions:
1254
# either its the last reference to this path that we are
1255
# removing, or its not. If its the last reference, we remove
1256
# the entire row and remove the path from the id mapping. If
1257
# its not the last reference, we just set it to absent.
1258
last_reference = True
1259
for lookup_index in xrange(1, num_present_parents + 1):
1260
if current_old[1][lookup_index] not in ('absent', 'relocated'):
1261
last_reference = False
1263
if not last_reference:
1264
# common case, theres a parent at this path
1265
current_old[1][0] = DirState.NULL_PARENT_DETAILS
1267
# there are no more references at this path
1268
id_index[current_old[0][2]].remove(current_old[0])
1269
# are there others (which will need to be changed
1270
# from relocated to absent for index 0)?
1271
for update_key in id_index[current_old[0][2]]:
1272
# update the entry for 0 to say absent: there is a parent at
1273
# that path, but nothing in this tree for that file id anymore.
1274
update_block_index, present = \
1275
self._find_block_index_from_key(update_key)
1277
update_entry_index, present = \
1278
self._find_entry_index(update_key, self._dirblocks[update_block_index][1])
1280
update_tree_details = self._dirblocks[update_block_index][1][update_entry_index][1]
1281
# it must currently be relocated
1282
assert update_tree_details[0][0] == 'relocated'
1283
update_tree_details[0] = DirState.NULL_PARENT_DETAILS
1284
block = self._find_block(current_old[0])
1285
entry_index, present = self._find_entry_index(current_old[0], block[1])
1287
block[1].pop(entry_index)
1289
def update_minimal(self, key, kind, num_present_parents, executable=False,
1290
fingerprint='', packed_stat=None, size=0, id_index=None,
1262
1292
"""Update an entry to the state in tree 0."""
1263
self._read_dirblocks_if_needed()
1264
1293
if key[0:2] == ('', ''):
1265
1294
block = self._root_entries
1269
1298
packed_stat = DirState.NULLSTAT
1270
1299
entry_index, present = self._find_entry_index(key, block)
1271
1300
new_details = (kind, fingerprint, size, executable, packed_stat)
1301
assert id_index, 'need an id index to do updates for now !'
1272
1302
if not present:
1273
1303
# new entry, synthesis cross reference here,
1274
assert id_index, 'need an id index to generate a new entry'
1275
existing_keys = id_index.setdefault(key, set())
1304
existing_keys = id_index.setdefault(key[2], set())
1276
1305
if not existing_keys:
1277
1306
# not currently in the state, simplest case
1278
1307
new_entry = key, [new_details] + self._empty_parent_info()
1280
import pdb;pdb.set_trace()
1309
# present at one or more existing other paths.
1310
# grab one of them and use it to generate parent
1311
# relocation/absent entries.
1312
new_entry = key, [new_details]
1313
for other_key in existing_keys:
1314
# change the record at other to be a pointer to this new
1315
# record. The loop looks similar to the change to
1316
# relocations when updating an existing record but its not:
1317
# the test for existing kinds is different: this can be
1318
# factored out to a helper though.
1319
other_block_index, present = self._find_block_index_from_key(other_key)
1321
other_entry_index, present = self._find_entry_index(other_key, self._dirblocks[other_block_index][1])
1323
self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
1324
('relocated', path_utf8, 0, False, '')
1326
for lookup_index in xrange(1, num_present_parents + 1):
1327
# grab any one entry, use it to find the right path.
1328
# TODO: optimise this to reduce memory use in highly
1329
# fragmented situations by reusing the relocation
1331
update_block_index, present = \
1332
self._find_block_index_from_key(other_key)
1334
update_entry_index, present = \
1335
self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
1337
update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
1338
if update_details[0] in ('relocated', 'absent'):
1339
# its a pointer or absent in lookup_index's tree, use
1341
new_entry[1].append(update_details)
1343
# we have the right key, make a pointer to it.
1344
pointer_path = os.path.join(other_key[0:2])
1345
new_details.append(('relocated', pointer_path, 0, False, ''))
1282
1346
block.insert(entry_index, new_entry)
1283
1347
existing_keys.add(key)
1285
1349
# Does the new state matter?
1286
import pdb;pdb.set_trace()
1287
1350
block[entry_index][1][0] = new_details
1351
# parents cannot be affected by what we do.
1352
# other occurences of this id can be found
1353
# from the id index.
1355
# tree index consistency: All other paths for this id in this tree
1356
# index must point to the correct path. We have to loop here because
1357
# we may have passed entries in the state with this file id already
1358
# that were absent - where parent entries are - and they need to be
1359
# converted to relocated.
1360
assert path_utf8 is not None
1361
for entry_key in id_index.setdefault(key[2], set()):
1362
# TODO:PROFILING: It might be faster to just update
1363
# rather than checking if we need to, and then overwrite
1364
# the one we are located at.
1365
if entry_key != key:
1366
# this file id is at a different path in one of the
1367
# other trees, so put absent pointers there
1368
# This is the vertical axis in the matrix, all pointing
1370
block_index, present = self._find_block_index_from_key(entry_key)
1372
entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
1374
self._dirblocks[block_index][1][entry_index][1][0] = \
1375
('relocated', path_utf8, 0, False, '')
1288
1377
self._dirblock_state = DirState.IN_MEMORY_MODIFIED