1091
def update_entry(self, entry, abspath, stat_value,
1092
_stat_to_minikind=_stat_to_minikind,
1093
_pack_stat=pack_stat):
1094
"""Update the entry based on what is actually on disk.
1096
:param entry: This is the dirblock entry for the file in question.
1097
:param abspath: The path on disk for this file.
1098
:param stat_value: (optional) if we already have done a stat on the
1100
:return: The sha1 hexdigest of the file (40 bytes) or link target of a
1278
def update_by_delta(self, delta):
1279
"""Apply an inventory delta to the dirstate for tree 0
1281
This is the workhorse for apply_inventory_delta in dirstate based
1284
:param delta: An inventory delta. See Inventory.apply_delta for
1287
self._read_dirblocks_if_needed()
1288
encode = cache_utf8.encode
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))))),
1308
if (file_id in insertions) or (file_id in removals):
1309
raise errors.InconsistentDelta(old_path or new_path, file_id,
1311
if old_path is not None:
1312
old_path = old_path.encode('utf-8')
1313
removals[file_id] = old_path
1315
new_ids.add(file_id)
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")
1320
new_path = new_path.encode('utf-8')
1321
dirname_utf8, basename = osutils.split(new_path)
1323
parents.add((dirname_utf8, inv_entry.parent_id))
1324
key = (dirname_utf8, basename, file_id)
1325
minikind = DirState._kind_to_minikind[inv_entry.kind]
1327
fingerprint = inv_entry.reference_revision or ''
1330
insertions[file_id] = (key, minikind, inv_entry.executable,
1331
fingerprint, new_path)
1332
# Transform moves into delete+add pairs
1333
if None not in (old_path, new_path):
1334
for child in self._iter_child_entries(0, old_path):
1335
if child[0][2] in insertions or child[0][2] in removals:
1337
child_dirname = child[0][0]
1338
child_basename = child[0][1]
1339
minikind = child[1][0][0]
1340
fingerprint = child[1][0][4]
1341
executable = child[1][0][3]
1342
old_child_path = osutils.pathjoin(child[0][0],
1344
removals[child[0][2]] = old_child_path
1345
child_suffix = child_dirname[len(old_path):]
1346
new_child_dirname = (new_path + child_suffix)
1347
key = (new_child_dirname, child_basename, child[0][2])
1348
new_child_path = os.path.join(new_child_dirname,
1350
insertions[child[0][2]] = (key, minikind, executable,
1351
fingerprint, new_child_path)
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.")
1367
def _apply_removals(self, removals):
1368
for file_id, path in sorted(removals, reverse=True,
1369
key=operator.itemgetter(1)):
1370
dirname, basename = osutils.split(path)
1371
block_i, entry_i, d_present, f_present = \
1372
self._get_block_entry_index(dirname, basename, 0)
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."
1388
self._make_absent(entry)
1389
# See if we have a malformed delta: deleting a directory must not
1390
# leave crud behind. This increases the number of bisects needed
1391
# substantially, but deletion or renames of large numbers of paths
1392
# is rare enough it shouldn't be an issue (famous last words?) RBC
1394
block_i, entry_i, d_present, f_present = \
1395
self._get_block_entry_index(path, '', 0)
1397
# The dir block is still present in the dirstate; this could
1398
# be due to it being in a parent tree, or a corrupt delta.
1399
for child_entry in self._dirblocks[block_i][1]:
1400
if child_entry[1][0][0] not in ('r', 'a'):
1401
self._changes_aborted = True
1402
raise errors.InconsistentDelta(path, entry[0][2],
1403
"The file id was deleted but its children were "
1406
def _apply_insertions(self, adds):
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],
1416
def update_basis_by_delta(self, delta, new_revid):
1417
"""Update the parents of this tree after a commit.
1419
This gives the tree one parent, with revision id new_revid. The
1420
inventory delta is applied to the current basis tree to generate the
1421
inventory for the parent new_revid, and all other parent trees are
1424
Note that an exception during the operation of this method will leave
1425
the dirstate in a corrupt state where it should not be saved.
1427
Finally, we expect all changes to be synchronising the basis tree with
1430
:param new_revid: The new revision id for the trees parent.
1431
:param delta: An inventory delta (see apply_inventory_delta) describing
1432
the changes from the current left most parent revision to new_revid.
1434
self._read_dirblocks_if_needed()
1435
self._discard_merge_parents()
1436
if self._ghosts != []:
1437
raise NotImplementedError(self.update_basis_by_delta)
1438
if len(self._parents) == 0:
1439
# setup a blank tree, the most simple way.
1440
empty_parent = DirState.NULL_PARENT_DETAILS
1441
for entry in self._iter_entries():
1442
entry[1].append(empty_parent)
1443
self._parents.append(new_revid)
1445
self._parents[0] = new_revid
1447
delta = sorted(delta, reverse=True)
1451
# The paths this function accepts are unicode and must be encoded as we
1453
encode = cache_utf8.encode
1454
inv_to_entry = self._inv_entry_to_details
1455
# delta is now (deletes, changes), (adds) in reverse lexographical
1457
# deletes in reverse lexographic order are safe to process in situ.
1458
# renames are not, as a rename from any path could go to a path
1459
# lexographically lower, so we transform renames into delete, add pairs,
1460
# expanding them recursively as needed.
1461
# At the same time, to reduce interface friction we convert the input
1462
# inventory entries to dirstate.
1463
root_only = ('', '')
1464
# Accumulate parent references (path_utf8, id), to check for parentless
1465
# items or items placed under files/links/tree-references. We get
1466
# references from every item in the delta that is not a deletion and
1467
# is not itself the root.
1469
# Added ids must not be in the dirstate already. This set holds those
1472
for old_path, new_path, file_id, inv_entry in delta:
1473
if inv_entry is not None and file_id != inv_entry.file_id:
1474
raise errors.InconsistentDelta(new_path, file_id,
1475
"mismatched entry file_id %r" % inv_entry)
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")
1480
new_path_utf8 = encode(new_path)
1481
# note the parent for validation
1482
dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
1484
parents.add((dirname_utf8, inv_entry.parent_id))
1485
if old_path is None:
1486
adds.append((None, encode(new_path), file_id,
1487
inv_to_entry(inv_entry), True))
1488
new_ids.add(file_id)
1489
elif new_path is None:
1490
deletes.append((encode(old_path), None, file_id, None, True))
1491
elif (old_path, new_path) != root_only:
1493
# Because renames must preserve their children we must have
1494
# processed all relocations and removes before hand. The sort
1495
# order ensures we've examined the child paths, but we also
1496
# have to execute the removals, or the split to an add/delete
1497
# pair will result in the deleted item being reinserted, or
1498
# renamed items being reinserted twice - and possibly at the
1499
# wrong place. Splitting into a delete/add pair also simplifies
1500
# the handling of entries with ('f', ...), ('r' ...) because
1501
# the target of the 'r' is old_path here, and we add that to
1502
# deletes, meaning that the add handler does not need to check
1503
# for 'r' items on every pass.
1504
self._update_basis_apply_deletes(deletes)
1506
# Split into an add/delete pair recursively.
1507
adds.append((None, new_path_utf8, file_id,
1508
inv_to_entry(inv_entry), False))
1509
# Expunge deletes that we've seen so that deleted/renamed
1510
# children of a rename directory are handled correctly.
1511
new_deletes = reversed(list(self._iter_child_entries(1,
1513
# Remove the current contents of the tree at orig_path, and
1514
# reinsert at the correct new path.
1515
for entry in new_deletes:
1517
source_path = entry[0][0] + '/' + entry[0][1]
1519
source_path = entry[0][1]
1521
target_path = new_path_utf8 + source_path[len(old_path):]
1524
raise AssertionError("cannot rename directory to"
1526
target_path = source_path[len(old_path) + 1:]
1527
adds.append((None, target_path, entry[0][2], entry[1][1], False))
1529
(source_path, target_path, entry[0][2], None, False))
1531
(encode(old_path), new_path, file_id, None, False))
1533
# changes to just the root should not require remove/insertion
1535
changes.append((encode(old_path), encode(new_path), file_id,
1536
inv_to_entry(inv_entry)))
1537
self._check_delta_ids_absent(new_ids, delta, 1)
1539
# Finish expunging deletes/first half of renames.
1540
self._update_basis_apply_deletes(deletes)
1541
# Reinstate second half of renames and new paths.
1542
self._update_basis_apply_adds(adds)
1543
# Apply in-situ changes.
1544
self._update_basis_apply_changes(changes)
1546
self._after_delta_check_parents(parents, 1)
1547
except errors.BzrError, e:
1548
self._changes_aborted = True
1549
if 'integrity error' not in str(e):
1551
# _get_entry raises BzrError when a request is inconsistent; we
1552
# want such errors to be shown as InconsistentDelta - and that
1553
# fits the behaviour we trigger. Partof this is driven by dirstate
1554
# only supporting deltas that turn the basis into a closer fit to
1556
raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1558
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1559
self._header_state = DirState.IN_MEMORY_MODIFIED
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 "
1586
def _update_basis_apply_adds(self, adds):
1587
"""Apply a sequence of adds to tree 1 during update_basis_by_delta.
1589
They may be adds, or renames that have been split into add/delete
1592
:param adds: A sequence of adds. Each add is a tuple:
1593
(None, new_path_utf8, file_id, (entry_details), real_add). real_add
1594
is False when the add is the second half of a remove-and-reinsert
1595
pair created to handle renames and deletes.
1597
# Adds are accumulated partly from renames, so can be in any input
1600
# adds is now in lexographic order, which places all parents before
1601
# their children, so we can process it linearly.
1603
for old_path, new_path, file_id, new_details, real_add in adds:
1604
# the entry for this file_id must be in tree 0.
1605
entry = self._get_entry(0, file_id, new_path)
1606
if entry[0] is None or entry[0][2] != file_id:
1607
self._changes_aborted = True
1608
raise errors.InconsistentDelta(new_path, file_id,
1609
'working tree does not contain new entry')
1610
if real_add and entry[1][1][0] not in absent:
1611
self._changes_aborted = True
1612
raise errors.InconsistentDelta(new_path, file_id,
1613
'The entry was considered to be a genuinely new record,'
1614
' but there was already an old record for it.')
1615
# We don't need to update the target of an 'r' because the handling
1616
# of renames turns all 'r' situations into a delete at the original
1618
entry[1][1] = new_details
1620
def _update_basis_apply_changes(self, changes):
1621
"""Apply a sequence of changes to tree 1 during update_basis_by_delta.
1623
:param adds: A sequence of changes. Each change is a tuple:
1624
(path_utf8, path_utf8, file_id, (entry_details))
1627
for old_path, new_path, file_id, new_details in changes:
1628
# the entry for this file_id must be in tree 0.
1629
entry = self._get_entry(0, file_id, new_path)
1630
if entry[0] is None or entry[0][2] != file_id:
1631
self._changes_aborted = True
1632
raise errors.InconsistentDelta(new_path, file_id,
1633
'working tree does not contain new entry')
1634
if (entry[1][0][0] in absent or
1635
entry[1][1][0] in absent):
1636
self._changes_aborted = True
1637
raise errors.InconsistentDelta(new_path, file_id,
1638
'changed considered absent')
1639
entry[1][1] = new_details
1641
def _update_basis_apply_deletes(self, deletes):
1642
"""Apply a sequence of deletes to tree 1 during update_basis_by_delta.
1644
They may be deletes, or renames that have been split into add/delete
1647
:param deletes: A sequence of deletes. Each delete is a tuple:
1648
(old_path_utf8, new_path_utf8, file_id, None, real_delete).
1649
real_delete is True when the desired outcome is an actual deletion
1650
rather than the rename handling logic temporarily deleting a path
1651
during the replacement of a parent.
1653
null = DirState.NULL_PARENT_DETAILS
1654
for old_path, new_path, file_id, _, real_delete in deletes:
1655
if real_delete != (new_path is None):
1656
self._changes_aborted = True
1657
raise AssertionError("bad delete delta")
1658
# the entry for this file_id must be in tree 1.
1659
dirname, basename = osutils.split(old_path)
1660
block_index, entry_index, dir_present, file_present = \
1661
self._get_block_entry_index(dirname, basename, 1)
1662
if not file_present:
1663
self._changes_aborted = True
1664
raise errors.InconsistentDelta(old_path, file_id,
1665
'basis tree does not contain removed entry')
1666
entry = self._dirblocks[block_index][1][entry_index]
1667
if entry[0][2] != file_id:
1668
self._changes_aborted = True
1669
raise errors.InconsistentDelta(old_path, file_id,
1670
'mismatched file_id in tree 1')
1672
if entry[1][0][0] != 'a':
1673
self._changes_aborted = True
1674
raise errors.InconsistentDelta(old_path, file_id,
1675
'This was marked as a real delete, but the WT state'
1676
' claims that it still exists and is versioned.')
1677
del self._dirblocks[block_index][1][entry_index]
1679
if entry[1][0][0] == 'a':
1680
self._changes_aborted = True
1681
raise errors.InconsistentDelta(old_path, file_id,
1682
'The entry was considered a rename, but the source path'
1683
' is marked as absent.')
1684
# For whatever reason, we were asked to rename an entry
1685
# that was originally marked as deleted. This could be
1686
# because we are renaming the parent directory, and the WT
1687
# current state has the file marked as deleted.
1688
elif entry[1][0][0] == 'r':
1689
# implement the rename
1690
del self._dirblocks[block_index][1][entry_index]
1692
# it is being resurrected here, so blank it out temporarily.
1693
self._dirblocks[block_index][1][entry_index][1][1] = null
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.
1703
for dirname_utf8, file_id in parents:
1704
# Get the entry - the ensures that file_id, dirname_utf8 exists and
1705
# has the right file id.
1706
entry = self._get_entry(index, file_id, dirname_utf8)
1707
if entry[1] is None:
1708
self._changes_aborted = True
1709
raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1710
file_id, "This parent is not present.")
1711
# Parents of things must be directories
1712
if entry[1][index][0] != 'd':
1713
self._changes_aborted = True
1714
raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1715
file_id, "This parent is not a directory.")
1717
def _observed_sha1(self, entry, sha1, stat_value,
1718
_stat_to_minikind=_stat_to_minikind, _pack_stat=pack_stat):
1719
"""Note the sha1 of a file.
1721
:param entry: The entry the sha1 is for.
1722
:param sha1: The observed sha1.
1723
:param stat_value: The os.lstat for the file.
1104
1726
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
2367
3069
self._split_path_cache = {}
2369
3071
def _requires_lock(self):
2370
"""Checks that a lock is currently held by someone on the dirstate"""
3072
"""Check that a lock is currently held by someone on the dirstate."""
2371
3073
if not self._lock_token:
2372
3074
raise errors.ObjectNotLocked(self)
2375
def bisect_dirblock(dirblocks, dirname, lo=0, hi=None, cache={}):
2376
"""Return the index where to insert dirname into the dirblocks.
2378
The return value idx is such that all directories blocks in dirblock[:idx]
2379
have names < dirname, and all blocks in dirblock[idx:] have names >=
2382
Optional args lo (default 0) and hi (default len(dirblocks)) bound the
2383
slice of a to be searched.
3077
def py_update_entry(state, entry, abspath, stat_value,
3078
_stat_to_minikind=DirState._stat_to_minikind,
3079
_pack_stat=pack_stat):
3080
"""Update the entry based on what is actually on disk.
3082
This function only calculates the sha if it needs to - if the entry is
3083
uncachable, or clearly different to the first parent's entry, no sha
3084
is calculated, and None is returned.
3086
:param state: The dirstate this entry is in.
3087
:param entry: This is the dirblock entry for the file in question.
3088
:param abspath: The path on disk for this file.
3089
:param stat_value: The stat value done on the path.
3090
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
3091
target of a symlink.
2388
dirname_split = cache[dirname]
3094
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
2389
3095
except KeyError:
2390
dirname_split = dirname.split('/')
2391
cache[dirname] = dirname_split
2394
# Grab the dirname for the current dirblock
2395
cur = dirblocks[mid][0]
3098
packed_stat = _pack_stat(stat_value)
3099
(saved_minikind, saved_link_or_sha1, saved_file_size,
3100
saved_executable, saved_packed_stat) = entry[1][0]
3102
if minikind == 'd' and saved_minikind == 't':
3104
if (minikind == saved_minikind
3105
and packed_stat == saved_packed_stat):
3106
# The stat hasn't changed since we saved, so we can re-use the
3111
# size should also be in packed_stat
3112
if saved_file_size == stat_value.st_size:
3113
return saved_link_or_sha1
3115
# If we have gotten this far, that means that we need to actually
3116
# process this entry.
3119
executable = state._is_executable(stat_value.st_mode,
3121
if state._cutoff_time is None:
3122
state._sha_cutoff_time()
3123
if (stat_value.st_mtime < state._cutoff_time
3124
and stat_value.st_ctime < state._cutoff_time
3125
and len(entry[1]) > 1
3126
and entry[1][1][0] != 'a'):
3127
# Could check for size changes for further optimised
3128
# avoidance of sha1's. However the most prominent case of
3129
# over-shaing is during initial add, which this catches.
3130
# Besides, if content filtering happens, size and sha
3131
# are calculated at the same time, so checking just the size
3132
# gains nothing w.r.t. performance.
3133
link_or_sha1 = state._sha1_file(abspath)
3134
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
3135
executable, packed_stat)
3137
entry[1][0] = ('f', '', stat_value.st_size,
3138
executable, DirState.NULLSTAT)
3139
elif minikind == 'd':
3141
entry[1][0] = ('d', '', 0, False, packed_stat)
3142
if saved_minikind != 'd':
3143
# This changed from something into a directory. Make sure we
3144
# have a directory block for it. This doesn't happen very
3145
# often, so this doesn't have to be super fast.
3146
block_index, entry_index, dir_present, file_present = \
3147
state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3148
state._ensure_block(block_index, entry_index,
3149
osutils.pathjoin(entry[0][0], entry[0][1]))
3150
elif minikind == 'l':
3151
link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3152
if state._cutoff_time is None:
3153
state._sha_cutoff_time()
3154
if (stat_value.st_mtime < state._cutoff_time
3155
and stat_value.st_ctime < state._cutoff_time):
3156
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
3159
entry[1][0] = ('l', '', stat_value.st_size,
3160
False, DirState.NULLSTAT)
3161
state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3165
class ProcessEntryPython(object):
3167
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3168
"last_source_parent", "last_target_parent", "include_unchanged",
3169
"partial", "use_filesystem_for_exec", "utf8_decode",
3170
"searched_specific_files", "search_specific_files",
3171
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3172
"state", "source_index", "target_index", "want_unversioned", "tree"]
3174
def __init__(self, include_unchanged, use_filesystem_for_exec,
3175
search_specific_files, state, source_index, target_index,
3176
want_unversioned, tree):
3177
self.old_dirname_to_file_id = {}
3178
self.new_dirname_to_file_id = {}
3179
# Are we doing a partial iter_changes?
3180
self.partial = search_specific_files != set([''])
3181
# Using a list so that we can access the values and change them in
3182
# nested scope. Each one is [path, file_id, entry]
3183
self.last_source_parent = [None, None]
3184
self.last_target_parent = [None, None]
3185
self.include_unchanged = include_unchanged
3186
self.use_filesystem_for_exec = use_filesystem_for_exec
3187
self.utf8_decode = cache_utf8._utf8_decode
3188
# for all search_indexs in each path at or under each element of
3189
# search_specific_files, if the detail is relocated: add the id, and
3190
# add the relocated path as one to search if its not searched already.
3191
# If the detail is not relocated, add the id.
3192
self.searched_specific_files = set()
3193
# When we search exact paths without expanding downwards, we record
3195
self.searched_exact_paths = set()
3196
self.search_specific_files = search_specific_files
3197
# The parents up to the root of the paths we are searching.
3198
# After all normal paths are returned, these specific items are returned.
3199
self.search_specific_file_parents = set()
3200
# The ids we've sent out in the delta.
3201
self.seen_ids = set()
3203
self.source_index = source_index
3204
self.target_index = target_index
3205
if target_index != 0:
3206
# A lot of code in here depends on target_index == 0
3207
raise errors.BzrError('unsupported target index')
3208
self.want_unversioned = want_unversioned
3211
def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
3212
"""Compare an entry and real disk to generate delta information.
3214
:param path_info: top_relpath, basename, kind, lstat, abspath for
3215
the path of entry. If None, then the path is considered absent in
3216
the target (Perhaps we should pass in a concrete entry for this ?)
3217
Basename is returned as a utf8 string because we expect this
3218
tuple will be ignored, and don't want to take the time to
3220
:return: (iter_changes_result, changed). If the entry has not been
3221
handled then changed is None. Otherwise it is False if no content
3222
or metadata changes have occurred, and True if any content or
3223
metadata change has occurred. If self.include_unchanged is True then
3224
if changed is not None, iter_changes_result will always be a result
3225
tuple. Otherwise, iter_changes_result is None unless changed is
3228
if self.source_index is None:
3229
source_details = DirState.NULL_PARENT_DETAILS
3231
source_details = entry[1][self.source_index]
3232
target_details = entry[1][self.target_index]
3233
target_minikind = target_details[0]
3234
if path_info is not None and target_minikind in 'fdlt':
3235
if not (self.target_index == 0):
3236
raise AssertionError()
3237
link_or_sha1 = update_entry(self.state, entry,
3238
abspath=path_info[4], stat_value=path_info[3])
3239
# The entry may have been modified by update_entry
3240
target_details = entry[1][self.target_index]
3241
target_minikind = target_details[0]
3244
file_id = entry[0][2]
3245
source_minikind = source_details[0]
3246
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
3247
# claimed content in both: diff
3248
# r | fdlt | | add source to search, add id path move and perform
3249
# | | | diff check on source-target
3250
# r | fdlt | a | dangling file that was present in the basis.
3252
if source_minikind in 'r':
3253
# add the source to the search path to find any children it
3254
# has. TODO ? : only add if it is a container ?
3255
if not osutils.is_inside_any(self.searched_specific_files,
3257
self.search_specific_files.add(source_details[1])
3258
# generate the old path; this is needed for stating later
3260
old_path = source_details[1]
3261
old_dirname, old_basename = os.path.split(old_path)
3262
path = pathjoin(entry[0][0], entry[0][1])
3263
old_entry = self.state._get_entry(self.source_index,
3265
# update the source details variable to be the real
3267
if old_entry == (None, None):
3268
raise errors.CorruptDirstate(self.state._filename,
3269
"entry '%s/%s' is considered renamed from %r"
3270
" but source does not exist\n"
3271
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3272
source_details = old_entry[1][self.source_index]
3273
source_minikind = source_details[0]
3275
old_dirname = entry[0][0]
3276
old_basename = entry[0][1]
3277
old_path = path = None
3278
if path_info is None:
3279
# the file is missing on disk, show as removed.
3280
content_change = True
3284
# source and target are both versioned and disk file is present.
3285
target_kind = path_info[2]
3286
if target_kind == 'directory':
3288
old_path = path = pathjoin(old_dirname, old_basename)
3289
self.new_dirname_to_file_id[path] = file_id
3290
if source_minikind != 'd':
3291
content_change = True
3293
# directories have no fingerprint
3294
content_change = False
3296
elif target_kind == 'file':
3297
if source_minikind != 'f':
3298
content_change = True
3300
# Check the sha. We can't just rely on the size as
3301
# content filtering may mean differ sizes actually
3302
# map to the same content
3303
if link_or_sha1 is None:
3305
statvalue, link_or_sha1 = \
3306
self.state._sha1_provider.stat_and_sha1(
3308
self.state._observed_sha1(entry, link_or_sha1,
3310
content_change = (link_or_sha1 != source_details[1])
3311
# Target details is updated at update_entry time
3312
if self.use_filesystem_for_exec:
3313
# We don't need S_ISREG here, because we are sure
3314
# we are dealing with a file.
3315
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
3317
target_exec = target_details[3]
3318
elif target_kind == 'symlink':
3319
if source_minikind != 'l':
3320
content_change = True
3322
content_change = (link_or_sha1 != source_details[1])
3324
elif target_kind == 'tree-reference':
3325
if source_minikind != 't':
3326
content_change = True
3328
content_change = False
3332
path = pathjoin(old_dirname, old_basename)
3333
raise errors.BadFileKindError(path, path_info[2])
3334
if source_minikind == 'd':
3336
old_path = path = pathjoin(old_dirname, old_basename)
3337
self.old_dirname_to_file_id[old_path] = file_id
3338
# parent id is the entry for the path in the target tree
3339
if old_basename and old_dirname == self.last_source_parent[0]:
3340
source_parent_id = self.last_source_parent[1]
3343
source_parent_id = self.old_dirname_to_file_id[old_dirname]
3345
source_parent_entry = self.state._get_entry(self.source_index,
3346
path_utf8=old_dirname)
3347
source_parent_id = source_parent_entry[0][2]
3348
if source_parent_id == entry[0][2]:
3349
# This is the root, so the parent is None
3350
source_parent_id = None
3352
self.last_source_parent[0] = old_dirname
3353
self.last_source_parent[1] = source_parent_id
3354
new_dirname = entry[0][0]
3355
if entry[0][1] and new_dirname == self.last_target_parent[0]:
3356
target_parent_id = self.last_target_parent[1]
3359
target_parent_id = self.new_dirname_to_file_id[new_dirname]
3361
# TODO: We don't always need to do the lookup, because the
3362
# parent entry will be the same as the source entry.
3363
target_parent_entry = self.state._get_entry(self.target_index,
3364
path_utf8=new_dirname)
3365
if target_parent_entry == (None, None):
3366
raise AssertionError(
3367
"Could not find target parent in wt: %s\nparent of: %s"
3368
% (new_dirname, entry))
3369
target_parent_id = target_parent_entry[0][2]
3370
if target_parent_id == entry[0][2]:
3371
# This is the root, so the parent is None
3372
target_parent_id = None
3374
self.last_target_parent[0] = new_dirname
3375
self.last_target_parent[1] = target_parent_id
3377
source_exec = source_details[3]
3378
changed = (content_change
3379
or source_parent_id != target_parent_id
3380
or old_basename != entry[0][1]
3381
or source_exec != target_exec
3383
if not changed and not self.include_unchanged:
3386
if old_path is None:
3387
old_path = path = pathjoin(old_dirname, old_basename)
3388
old_path_u = self.utf8_decode(old_path)[0]
3391
old_path_u = self.utf8_decode(old_path)[0]
3392
if old_path == path:
3395
path_u = self.utf8_decode(path)[0]
3396
source_kind = DirState._minikind_to_kind[source_minikind]
3397
return (entry[0][2],
3398
(old_path_u, path_u),
3401
(source_parent_id, target_parent_id),
3402
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3403
(source_kind, target_kind),
3404
(source_exec, target_exec)), changed
3405
elif source_minikind in 'a' and target_minikind in 'fdlt':
3406
# looks like a new file
3407
path = pathjoin(entry[0][0], entry[0][1])
3408
# parent id is the entry for the path in the target tree
3409
# TODO: these are the same for an entire directory: cache em.
3410
parent_id = self.state._get_entry(self.target_index,
3411
path_utf8=entry[0][0])[0][2]
3412
if parent_id == entry[0][2]:
3414
if path_info is not None:
3416
if self.use_filesystem_for_exec:
3417
# We need S_ISREG here, because we aren't sure if this
3420
stat.S_ISREG(path_info[3].st_mode)
3421
and stat.S_IEXEC & path_info[3].st_mode)
3423
target_exec = target_details[3]
3424
return (entry[0][2],
3425
(None, self.utf8_decode(path)[0]),
3429
(None, self.utf8_decode(entry[0][1])[0]),
3430
(None, path_info[2]),
3431
(None, target_exec)), True
3433
# Its a missing file, report it as such.
3434
return (entry[0][2],
3435
(None, self.utf8_decode(path)[0]),
3439
(None, self.utf8_decode(entry[0][1])[0]),
3441
(None, False)), True
3442
elif source_minikind in 'fdlt' and target_minikind in 'a':
3443
# unversioned, possibly, or possibly not deleted: we dont care.
3444
# if its still on disk, *and* theres no other entry at this
3445
# path [we dont know this in this routine at the moment -
3446
# perhaps we should change this - then it would be an unknown.
3447
old_path = pathjoin(entry[0][0], entry[0][1])
3448
# parent id is the entry for the path in the target tree
3449
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
3450
if parent_id == entry[0][2]:
3452
return (entry[0][2],
3453
(self.utf8_decode(old_path)[0], None),
3457
(self.utf8_decode(entry[0][1])[0], None),
3458
(DirState._minikind_to_kind[source_minikind], None),
3459
(source_details[3], None)), True
3460
elif source_minikind in 'fdlt' and target_minikind in 'r':
3461
# a rename; could be a true rename, or a rename inherited from
3462
# a renamed parent. TODO: handle this efficiently. Its not
3463
# common case to rename dirs though, so a correct but slow
3464
# implementation will do.
3465
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3466
self.search_specific_files.add(target_details[1])
3467
elif source_minikind in 'ra' and target_minikind in 'ra':
3468
# neither of the selected trees contain this file,
3469
# so skip over it. This is not currently directly tested, but
3470
# is indirectly via test_too_much.TestCommands.test_conflicts.
3473
raise AssertionError("don't know how to compare "
3474
"source_minikind=%r, target_minikind=%r"
3475
% (source_minikind, target_minikind))
3476
## import pdb;pdb.set_trace()
3482
def _gather_result_for_consistency(self, result):
3483
"""Check a result we will yield to make sure we are consistent later.
3485
This gathers result's parents into a set to output later.
3487
:param result: A result tuple.
3489
if not self.partial or not result[0]:
3491
self.seen_ids.add(result[0])
3492
new_path = result[1][1]
3494
# Not the root and not a delete: queue up the parents of the path.
3495
self.search_specific_file_parents.update(
3496
osutils.parent_directories(new_path.encode('utf8')))
3497
# Add the root directory which parent_directories does not
3499
self.search_specific_file_parents.add('')
3501
def iter_changes(self):
3502
"""Iterate over the changes."""
3503
utf8_decode = cache_utf8._utf8_decode
3504
_cmp_by_dirs = cmp_by_dirs
3505
_process_entry = self._process_entry
3506
search_specific_files = self.search_specific_files
3507
searched_specific_files = self.searched_specific_files
3508
splitpath = osutils.splitpath
3510
# compare source_index and target_index at or under each element of search_specific_files.
3511
# follow the following comparison table. Note that we only want to do diff operations when
3512
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
3516
# Source | Target | disk | action
3517
# r | fdlt | | add source to search, add id path move and perform
3518
# | | | diff check on source-target
3519
# r | fdlt | a | dangling file that was present in the basis.
3521
# r | a | | add source to search
3523
# r | r | | this path is present in a non-examined tree, skip.
3524
# r | r | a | this path is present in a non-examined tree, skip.
3525
# a | fdlt | | add new id
3526
# a | fdlt | a | dangling locally added file, skip
3527
# a | a | | not present in either tree, skip
3528
# a | a | a | not present in any tree, skip
3529
# a | r | | not present in either tree at this path, skip as it
3530
# | | | may not be selected by the users list of paths.
3531
# a | r | a | not present in either tree at this path, skip as it
3532
# | | | may not be selected by the users list of paths.
3533
# fdlt | fdlt | | content in both: diff them
3534
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
3535
# fdlt | a | | unversioned: output deleted id for now
3536
# fdlt | a | a | unversioned and deleted: output deleted id
3537
# fdlt | r | | relocated in this tree, so add target to search.
3538
# | | | Dont diff, we will see an r,fd; pair when we reach
3539
# | | | this id at the other path.
3540
# fdlt | r | a | relocated in this tree, so add target to search.
3541
# | | | Dont diff, we will see an r,fd; pair when we reach
3542
# | | | this id at the other path.
3544
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
3545
# keeping a cache of directories that we have seen.
3547
while search_specific_files:
3548
# TODO: the pending list should be lexically sorted? the
3549
# interface doesn't require it.
3550
current_root = search_specific_files.pop()
3551
current_root_unicode = current_root.decode('utf8')
3552
searched_specific_files.add(current_root)
3553
# process the entries for this containing directory: the rest will be
3554
# found by their parents recursively.
3555
root_entries = self.state._entries_for_path(current_root)
3556
root_abspath = self.tree.abspath(current_root_unicode)
3558
root_stat = os.lstat(root_abspath)
3560
if e.errno == errno.ENOENT:
3561
# the path does not exist: let _process_entry know that.
3562
root_dir_info = None
3564
# some other random error: hand it up.
3567
root_dir_info = ('', current_root,
3568
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3570
if root_dir_info[2] == 'directory':
3571
if self.tree._directory_is_tree_reference(
3572
current_root.decode('utf8')):
3573
root_dir_info = root_dir_info[:2] + \
3574
('tree-reference',) + root_dir_info[3:]
3576
if not root_entries and not root_dir_info:
3577
# this specified path is not present at all, skip it.
3579
path_handled = False
3580
for entry in root_entries:
3581
result, changed = _process_entry(entry, root_dir_info)
3582
if changed is not None:
3585
self._gather_result_for_consistency(result)
3586
if changed or self.include_unchanged:
3588
if self.want_unversioned and not path_handled and root_dir_info:
3589
new_executable = bool(
3590
stat.S_ISREG(root_dir_info[3].st_mode)
3591
and stat.S_IEXEC & root_dir_info[3].st_mode)
3593
(None, current_root_unicode),
3597
(None, splitpath(current_root_unicode)[-1]),
3598
(None, root_dir_info[2]),
3599
(None, new_executable)
3601
initial_key = (current_root, '', '')
3602
block_index, _ = self.state._find_block_index_from_key(initial_key)
3603
if block_index == 0:
3604
# we have processed the total root already, but because the
3605
# initial key matched it we should skip it here.
3607
if root_dir_info and root_dir_info[2] == 'tree-reference':
3608
current_dir_info = None
3610
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3612
current_dir_info = dir_iterator.next()
3614
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3615
# python 2.5 has e.errno == EINVAL,
3616
# and e.winerror == ERROR_DIRECTORY
3617
e_winerror = getattr(e, 'winerror', None)
3618
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
3619
# there may be directories in the inventory even though
3620
# this path is not a file on disk: so mark it as end of
3622
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
3623
current_dir_info = None
3624
elif (sys.platform == 'win32'
3625
and (e.errno in win_errors
3626
or e_winerror in win_errors)):
3627
current_dir_info = None
3631
if current_dir_info[0][0] == '':
3632
# remove .bzr from iteration
3633
bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3634
if current_dir_info[1][bzr_index][0] != '.bzr':
3635
raise AssertionError()
3636
del current_dir_info[1][bzr_index]
3637
# walk until both the directory listing and the versioned metadata
3639
if (block_index < len(self.state._dirblocks) and
3640
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3641
current_block = self.state._dirblocks[block_index]
3643
current_block = None
3644
while (current_dir_info is not None or
3645
current_block is not None):
3646
if (current_dir_info and current_block
3647
and current_dir_info[0][0] != current_block[0]):
3648
if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
3649
# filesystem data refers to paths not covered by the dirblock.
3650
# this has two possibilities:
3651
# A) it is versioned but empty, so there is no block for it
3652
# B) it is not versioned.
3654
# if (A) then we need to recurse into it to check for
3655
# new unknown files or directories.
3656
# if (B) then we should ignore it, because we don't
3657
# recurse into unknown directories.
3659
while path_index < len(current_dir_info[1]):
3660
current_path_info = current_dir_info[1][path_index]
3661
if self.want_unversioned:
3662
if current_path_info[2] == 'directory':
3663
if self.tree._directory_is_tree_reference(
3664
current_path_info[0].decode('utf8')):
3665
current_path_info = current_path_info[:2] + \
3666
('tree-reference',) + current_path_info[3:]
3667
new_executable = bool(
3668
stat.S_ISREG(current_path_info[3].st_mode)
3669
and stat.S_IEXEC & current_path_info[3].st_mode)
3671
(None, utf8_decode(current_path_info[0])[0]),
3675
(None, utf8_decode(current_path_info[1])[0]),
3676
(None, current_path_info[2]),
3677
(None, new_executable))
3678
# dont descend into this unversioned path if it is
3680
if current_path_info[2] in ('directory',
3682
del current_dir_info[1][path_index]
3686
# This dir info has been handled, go to the next
3688
current_dir_info = dir_iterator.next()
3689
except StopIteration:
3690
current_dir_info = None
3692
# We have a dirblock entry for this location, but there
3693
# is no filesystem path for this. This is most likely
3694
# because a directory was removed from the disk.
3695
# We don't have to report the missing directory,
3696
# because that should have already been handled, but we
3697
# need to handle all of the files that are contained
3699
for current_entry in current_block[1]:
3700
# entry referring to file not present on disk.
3701
# advance the entry only, after processing.
3702
result, changed = _process_entry(current_entry, None)
3703
if changed is not None:
3705
self._gather_result_for_consistency(result)
3706
if changed or self.include_unchanged:
3709
if (block_index < len(self.state._dirblocks) and
3710
osutils.is_inside(current_root,
3711
self.state._dirblocks[block_index][0])):
3712
current_block = self.state._dirblocks[block_index]
3714
current_block = None
3717
if current_block and entry_index < len(current_block[1]):
3718
current_entry = current_block[1][entry_index]
3720
current_entry = None
3721
advance_entry = True
3723
if current_dir_info and path_index < len(current_dir_info[1]):
3724
current_path_info = current_dir_info[1][path_index]
3725
if current_path_info[2] == 'directory':
3726
if self.tree._directory_is_tree_reference(
3727
current_path_info[0].decode('utf8')):
3728
current_path_info = current_path_info[:2] + \
3729
('tree-reference',) + current_path_info[3:]
3731
current_path_info = None
3733
path_handled = False
3734
while (current_entry is not None or
3735
current_path_info is not None):
3736
if current_entry is None:
3737
# the check for path_handled when the path is advanced
3738
# will yield this path if needed.
3740
elif current_path_info is None:
3741
# no path is fine: the per entry code will handle it.
3742
result, changed = _process_entry(current_entry, current_path_info)
3743
if changed is not None:
3745
self._gather_result_for_consistency(result)
3746
if changed or self.include_unchanged:
3748
elif (current_entry[0][1] != current_path_info[1]
3749
or current_entry[1][self.target_index][0] in 'ar'):
3750
# The current path on disk doesn't match the dirblock
3751
# record. Either the dirblock is marked as absent, or
3752
# the file on disk is not present at all in the
3753
# dirblock. Either way, report about the dirblock
3754
# entry, and let other code handle the filesystem one.
3756
# Compare the basename for these files to determine
3758
if current_path_info[1] < current_entry[0][1]:
3759
# extra file on disk: pass for now, but only
3760
# increment the path, not the entry
3761
advance_entry = False
3763
# entry referring to file not present on disk.
3764
# advance the entry only, after processing.
3765
result, changed = _process_entry(current_entry, None)
3766
if changed is not None:
3768
self._gather_result_for_consistency(result)
3769
if changed or self.include_unchanged:
3771
advance_path = False
3773
result, changed = _process_entry(current_entry, current_path_info)
3774
if changed is not None:
3777
self._gather_result_for_consistency(result)
3778
if changed or self.include_unchanged:
3780
if advance_entry and current_entry is not None:
3782
if entry_index < len(current_block[1]):
3783
current_entry = current_block[1][entry_index]
3785
current_entry = None
3787
advance_entry = True # reset the advance flaga
3788
if advance_path and current_path_info is not None:
3789
if not path_handled:
3790
# unversioned in all regards
3791
if self.want_unversioned:
3792
new_executable = bool(
3793
stat.S_ISREG(current_path_info[3].st_mode)
3794
and stat.S_IEXEC & current_path_info[3].st_mode)
3796
relpath_unicode = utf8_decode(current_path_info[0])[0]
3797
except UnicodeDecodeError:
3798
raise errors.BadFilenameEncoding(
3799
current_path_info[0], osutils._fs_enc)
3801
(None, relpath_unicode),
3805
(None, utf8_decode(current_path_info[1])[0]),
3806
(None, current_path_info[2]),
3807
(None, new_executable))
3808
# dont descend into this unversioned path if it is
3810
if current_path_info[2] in ('directory'):
3811
del current_dir_info[1][path_index]
3813
# dont descend the disk iterator into any tree
3815
if current_path_info[2] == 'tree-reference':
3816
del current_dir_info[1][path_index]
3819
if path_index < len(current_dir_info[1]):
3820
current_path_info = current_dir_info[1][path_index]
3821
if current_path_info[2] == 'directory':
3822
if self.tree._directory_is_tree_reference(
3823
current_path_info[0].decode('utf8')):
3824
current_path_info = current_path_info[:2] + \
3825
('tree-reference',) + current_path_info[3:]
3827
current_path_info = None
3828
path_handled = False
3830
advance_path = True # reset the advance flagg.
3831
if current_block is not None:
3833
if (block_index < len(self.state._dirblocks) and
3834
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3835
current_block = self.state._dirblocks[block_index]
3837
current_block = None
3838
if current_dir_info is not None:
3840
current_dir_info = dir_iterator.next()
3841
except StopIteration:
3842
current_dir_info = None
3843
for result in self._iter_specific_file_parents():
3846
def _iter_specific_file_parents(self):
3847
"""Iter over the specific file parents."""
3848
while self.search_specific_file_parents:
3849
# Process the parent directories for the paths we were iterating.
3850
# Even in extremely large trees this should be modest, so currently
3851
# no attempt is made to optimise.
3852
path_utf8 = self.search_specific_file_parents.pop()
3853
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3854
# We've examined this path.
3856
if path_utf8 in self.searched_exact_paths:
3857
# We've examined this path.
3859
path_entries = self.state._entries_for_path(path_utf8)
3860
# We need either one or two entries. If the path in
3861
# self.target_index has moved (so the entry in source_index is in
3862
# 'ar') then we need to also look for the entry for this path in
3863
# self.source_index, to output the appropriate delete-or-rename.
3864
selected_entries = []
3866
for candidate_entry in path_entries:
3867
# Find entries present in target at this path:
3868
if candidate_entry[1][self.target_index][0] not in 'ar':
3870
selected_entries.append(candidate_entry)
3871
# Find entries present in source at this path:
3872
elif (self.source_index is not None and
3873
candidate_entry[1][self.source_index][0] not in 'ar'):
3875
if candidate_entry[1][self.target_index][0] == 'a':
3876
# Deleted, emit it here.
3877
selected_entries.append(candidate_entry)
3879
# renamed, emit it when we process the directory it
3881
self.search_specific_file_parents.add(
3882
candidate_entry[1][self.target_index][1])
3884
raise AssertionError(
3885
"Missing entry for specific path parent %r, %r" % (
3886
path_utf8, path_entries))
3887
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3888
for entry in selected_entries:
3889
if entry[0][2] in self.seen_ids:
3891
result, changed = self._process_entry(entry, path_info)
3893
raise AssertionError(
3894
"Got entry<->path mismatch for specific path "
3895
"%r entry %r path_info %r " % (
3896
path_utf8, entry, path_info))
3897
# Only include changes - we're outside the users requested
3900
self._gather_result_for_consistency(result)
3901
if (result[6][0] == 'directory' and
3902
result[6][1] != 'directory'):
3903
# This stopped being a directory, the old children have
3905
if entry[1][self.source_index][0] == 'r':
3906
# renamed, take the source path
3907
entry_path_utf8 = entry[1][self.source_index][1]
3909
entry_path_utf8 = path_utf8
3910
initial_key = (entry_path_utf8, '', '')
3911
block_index, _ = self.state._find_block_index_from_key(
3913
if block_index == 0:
3914
# The children of the root are in block index 1.
3916
current_block = None
3917
if block_index < len(self.state._dirblocks):
3918
current_block = self.state._dirblocks[block_index]
3919
if not osutils.is_inside(
3920
entry_path_utf8, current_block[0]):
3921
# No entries for this directory at all.
3922
current_block = None
3923
if current_block is not None:
3924
for entry in current_block[1]:
3925
if entry[1][self.source_index][0] in 'ar':
3926
# Not in the source tree, so doesn't have to be
3929
# Path of the entry itself.
3931
self.search_specific_file_parents.add(
3932
osutils.pathjoin(*entry[0][:2]))
3933
if changed or self.include_unchanged:
3935
self.searched_exact_paths.add(path_utf8)
3937
def _path_info(self, utf8_path, unicode_path):
3938
"""Generate path_info for unicode_path.
3940
:return: None if unicode_path does not exist, or a path_info tuple.
3942
abspath = self.tree.abspath(unicode_path)
2397
cur_split = cache[cur]
2399
cur_split = cur.split('/')
2400
cache[cur] = cur_split
2401
if cur_split < dirname_split: lo = mid+1
3944
stat = os.lstat(abspath)
3946
if e.errno == errno.ENOENT:
3947
# the path does not exist.
3951
utf8_basename = utf8_path.rsplit('/', 1)[-1]
3952
dir_info = (utf8_path, utf8_basename,
3953
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3955
if dir_info[2] == 'directory':
3956
if self.tree._directory_is_tree_reference(
3958
self.root_dir_info = self.root_dir_info[:2] + \
3959
('tree-reference',) + self.root_dir_info[3:]
3963
# Try to load the compiled form if possible
3965
from bzrlib._dirstate_helpers_pyx import (
3971
ProcessEntryC as _process_entry,
3972
update_entry as update_entry,
3975
from bzrlib._dirstate_helpers_py import (
3982
# FIXME: It would be nice to be able to track moved lines so that the
3983
# corresponding python code can be moved to the _dirstate_helpers_py
3984
# module. I don't want to break the history for this important piece of
3985
# code so I left the code here -- vila 20090622
3986
update_entry = py_update_entry
3987
_process_entry = ProcessEntryPython