1552
1550
if basename_utf8:
1553
1551
parents.add((dirname_utf8, inv_entry.parent_id))
1554
1552
if old_path is None:
1555
adds.append((None, encode(new_path), file_id,
1553
old_path_utf8 = None
1555
old_path_utf8 = encode(old_path)
1556
if old_path is None:
1557
adds.append((None, new_path_utf8, file_id,
1556
1558
inv_to_entry(inv_entry), True))
1557
1559
new_ids.add(file_id)
1558
1560
elif new_path is None:
1559
deletes.append((encode(old_path), None, file_id, None, True))
1560
elif (old_path, new_path) != root_only:
1561
deletes.append((old_path_utf8, None, file_id, None, True))
1562
elif (old_path, new_path) == root_only:
1563
# change things in-place
1564
# Note: the case of a parent directory changing its file_id
1565
# tends to break optimizations here, because officially
1566
# the file has actually been moved, it just happens to
1567
# end up at the same path. If we can figure out how to
1568
# handle that case, we can avoid a lot of add+delete
1569
# pairs for objects that stay put.
1570
# elif old_path == new_path:
1571
changes.append((old_path_utf8, new_path_utf8, file_id,
1572
inv_to_entry(inv_entry)))
1562
1575
# Because renames must preserve their children we must have
1563
1576
# processed all relocations and removes before hand. The sort
1573
1586
self._update_basis_apply_deletes(deletes)
1575
1588
# Split into an add/delete pair recursively.
1576
adds.append((None, new_path_utf8, file_id,
1577
inv_to_entry(inv_entry), False))
1589
adds.append((old_path_utf8, new_path_utf8, file_id,
1590
inv_to_entry(inv_entry), False))
1578
1591
# Expunge deletes that we've seen so that deleted/renamed
1579
1592
# children of a rename directory are handled correctly.
1580
new_deletes = reversed(list(self._iter_child_entries(1,
1593
new_deletes = reversed(list(
1594
self._iter_child_entries(1, old_path_utf8)))
1582
1595
# Remove the current contents of the tree at orig_path, and
1583
1596
# reinsert at the correct new path.
1584
1597
for entry in new_deletes:
1586
source_path = entry[0][0] + '/' + entry[0][1]
1598
child_dirname, child_basename, child_file_id = entry[0]
1600
source_path = child_dirname + '/' + child_basename
1588
source_path = entry[0][1]
1602
source_path = child_basename
1589
1603
if new_path_utf8:
1590
1604
target_path = new_path_utf8 + source_path[len(old_path):]
1592
1606
if old_path == '':
1593
1607
raise AssertionError("cannot rename directory to"
1595
1609
target_path = source_path[len(old_path) + 1:]
1596
1610
adds.append((None, target_path, entry[0][2], entry[1][1], False))
1597
1611
deletes.append(
1598
1612
(source_path, target_path, entry[0][2], None, False))
1600
(encode(old_path), new_path, file_id, None, False))
1602
# changes to just the root should not require remove/insertion
1604
changes.append((encode(old_path), encode(new_path), file_id,
1605
inv_to_entry(inv_entry)))
1613
deletes.append((old_path_utf8, new_path, file_id, None, False))
1606
1614
self._check_delta_ids_absent(new_ids, delta, 1)
1608
1616
# Finish expunging deletes/first half of renames.
1665
1673
# Adds are accumulated partly from renames, so can be in any input
1666
1674
# order - sort it.
1675
# TODO: we may want to sort in dirblocks order. That way each entry
1676
# will end up in the same directory, allowing the _get_entry
1677
# fast-path for looking up 2 items in the same dir work.
1678
adds.sort(key=lambda x: x[1])
1668
1679
# adds is now in lexographic order, which places all parents before
1669
1680
# their children, so we can process it linearly.
1682
st = static_tuple.StaticTuple
1671
1683
for old_path, new_path, file_id, new_details, real_add in adds:
1672
# the entry for this file_id must be in tree 0.
1673
entry = self._get_entry(0, file_id, new_path)
1674
if entry[0] is None:
1675
# new_path is not versioned in the active WT state,
1676
# but we are adding it to the basis tree state, we
1677
# need to create a new entry record for it.
1678
dirname, basename = osutils.split(new_path)
1679
entry_key = (dirname, basename, file_id)
1680
_, block = self._find_block(entry_key, add_if_missing=True)
1681
index, _ = self._find_entry_index(entry_key, block)
1682
entry = (entry_key, [DirState.NULL_PARENT_DETAILS]*2)
1683
block.insert(index, entry)
1684
elif entry[0][2] != file_id:
1685
self._changes_aborted = True
1686
raise errors.InconsistentDelta(new_path, file_id,
1687
'working tree does not contain new entry')
1688
if real_add and entry[1][1][0] not in absent:
1689
self._changes_aborted = True
1690
raise errors.InconsistentDelta(new_path, file_id,
1691
'The entry was considered to be a genuinely new record,'
1692
' but there was already an old record for it.')
1693
# We don't need to update the target of an 'r' because the handling
1694
# of renames turns all 'r' situations into a delete at the original
1696
entry[1][1] = new_details
1684
dirname, basename = osutils.split(new_path)
1685
entry_key = st(dirname, basename, file_id)
1686
block_index, present = self._find_block_index_from_key(entry_key)
1688
self._raise_invalid(new_path, file_id,
1689
"Unable to find block for this record."
1690
" Was the parent added?")
1691
block = self._dirblocks[block_index][1]
1692
entry_index, present = self._find_entry_index(entry_key, block)
1694
if old_path is not None:
1695
self._raise_invalid(new_path, file_id,
1696
'considered a real add but still had old_path at %s'
1699
entry = block[entry_index]
1700
basis_kind = entry[1][1][0]
1701
if basis_kind == 'a':
1702
entry[1][1] = new_details
1703
elif basis_kind == 'r':
1704
raise NotImplementedError()
1706
self._raise_invalid(new_path, file_id,
1707
"An entry was marked as a new add"
1708
" but the basis target already existed")
1710
# The exact key was not found in the block. However, we need to
1711
# check if there is a key next to us that would have matched.
1712
# We only need to check 2 locations, because there are only 2
1714
for maybe_index in range(entry_index-1, entry_index+1):
1715
if maybe_index < 0 or maybe_index >= len(block):
1717
maybe_entry = block[maybe_index]
1718
if maybe_entry[0][:2] != (dirname, basename):
1719
# Just a random neighbor
1721
if maybe_entry[0][2] == file_id:
1722
raise AssertionError(
1723
'_find_entry_index didnt find a key match'
1724
' but walking the data did, for %s'
1726
basis_kind = maybe_entry[1][1][0]
1727
if basis_kind not in 'ar':
1728
self._raise_invalid(new_path, file_id,
1729
"we have an add record for path, but the path"
1730
" is already present with another file_id %s"
1731
% (maybe_entry[0][2],))
1733
entry = (entry_key, [DirState.NULL_PARENT_DETAILS,
1735
block.insert(entry_index, entry)
1737
active_kind = entry[1][0][0]
1738
if active_kind == 'a':
1739
# The active record shows up as absent, this could be genuine,
1740
# or it could be present at some other location. We need to
1742
id_index = self._get_id_index()
1743
# The id_index may not be perfectly accurate for tree1, because
1744
# we haven't been keeping it updated. However, it should be
1745
# fine for tree0, and that gives us enough info for what we
1747
keys = id_index.get(file_id, ())
1749
block_i, entry_i, d_present, f_present = \
1750
self._get_block_entry_index(key[0], key[1], 0)
1753
active_entry = self._dirblocks[block_i][1][entry_i]
1754
if (active_entry[0][2] != file_id):
1755
# Some other file is at this path, we don't need to
1758
real_active_kind = active_entry[1][0][0]
1759
if real_active_kind in 'ar':
1760
# We found a record, which was not *this* record,
1761
# which matches the file_id, but is not actually
1762
# present. Something seems *really* wrong.
1763
self._raise_invalid(new_path, file_id,
1764
"We found a tree0 entry that doesnt make sense")
1765
# Now, we've found a tree0 entry which matches the file_id
1766
# but is at a different location. So update them to be
1768
active_dir, active_name = active_entry[0][:2]
1770
active_path = active_dir + '/' + active_name
1772
active_path = active_name
1773
active_entry[1][1] = st('r', new_path, 0, False, '')
1774
entry[1][0] = st('r', active_path, 0, False, '')
1775
elif active_kind == 'r':
1776
raise NotImplementedError()
1778
new_kind = new_details[0]
1780
self._ensure_block(block_index, entry_index, new_path)
1698
1782
def _update_basis_apply_changes(self, changes):
1699
1783
"""Apply a sequence of changes to tree 1 during update_basis_by_delta.
1705
1789
for old_path, new_path, file_id, new_details in changes:
1706
1790
# the entry for this file_id must be in tree 0.
1707
entry = self._get_entry(0, file_id, new_path)
1708
if entry[0] is None or entry[0][2] != file_id:
1709
self._changes_aborted = True
1710
raise errors.InconsistentDelta(new_path, file_id,
1711
'working tree does not contain new entry')
1712
if (entry[1][0][0] in absent or
1713
entry[1][1][0] in absent):
1714
self._changes_aborted = True
1715
raise errors.InconsistentDelta(new_path, file_id,
1716
'changed considered absent')
1791
entry = self._get_entry(1, file_id, new_path)
1792
if entry[0] is None or entry[1][1][0] in 'ar':
1793
self._raise_invalid(new_path, file_id,
1794
'changed entry considered not present')
1717
1795
entry[1][1] = new_details
1719
1797
def _update_basis_apply_deletes(self, deletes):
1731
1809
null = DirState.NULL_PARENT_DETAILS
1732
1810
for old_path, new_path, file_id, _, real_delete in deletes:
1733
1811
if real_delete != (new_path is None):
1734
self._changes_aborted = True
1735
raise AssertionError("bad delete delta")
1812
self._raise_invalid(old_path, file_id, "bad delete delta")
1736
1813
# the entry for this file_id must be in tree 1.
1737
1814
dirname, basename = osutils.split(old_path)
1738
1815
block_index, entry_index, dir_present, file_present = \
1739
1816
self._get_block_entry_index(dirname, basename, 1)
1740
1817
if not file_present:
1741
self._changes_aborted = True
1742
raise errors.InconsistentDelta(old_path, file_id,
1818
self._raise_invalid(old_path, file_id,
1743
1819
'basis tree does not contain removed entry')
1744
1820
entry = self._dirblocks[block_index][1][entry_index]
1821
# The state of the entry in the 'active' WT
1822
active_kind = entry[1][0][0]
1745
1823
if entry[0][2] != file_id:
1746
self._changes_aborted = True
1747
raise errors.InconsistentDelta(old_path, file_id,
1824
self._raise_invalid(old_path, file_id,
1748
1825
'mismatched file_id in tree 1')
1750
if entry[1][0][0] == 'a':
1751
# The file was marked as deleted in the active
1752
# state, and it is now deleted in the basis state,
1753
# so just remove the record entirely
1754
del self._dirblocks[block_index][1][entry_index]
1756
# The basis entry needs to be marked deleted
1758
# If we are deleting a directory, we need to make sure
1759
# that all of its children are already deleted
1827
old_kind = entry[1][1][0]
1828
if active_kind in 'ar':
1829
# The active tree doesn't have this file_id.
1830
# The basis tree is changing this record. If this is a
1831
# rename, then we don't want the record here at all
1832
# anymore. If it is just an in-place change, we want the
1833
# record here, but we'll add it if we need to. So we just
1835
if active_kind == 'r':
1836
active_path = entry[1][0][1]
1837
active_entry = self._get_entry(0, file_id, active_path)
1838
if active_entry[1][1][0] != 'r':
1839
self._raise_invalid(old_path, file_id,
1840
"Dirstate did not have matching rename entries")
1841
elif active_entry[1][0][0] in 'ar':
1842
self._raise_invalid(old_path, file_id,
1843
"Dirstate had a rename pointing at an inactive"
1845
active_entry[1][1] = null
1846
del self._dirblocks[block_index][1][entry_index]
1848
# This was a directory, and the active tree says it
1849
# doesn't exist, and now the basis tree says it doesn't
1850
# exist. Remove its dirblock if present
1852
present) = self._find_block_index_from_key(
1855
dir_block = self._dirblocks[dir_block_index][1]
1857
# This entry is empty, go ahead and just remove it
1858
del self._dirblocks[dir_block_index]
1860
# There is still an active record, so just mark this
1760
1863
block_i, entry_i, d_present, f_present = \
1761
self._get_block_entry_index(old_path, '', 0)
1864
self._get_block_entry_index(old_path, '', 1)
1763
# The dir block is still present in the dirstate; this could
1764
# be due to it being in a parent tree, or a corrupt delta.
1765
for child_entry in self._dirblocks[block_i][1]:
1766
if child_entry[1][1][0] not in ('r', 'a'):
1767
self._changes_aborted = True
1768
raise errors.InconsistentDelta(old_path, entry[0][2],
1769
"The file id was deleted but its children were "
1772
if entry[1][0][0] == 'a':
1773
self._changes_aborted = True
1774
raise errors.InconsistentDelta(old_path, file_id,
1775
'The entry was considered a rename, but the source path'
1776
' is marked as absent.')
1777
# For whatever reason, we were asked to rename an entry
1778
# that was originally marked as deleted. This could be
1779
# because we are renaming the parent directory, and the WT
1780
# current state has the file marked as deleted.
1781
elif entry[1][0][0] == 'r':
1782
# implement the rename
1783
del self._dirblocks[block_index][1][entry_index]
1785
# it is being resurrected here, so blank it out temporarily.
1786
# should be equivalent to entry[1][1] = null
1787
self._dirblocks[block_index][1][entry_index][1][1] = null
1866
dir_block = self._dirblocks[block_i][1]
1867
for child_entry in dir_block:
1868
child_basis_kind = child_entry[1][1][0]
1869
if child_basis_kind not in 'ar':
1870
self._raise_invalid(old_path, file_id,
1871
"The file id was deleted but its children were "
1789
1874
def _after_delta_check_parents(self, parents, index):
1790
1875
"""Check that parents required by the delta are all intact.