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.")
2436
2537
new_iterator = new_inv.iter_entries_by_dir()
2437
2538
# we will be modifying the dirstate, so we need a stable iterator. In
2438
2539
# future we might write one, for now we just clone the state into a
2439
# list - which is a shallow copy.
2540
# list using a copy so that we see every original item and don't have
2541
# to adjust the position when items are inserted or deleted in the
2542
# underlying dirstate.
2440
2543
old_iterator = iter(list(self._iter_entries()))
2441
2544
# both must have roots so this is safe:
2442
2545
current_new = new_iterator.next()
2476
2579
# we make both end conditions explicit
2477
2580
if not current_old:
2478
2581
# old is finished: insert current_new into the state.
2583
trace.mutter("Appending from new '%s'.",
2584
new_path_utf8.decode('utf8'))
2479
2585
self.update_minimal(new_entry_key, current_new_minikind,
2480
2586
executable=current_new[1].executable,
2481
path_utf8=new_path_utf8, fingerprint=fingerprint)
2587
path_utf8=new_path_utf8, fingerprint=fingerprint,
2482
2589
current_new = advance(new_iterator)
2483
2590
elif not current_new:
2484
2591
# new is finished
2593
trace.mutter("Truncating from old '%s/%s'.",
2594
current_old[0][0].decode('utf8'),
2595
current_old[0][1].decode('utf8'))
2485
2596
self._make_absent(current_old)
2486
2597
current_old = advance(old_iterator)
2487
2598
elif new_entry_key == current_old[0]:
2494
2605
# kind has changed.
2495
2606
if (current_old[1][0][3] != current_new[1].executable or
2496
2607
current_old[1][0][0] != current_new_minikind):
2609
trace.mutter("Updating in-place change '%s'.",
2610
new_path_utf8.decode('utf8'))
2497
2611
self.update_minimal(current_old[0], current_new_minikind,
2498
2612
executable=current_new[1].executable,
2499
path_utf8=new_path_utf8, fingerprint=fingerprint)
2613
path_utf8=new_path_utf8, fingerprint=fingerprint,
2500
2615
# both sides are dealt with, move on
2501
2616
current_old = advance(old_iterator)
2502
2617
current_new = advance(new_iterator)
2505
2620
and new_entry_key[1:] < current_old[0][1:])):
2506
2621
# new comes before:
2507
2622
# add a entry for this and advance new
2624
trace.mutter("Inserting from new '%s'.",
2625
new_path_utf8.decode('utf8'))
2508
2626
self.update_minimal(new_entry_key, current_new_minikind,
2509
2627
executable=current_new[1].executable,
2510
path_utf8=new_path_utf8, fingerprint=fingerprint)
2628
path_utf8=new_path_utf8, fingerprint=fingerprint,
2511
2630
current_new = advance(new_iterator)
2513
2632
# we've advanced past the place where the old key would be,
2514
2633
# without seeing it in the new list. so it must be gone.
2635
trace.mutter("Deleting from old '%s/%s'.",
2636
current_old[0][0].decode('utf8'),
2637
current_old[0][1].decode('utf8'))
2515
2638
self._make_absent(current_old)
2516
2639
current_old = advance(old_iterator)
2517
2640
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2518
2641
self._id_index = None
2519
2642
self._packed_stat_index = None
2644
trace.mutter("set_state_from_inventory complete.")
2521
2646
def _make_absent(self, current_old):
2522
2647
"""Mark current_old - an entry - as absent for tree 0.
2571
2696
return last_reference
2573
2698
def update_minimal(self, key, minikind, executable=False, fingerprint='',
2574
packed_stat=None, size=0, path_utf8=None):
2699
packed_stat=None, size=0, path_utf8=None, fullscan=False):
2575
2700
"""Update an entry to the state in tree 0.
2577
2702
This will either create a new entry at 'key' or update an existing one.
2588
2713
:param size: Size information for new entry
2589
2714
:param path_utf8: key[0] + '/' + key[1], just passed in to avoid doing
2590
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.
2592
2720
If packed_stat and fingerprint are not given, they're invalidated in
2602
2730
new_details = (minikind, fingerprint, size, executable, packed_stat)
2603
2731
id_index = self._get_id_index()
2604
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])
2605
2750
# new entry, synthesis cross reference here,
2606
2751
existing_keys = id_index.setdefault(key[2], set())
2607
2752
if not existing_keys:
2612
2757
# grab one of them and use it to generate parent
2613
2758
# relocation/absent entries.
2614
2759
new_entry = key, [new_details]
2615
for other_key in existing_keys:
2760
# existing_keys can be changed as we iterate.
2761
for other_key in tuple(existing_keys):
2616
2762
# change the record at other to be a pointer to this new
2617
2763
# record. The loop looks similar to the change to
2618
2764
# relocations when updating an existing record but its not:
2619
2765
# the test for existing kinds is different: this can be
2620
2766
# factored out to a helper though.
2621
other_block_index, present = self._find_block_index_from_key(other_key)
2623
raise AssertionError('could not find block for %s' % (other_key,))
2624
other_entry_index, present = self._find_entry_index(other_key,
2625
self._dirblocks[other_block_index][1])
2627
raise AssertionError('could not find entry for %s' % (other_key,))
2767
other_block_index, present = self._find_block_index_from_key(
2770
raise AssertionError('could not find block for %s' % (
2772
other_block = self._dirblocks[other_block_index][1]
2773
other_entry_index, present = self._find_entry_index(
2774
other_key, other_block)
2776
raise AssertionError(
2777
'update_minimal: could not find other entry for %s'
2628
2779
if path_utf8 is None:
2629
2780
raise AssertionError('no path')
2630
self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
2631
('r', path_utf8, 0, False, '')
2781
# Turn this other location into a reference to the new
2782
# location. This also updates the aliased iterator
2783
# (current_old in set_state_from_inventory) so that the old
2784
# entry, if not already examined, is skipped over by that
2786
other_entry = other_block[other_entry_index]
2787
other_entry[1][0] = ('r', path_utf8, 0, False, '')
2788
self._maybe_remove_row(other_block, other_entry_index,
2792
# adds a tuple to the new details for each column
2793
# - either by copying an existing relocation pointer inside that column
2794
# - or by creating a new pointer to the right row inside that column
2633
2795
num_present_parents = self._num_present_parents()
2796
if num_present_parents:
2797
other_key = list(existing_keys)[0]
2634
2798
for lookup_index in xrange(1, num_present_parents + 1):
2635
2799
# grab any one entry, use it to find the right path.
2636
2800
# TODO: optimise this to reduce memory use in highly
2643
2807
update_entry_index, present = \
2644
2808
self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
2645
2809
if not present:
2646
raise AssertionError('could not find entry for %s' % (other_key,))
2810
raise AssertionError('update_minimal: could not find entry for %s' % (other_key,))
2647
2811
update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
2648
2812
if update_details[0] in 'ar': # relocated, absent
2649
2813
# its a pointer or absent in lookup_index's tree, use
2696
2860
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2862
def _maybe_remove_row(self, block, index, id_index):
2863
"""Remove index if it is absent or relocated across the row.
2865
id_index is updated accordingly.
2867
present_in_row = False
2868
entry = block[index]
2869
for column in entry[1]:
2870
if column[0] not in 'ar':
2871
present_in_row = True
2873
if not present_in_row:
2875
id_index[entry[0][2]].remove(entry[0])
2698
2877
def _validate(self):
2699
2878
"""Check that invariants on the dirblock are correct.