1277
1278
def update_by_delta(self, delta):
1278
1279
"""Apply an inventory delta to the dirstate for tree 0
1281
This is the workhorse for apply_inventory_delta in dirstate based
1280
1284
:param delta: An inventory delta. See Inventory.apply_delta for
1283
1287
self._read_dirblocks_if_needed()
1288
encode = cache_utf8.encode
1284
1289
insertions = {}
1286
for old_path, new_path, file_id, inv_entry in sorted(delta, reverse=True):
1291
# Accumulate parent references (path_utf8, id), to check for parentless
1292
# items or items placed under files/links/tree-references. We get
1293
# references from every item in the delta that is not a deletion and
1294
# is not itself the root.
1296
# Added ids must not be in the dirstate already. This set holds those
1299
# This loop transforms the delta to single atomic operations that can
1300
# be executed and validated.
1301
for old_path, new_path, file_id, inv_entry in sorted(
1302
inventory._check_delta_unique_old_paths(
1303
inventory._check_delta_unique_new_paths(
1304
inventory._check_delta_ids_match_entry(
1305
inventory._check_delta_ids_are_valid(
1306
inventory._check_delta_new_path_entry_both_or_None(delta))))),
1287
1308
if (file_id in insertions) or (file_id in removals):
1288
1309
raise errors.InconsistentDelta(old_path or new_path, file_id,
1289
1310
"repeated file_id")
1290
1311
if old_path is not None:
1291
1312
old_path = old_path.encode('utf-8')
1292
1313
removals[file_id] = old_path
1315
new_ids.add(file_id)
1293
1316
if new_path is not None:
1317
if inv_entry is None:
1318
raise errors.InconsistentDelta(new_path, file_id,
1319
"new_path with no entry")
1294
1320
new_path = new_path.encode('utf-8')
1295
dirname, basename = osutils.split(new_path)
1296
key = (dirname, basename, file_id)
1321
dirname_utf8, basename = osutils.split(new_path)
1323
parents.add((dirname_utf8, inv_entry.parent_id))
1324
key = (dirname_utf8, basename, file_id)
1297
1325
minikind = DirState._kind_to_minikind[inv_entry.kind]
1298
1326
if minikind == 't':
1299
1327
fingerprint = inv_entry.reference_revision
1321
1349
child_basename)
1322
1350
insertions[child[0][2]] = (key, minikind, executable,
1323
1351
fingerprint, new_child_path)
1324
self._apply_removals(removals.values())
1325
self._apply_insertions(insertions.values())
1352
self._check_delta_ids_absent(new_ids, delta, 0)
1354
self._apply_removals(removals.iteritems())
1355
self._apply_insertions(insertions.values())
1357
self._after_delta_check_parents(parents, 0)
1358
except errors.BzrError, e:
1359
self._changes_aborted = True
1360
if 'integrity error' not in str(e):
1362
# _get_entry raises BzrError when a request is inconsistent; we
1363
# want such errors to be shown as InconsistentDelta - and that
1364
# fits the behaviour we trigger.
1365
raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1327
1367
def _apply_removals(self, removals):
1328
for path in sorted(removals, reverse=True):
1368
for file_id, path in sorted(removals, reverse=True,
1369
key=operator.itemgetter(1)):
1329
1370
dirname, basename = osutils.split(path)
1330
1371
block_i, entry_i, d_present, f_present = \
1331
1372
self._get_block_entry_index(dirname, basename, 0)
1332
entry = self._dirblocks[block_i][1][entry_i]
1374
entry = self._dirblocks[block_i][1][entry_i]
1376
self._changes_aborted = True
1377
raise errors.InconsistentDelta(path, file_id,
1378
"Wrong path for old path.")
1379
if not f_present or entry[1][0][0] in 'ar':
1380
self._changes_aborted = True
1381
raise errors.InconsistentDelta(path, file_id,
1382
"Wrong path for old path.")
1383
if file_id != entry[0][2]:
1384
self._changes_aborted = True
1385
raise errors.InconsistentDelta(path, file_id,
1386
"Attempt to remove path has wrong id - found %r."
1333
1388
self._make_absent(entry)
1334
1389
# See if we have a malformed delta: deleting a directory must not
1335
1390
# leave crud behind. This increases the number of bisects needed
1343
1398
# be due to it being in a parent tree, or a corrupt delta.
1344
1399
for child_entry in self._dirblocks[block_i][1]:
1345
1400
if child_entry[1][0][0] not in ('r', 'a'):
1401
self._changes_aborted = True
1346
1402
raise errors.InconsistentDelta(path, entry[0][2],
1347
1403
"The file id was deleted but its children were "
1348
1404
"not deleted.")
1350
1406
def _apply_insertions(self, adds):
1351
for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1352
self.update_minimal(key, minikind, executable, fingerprint,
1353
path_utf8=path_utf8)
1408
for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1409
self.update_minimal(key, minikind, executable, fingerprint,
1410
path_utf8=path_utf8)
1411
except errors.NotVersionedError:
1412
self._changes_aborted = True
1413
raise errors.InconsistentDelta(path_utf8.decode('utf8'), key[2],
1355
1416
def update_basis_by_delta(self, delta, new_revid):
1356
1417
"""Update the parents of this tree after a commit.
1400
1461
# At the same time, to reduce interface friction we convert the input
1401
1462
# inventory entries to dirstate.
1402
1463
root_only = ('', '')
1403
# Accumulate parent references (path and id), to check for parentless
1464
# Accumulate parent references (path_utf8, id), to check for parentless
1404
1465
# items or items placed under files/links/tree-references. We get
1405
1466
# references from every item in the delta that is not a deletion and
1406
1467
# is not itself the root.
1407
1468
parents = set()
1469
# Added ids must not be in the dirstate already. This set holds those
1408
1472
for old_path, new_path, file_id, inv_entry in delta:
1409
1473
if inv_entry is not None and file_id != inv_entry.file_id:
1410
1474
raise errors.InconsistentDelta(new_path, file_id,
1411
1475
"mismatched entry file_id %r" % inv_entry)
1412
1476
if new_path is not None:
1477
if inv_entry is None:
1478
raise errors.InconsistentDelta(new_path, file_id,
1479
"new_path with no entry")
1413
1480
new_path_utf8 = encode(new_path)
1414
1481
# note the parent for validation
1415
1482
dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
1418
1485
if old_path is None:
1419
1486
adds.append((None, encode(new_path), file_id,
1420
1487
inv_to_entry(inv_entry), True))
1488
new_ids.add(file_id)
1421
1489
elif new_path is None:
1422
1490
deletes.append((encode(old_path), None, file_id, None, True))
1423
1491
elif (old_path, new_path) != root_only:
1466
1534
# of everything.
1467
1535
changes.append((encode(old_path), encode(new_path), file_id,
1468
1536
inv_to_entry(inv_entry)))
1537
self._check_delta_ids_absent(new_ids, delta, 1)
1471
1539
# Finish expunging deletes/first half of renames.
1472
1540
self._update_basis_apply_deletes(deletes)
1475
1543
# Apply in-situ changes.
1476
1544
self._update_basis_apply_changes(changes)
1477
1545
# Validate parents
1478
self._update_basis_check_parents(parents)
1546
self._after_delta_check_parents(parents, 1)
1479
1547
except errors.BzrError, e:
1548
self._changes_aborted = True
1480
1549
if 'integrity error' not in str(e):
1482
1551
# _get_entry raises BzrError when a request is inconsistent; we
1492
1560
self._id_index = None
1563
def _check_delta_ids_absent(self, new_ids, delta, tree_index):
1564
"""Check that none of the file_ids in new_ids are present in a tree."""
1567
id_index = self._get_id_index()
1568
for file_id in new_ids:
1569
for key in id_index.get(file_id, []):
1570
block_i, entry_i, d_present, f_present = \
1571
self._get_block_entry_index(key[0], key[1], tree_index)
1573
# In a different tree
1575
entry = self._dirblocks[block_i][1][entry_i]
1576
if entry[0][2] != file_id:
1577
# Different file_id, so not what we want.
1579
# NB: No changes made before this helper is called, so no need
1580
# to set the _changes_aborted flag.
1581
raise errors.InconsistentDelta(
1582
("%s/%s" % key[0:2]).decode('utf8'), file_id,
1583
"This file_id is new in the delta but already present in "
1495
1586
def _update_basis_apply_adds(self, adds):
1496
1587
"""Apply a sequence of adds to tree 1 during update_basis_by_delta.
1562
1653
null = DirState.NULL_PARENT_DETAILS
1563
1654
for old_path, new_path, file_id, _, real_delete in deletes:
1564
1655
if real_delete != (new_path is None):
1656
self._changes_aborted = True
1565
1657
raise AssertionError("bad delete delta")
1566
1658
# the entry for this file_id must be in tree 1.
1567
1659
dirname, basename = osutils.split(old_path)
1600
1692
# it is being resurrected here, so blank it out temporarily.
1601
1693
self._dirblocks[block_index][1][entry_index][1][1] = null
1603
def _update_basis_check_parents(self, parents):
1604
"""Check that parents required by the delta are all intact."""
1695
def _after_delta_check_parents(self, parents, index):
1696
"""Check that parents required by the delta are all intact.
1698
:param parents: An iterable of (path_utf8, file_id) tuples which are
1699
required to be present in tree 'index' at path_utf8 with id file_id
1701
:param index: The column in the dirstate to check for parents in.
1605
1703
for dirname_utf8, file_id in parents:
1606
1704
# Get the entry - the ensures that file_id, dirname_utf8 exists and
1607
1705
# has the right file id.
1608
entry = self._get_entry(1, file_id, dirname_utf8)
1706
entry = self._get_entry(index, file_id, dirname_utf8)
1609
1707
if entry[1] is None:
1610
1708
self._changes_aborted = True
1611
1709
raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1612
1710
file_id, "This parent is not present.")
1613
1711
# Parents of things must be directories
1614
if entry[1][1][0] != 'd':
1712
if entry[1][index][0] != 'd':
1615
1713
self._changes_aborted = True
1616
1714
raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1617
1715
file_id, "This parent is not a directory.")
2486
2584
new_path_utf8.decode('utf8'))
2487
2585
self.update_minimal(new_entry_key, current_new_minikind,
2488
2586
executable=current_new[1].executable,
2489
path_utf8=new_path_utf8, fingerprint=fingerprint)
2587
path_utf8=new_path_utf8, fingerprint=fingerprint,
2490
2589
current_new = advance(new_iterator)
2491
2590
elif not current_new:
2492
2591
# new is finished
2511
2610
new_path_utf8.decode('utf8'))
2512
2611
self.update_minimal(current_old[0], current_new_minikind,
2513
2612
executable=current_new[1].executable,
2514
path_utf8=new_path_utf8, fingerprint=fingerprint)
2613
path_utf8=new_path_utf8, fingerprint=fingerprint,
2515
2615
# both sides are dealt with, move on
2516
2616
current_old = advance(old_iterator)
2517
2617
current_new = advance(new_iterator)
2525
2625
new_path_utf8.decode('utf8'))
2526
2626
self.update_minimal(new_entry_key, current_new_minikind,
2527
2627
executable=current_new[1].executable,
2528
path_utf8=new_path_utf8, fingerprint=fingerprint)
2628
path_utf8=new_path_utf8, fingerprint=fingerprint,
2529
2630
current_new = advance(new_iterator)
2531
2632
# we've advanced past the place where the old key would be,
2595
2696
return last_reference
2597
2698
def update_minimal(self, key, minikind, executable=False, fingerprint='',
2598
packed_stat=None, size=0, path_utf8=None):
2699
packed_stat=None, size=0, path_utf8=None, fullscan=False):
2599
2700
"""Update an entry to the state in tree 0.
2601
2702
This will either create a new entry at 'key' or update an existing one.
2612
2713
:param size: Size information for new entry
2613
2714
:param path_utf8: key[0] + '/' + key[1], just passed in to avoid doing
2614
2715
extra computation.
2716
:param fullscan: If True then a complete scan of the dirstate is being
2717
done and checking for duplicate rows should not be done. This
2718
should only be set by set_state_from_inventory and similar methods.
2616
2720
If packed_stat and fingerprint are not given, they're invalidated in
2626
2730
new_details = (minikind, fingerprint, size, executable, packed_stat)
2627
2731
id_index = self._get_id_index()
2628
2732
if not present:
2733
# New record. Check there isn't a entry at this path already.
2735
low_index, _ = self._find_entry_index(key[0:2] + ('',), block)
2736
while low_index < len(block):
2737
entry = block[low_index]
2738
if entry[0][0:2] == key[0:2]:
2739
if entry[1][0][0] not in 'ar':
2740
# This entry has the same path (but a different id) as
2741
# the new entry we're adding, and is present in ths
2743
raise errors.InconsistentDelta(
2744
("%s/%s" % key[0:2]).decode('utf8'), key[2],
2745
"Attempt to add item at path already occupied by "
2746
"id %r" % entry[0][2])
2629
2750
# new entry, synthesis cross reference here,
2630
2751
existing_keys = id_index.setdefault(key[2], set())
2631
2752
if not existing_keys: