1094
def update_entry(self, entry, abspath, stat_value,
1095
_stat_to_minikind=_stat_to_minikind,
1096
_pack_stat=pack_stat):
1097
"""Update the entry based on what is actually on disk.
1099
:param entry: This is the dirblock entry for the file in question.
1100
:param abspath: The path on disk for this file.
1101
:param stat_value: (optional) if we already have done a stat on the
1103
: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_dirname,
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 = osutils.pathjoin(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.
1107
1726
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
2278
3076
self._split_path_cache = {}
2280
3078
def _requires_lock(self):
2281
"""Checks that a lock is currently held by someone on the dirstate"""
3079
"""Check that a lock is currently held by someone on the dirstate."""
2282
3080
if not self._lock_token:
2283
3081
raise errors.ObjectNotLocked(self)
3084
def py_update_entry(state, entry, abspath, stat_value,
3085
_stat_to_minikind=DirState._stat_to_minikind,
3086
_pack_stat=pack_stat):
3087
"""Update the entry based on what is actually on disk.
3089
This function only calculates the sha if it needs to - if the entry is
3090
uncachable, or clearly different to the first parent's entry, no sha
3091
is calculated, and None is returned.
3093
:param state: The dirstate this entry is in.
3094
:param entry: This is the dirblock entry for the file in question.
3095
:param abspath: The path on disk for this file.
3096
:param stat_value: The stat value done on the path.
3097
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
3098
target of a symlink.
3101
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
3105
packed_stat = _pack_stat(stat_value)
3106
(saved_minikind, saved_link_or_sha1, saved_file_size,
3107
saved_executable, saved_packed_stat) = entry[1][0]
3109
if minikind == 'd' and saved_minikind == 't':
3111
if (minikind == saved_minikind
3112
and packed_stat == saved_packed_stat):
3113
# The stat hasn't changed since we saved, so we can re-use the
3118
# size should also be in packed_stat
3119
if saved_file_size == stat_value.st_size:
3120
return saved_link_or_sha1
3122
# If we have gotten this far, that means that we need to actually
3123
# process this entry.
3126
executable = state._is_executable(stat_value.st_mode,
3128
if state._cutoff_time is None:
3129
state._sha_cutoff_time()
3130
if (stat_value.st_mtime < state._cutoff_time
3131
and stat_value.st_ctime < state._cutoff_time
3132
and len(entry[1]) > 1
3133
and entry[1][1][0] != 'a'):
3134
# Could check for size changes for further optimised
3135
# avoidance of sha1's. However the most prominent case of
3136
# over-shaing is during initial add, which this catches.
3137
# Besides, if content filtering happens, size and sha
3138
# are calculated at the same time, so checking just the size
3139
# gains nothing w.r.t. performance.
3140
link_or_sha1 = state._sha1_file(abspath)
3141
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
3142
executable, packed_stat)
3144
entry[1][0] = ('f', '', stat_value.st_size,
3145
executable, DirState.NULLSTAT)
3146
elif minikind == 'd':
3148
entry[1][0] = ('d', '', 0, False, packed_stat)
3149
if saved_minikind != 'd':
3150
# This changed from something into a directory. Make sure we
3151
# have a directory block for it. This doesn't happen very
3152
# often, so this doesn't have to be super fast.
3153
block_index, entry_index, dir_present, file_present = \
3154
state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3155
state._ensure_block(block_index, entry_index,
3156
osutils.pathjoin(entry[0][0], entry[0][1]))
3157
elif minikind == 'l':
3158
link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3159
if state._cutoff_time is None:
3160
state._sha_cutoff_time()
3161
if (stat_value.st_mtime < state._cutoff_time
3162
and stat_value.st_ctime < state._cutoff_time):
3163
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
3166
entry[1][0] = ('l', '', stat_value.st_size,
3167
False, DirState.NULLSTAT)
3168
state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3172
class ProcessEntryPython(object):
3174
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3175
"last_source_parent", "last_target_parent", "include_unchanged",
3176
"partial", "use_filesystem_for_exec", "utf8_decode",
3177
"searched_specific_files", "search_specific_files",
3178
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3179
"state", "source_index", "target_index", "want_unversioned", "tree"]
3181
def __init__(self, include_unchanged, use_filesystem_for_exec,
3182
search_specific_files, state, source_index, target_index,
3183
want_unversioned, tree):
3184
self.old_dirname_to_file_id = {}
3185
self.new_dirname_to_file_id = {}
3186
# Are we doing a partial iter_changes?
3187
self.partial = search_specific_files != set([''])
3188
# Using a list so that we can access the values and change them in
3189
# nested scope. Each one is [path, file_id, entry]
3190
self.last_source_parent = [None, None]
3191
self.last_target_parent = [None, None]
3192
self.include_unchanged = include_unchanged
3193
self.use_filesystem_for_exec = use_filesystem_for_exec
3194
self.utf8_decode = cache_utf8._utf8_decode
3195
# for all search_indexs in each path at or under each element of
3196
# search_specific_files, if the detail is relocated: add the id, and
3197
# add the relocated path as one to search if its not searched already.
3198
# If the detail is not relocated, add the id.
3199
self.searched_specific_files = set()
3200
# When we search exact paths without expanding downwards, we record
3202
self.searched_exact_paths = set()
3203
self.search_specific_files = search_specific_files
3204
# The parents up to the root of the paths we are searching.
3205
# After all normal paths are returned, these specific items are returned.
3206
self.search_specific_file_parents = set()
3207
# The ids we've sent out in the delta.
3208
self.seen_ids = set()
3210
self.source_index = source_index
3211
self.target_index = target_index
3212
if target_index != 0:
3213
# A lot of code in here depends on target_index == 0
3214
raise errors.BzrError('unsupported target index')
3215
self.want_unversioned = want_unversioned
3218
def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
3219
"""Compare an entry and real disk to generate delta information.
3221
:param path_info: top_relpath, basename, kind, lstat, abspath for
3222
the path of entry. If None, then the path is considered absent in
3223
the target (Perhaps we should pass in a concrete entry for this ?)
3224
Basename is returned as a utf8 string because we expect this
3225
tuple will be ignored, and don't want to take the time to
3227
:return: (iter_changes_result, changed). If the entry has not been
3228
handled then changed is None. Otherwise it is False if no content
3229
or metadata changes have occurred, and True if any content or
3230
metadata change has occurred. If self.include_unchanged is True then
3231
if changed is not None, iter_changes_result will always be a result
3232
tuple. Otherwise, iter_changes_result is None unless changed is
3235
if self.source_index is None:
3236
source_details = DirState.NULL_PARENT_DETAILS
3238
source_details = entry[1][self.source_index]
3239
target_details = entry[1][self.target_index]
3240
target_minikind = target_details[0]
3241
if path_info is not None and target_minikind in 'fdlt':
3242
if not (self.target_index == 0):
3243
raise AssertionError()
3244
link_or_sha1 = update_entry(self.state, entry,
3245
abspath=path_info[4], stat_value=path_info[3])
3246
# The entry may have been modified by update_entry
3247
target_details = entry[1][self.target_index]
3248
target_minikind = target_details[0]
3251
file_id = entry[0][2]
3252
source_minikind = source_details[0]
3253
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
3254
# claimed content in both: diff
3255
# r | fdlt | | add source to search, add id path move and perform
3256
# | | | diff check on source-target
3257
# r | fdlt | a | dangling file that was present in the basis.
3259
if source_minikind in 'r':
3260
# add the source to the search path to find any children it
3261
# has. TODO ? : only add if it is a container ?
3262
if not osutils.is_inside_any(self.searched_specific_files,
3264
self.search_specific_files.add(source_details[1])
3265
# generate the old path; this is needed for stating later
3267
old_path = source_details[1]
3268
old_dirname, old_basename = os.path.split(old_path)
3269
path = pathjoin(entry[0][0], entry[0][1])
3270
old_entry = self.state._get_entry(self.source_index,
3272
# update the source details variable to be the real
3274
if old_entry == (None, None):
3275
raise errors.CorruptDirstate(self.state._filename,
3276
"entry '%s/%s' is considered renamed from %r"
3277
" but source does not exist\n"
3278
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3279
source_details = old_entry[1][self.source_index]
3280
source_minikind = source_details[0]
3282
old_dirname = entry[0][0]
3283
old_basename = entry[0][1]
3284
old_path = path = None
3285
if path_info is None:
3286
# the file is missing on disk, show as removed.
3287
content_change = True
3291
# source and target are both versioned and disk file is present.
3292
target_kind = path_info[2]
3293
if target_kind == 'directory':
3295
old_path = path = pathjoin(old_dirname, old_basename)
3296
self.new_dirname_to_file_id[path] = file_id
3297
if source_minikind != 'd':
3298
content_change = True
3300
# directories have no fingerprint
3301
content_change = False
3303
elif target_kind == 'file':
3304
if source_minikind != 'f':
3305
content_change = True
3307
# Check the sha. We can't just rely on the size as
3308
# content filtering may mean differ sizes actually
3309
# map to the same content
3310
if link_or_sha1 is None:
3312
statvalue, link_or_sha1 = \
3313
self.state._sha1_provider.stat_and_sha1(
3315
self.state._observed_sha1(entry, link_or_sha1,
3317
content_change = (link_or_sha1 != source_details[1])
3318
# Target details is updated at update_entry time
3319
if self.use_filesystem_for_exec:
3320
# We don't need S_ISREG here, because we are sure
3321
# we are dealing with a file.
3322
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
3324
target_exec = target_details[3]
3325
elif target_kind == 'symlink':
3326
if source_minikind != 'l':
3327
content_change = True
3329
content_change = (link_or_sha1 != source_details[1])
3331
elif target_kind == 'tree-reference':
3332
if source_minikind != 't':
3333
content_change = True
3335
content_change = False
3339
path = pathjoin(old_dirname, old_basename)
3340
raise errors.BadFileKindError(path, path_info[2])
3341
if source_minikind == 'd':
3343
old_path = path = pathjoin(old_dirname, old_basename)
3344
self.old_dirname_to_file_id[old_path] = file_id
3345
# parent id is the entry for the path in the target tree
3346
if old_basename and old_dirname == self.last_source_parent[0]:
3347
source_parent_id = self.last_source_parent[1]
3350
source_parent_id = self.old_dirname_to_file_id[old_dirname]
3352
source_parent_entry = self.state._get_entry(self.source_index,
3353
path_utf8=old_dirname)
3354
source_parent_id = source_parent_entry[0][2]
3355
if source_parent_id == entry[0][2]:
3356
# This is the root, so the parent is None
3357
source_parent_id = None
3359
self.last_source_parent[0] = old_dirname
3360
self.last_source_parent[1] = source_parent_id
3361
new_dirname = entry[0][0]
3362
if entry[0][1] and new_dirname == self.last_target_parent[0]:
3363
target_parent_id = self.last_target_parent[1]
3366
target_parent_id = self.new_dirname_to_file_id[new_dirname]
3368
# TODO: We don't always need to do the lookup, because the
3369
# parent entry will be the same as the source entry.
3370
target_parent_entry = self.state._get_entry(self.target_index,
3371
path_utf8=new_dirname)
3372
if target_parent_entry == (None, None):
3373
raise AssertionError(
3374
"Could not find target parent in wt: %s\nparent of: %s"
3375
% (new_dirname, entry))
3376
target_parent_id = target_parent_entry[0][2]
3377
if target_parent_id == entry[0][2]:
3378
# This is the root, so the parent is None
3379
target_parent_id = None
3381
self.last_target_parent[0] = new_dirname
3382
self.last_target_parent[1] = target_parent_id
3384
source_exec = source_details[3]
3385
changed = (content_change
3386
or source_parent_id != target_parent_id
3387
or old_basename != entry[0][1]
3388
or source_exec != target_exec
3390
if not changed and not self.include_unchanged:
3393
if old_path is None:
3394
old_path = path = pathjoin(old_dirname, old_basename)
3395
old_path_u = self.utf8_decode(old_path)[0]
3398
old_path_u = self.utf8_decode(old_path)[0]
3399
if old_path == path:
3402
path_u = self.utf8_decode(path)[0]
3403
source_kind = DirState._minikind_to_kind[source_minikind]
3404
return (entry[0][2],
3405
(old_path_u, path_u),
3408
(source_parent_id, target_parent_id),
3409
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3410
(source_kind, target_kind),
3411
(source_exec, target_exec)), changed
3412
elif source_minikind in 'a' and target_minikind in 'fdlt':
3413
# looks like a new file
3414
path = pathjoin(entry[0][0], entry[0][1])
3415
# parent id is the entry for the path in the target tree
3416
# TODO: these are the same for an entire directory: cache em.
3417
parent_id = self.state._get_entry(self.target_index,
3418
path_utf8=entry[0][0])[0][2]
3419
if parent_id == entry[0][2]:
3421
if path_info is not None:
3423
if self.use_filesystem_for_exec:
3424
# We need S_ISREG here, because we aren't sure if this
3427
stat.S_ISREG(path_info[3].st_mode)
3428
and stat.S_IEXEC & path_info[3].st_mode)
3430
target_exec = target_details[3]
3431
return (entry[0][2],
3432
(None, self.utf8_decode(path)[0]),
3436
(None, self.utf8_decode(entry[0][1])[0]),
3437
(None, path_info[2]),
3438
(None, target_exec)), True
3440
# Its a missing file, report it as such.
3441
return (entry[0][2],
3442
(None, self.utf8_decode(path)[0]),
3446
(None, self.utf8_decode(entry[0][1])[0]),
3448
(None, False)), True
3449
elif source_minikind in 'fdlt' and target_minikind in 'a':
3450
# unversioned, possibly, or possibly not deleted: we dont care.
3451
# if its still on disk, *and* theres no other entry at this
3452
# path [we dont know this in this routine at the moment -
3453
# perhaps we should change this - then it would be an unknown.
3454
old_path = pathjoin(entry[0][0], entry[0][1])
3455
# parent id is the entry for the path in the target tree
3456
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
3457
if parent_id == entry[0][2]:
3459
return (entry[0][2],
3460
(self.utf8_decode(old_path)[0], None),
3464
(self.utf8_decode(entry[0][1])[0], None),
3465
(DirState._minikind_to_kind[source_minikind], None),
3466
(source_details[3], None)), True
3467
elif source_minikind in 'fdlt' and target_minikind in 'r':
3468
# a rename; could be a true rename, or a rename inherited from
3469
# a renamed parent. TODO: handle this efficiently. Its not
3470
# common case to rename dirs though, so a correct but slow
3471
# implementation will do.
3472
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3473
self.search_specific_files.add(target_details[1])
3474
elif source_minikind in 'ra' and target_minikind in 'ra':
3475
# neither of the selected trees contain this file,
3476
# so skip over it. This is not currently directly tested, but
3477
# is indirectly via test_too_much.TestCommands.test_conflicts.
3480
raise AssertionError("don't know how to compare "
3481
"source_minikind=%r, target_minikind=%r"
3482
% (source_minikind, target_minikind))
3483
## import pdb;pdb.set_trace()
3489
def _gather_result_for_consistency(self, result):
3490
"""Check a result we will yield to make sure we are consistent later.
3492
This gathers result's parents into a set to output later.
3494
:param result: A result tuple.
3496
if not self.partial or not result[0]:
3498
self.seen_ids.add(result[0])
3499
new_path = result[1][1]
3501
# Not the root and not a delete: queue up the parents of the path.
3502
self.search_specific_file_parents.update(
3503
osutils.parent_directories(new_path.encode('utf8')))
3504
# Add the root directory which parent_directories does not
3506
self.search_specific_file_parents.add('')
3508
def iter_changes(self):
3509
"""Iterate over the changes."""
3510
utf8_decode = cache_utf8._utf8_decode
3511
_cmp_by_dirs = cmp_by_dirs
3512
_process_entry = self._process_entry
3513
search_specific_files = self.search_specific_files
3514
searched_specific_files = self.searched_specific_files
3515
splitpath = osutils.splitpath
3517
# compare source_index and target_index at or under each element of search_specific_files.
3518
# follow the following comparison table. Note that we only want to do diff operations when
3519
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
3523
# Source | Target | disk | action
3524
# r | fdlt | | add source to search, add id path move and perform
3525
# | | | diff check on source-target
3526
# r | fdlt | a | dangling file that was present in the basis.
3528
# r | a | | add source to search
3530
# r | r | | this path is present in a non-examined tree, skip.
3531
# r | r | a | this path is present in a non-examined tree, skip.
3532
# a | fdlt | | add new id
3533
# a | fdlt | a | dangling locally added file, skip
3534
# a | a | | not present in either tree, skip
3535
# a | a | a | not present in any tree, skip
3536
# a | r | | not present in either tree at this path, skip as it
3537
# | | | may not be selected by the users list of paths.
3538
# a | r | a | not present in either tree at this path, skip as it
3539
# | | | may not be selected by the users list of paths.
3540
# fdlt | fdlt | | content in both: diff them
3541
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
3542
# fdlt | a | | unversioned: output deleted id for now
3543
# fdlt | a | a | unversioned and deleted: output deleted id
3544
# fdlt | r | | relocated in this tree, so add target to search.
3545
# | | | Dont diff, we will see an r,fd; pair when we reach
3546
# | | | this id at the other path.
3547
# fdlt | r | a | relocated in this tree, so add target to search.
3548
# | | | Dont diff, we will see an r,fd; pair when we reach
3549
# | | | this id at the other path.
3551
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
3552
# keeping a cache of directories that we have seen.
3554
while search_specific_files:
3555
# TODO: the pending list should be lexically sorted? the
3556
# interface doesn't require it.
3557
current_root = search_specific_files.pop()
3558
current_root_unicode = current_root.decode('utf8')
3559
searched_specific_files.add(current_root)
3560
# process the entries for this containing directory: the rest will be
3561
# found by their parents recursively.
3562
root_entries = self.state._entries_for_path(current_root)
3563
root_abspath = self.tree.abspath(current_root_unicode)
3565
root_stat = os.lstat(root_abspath)
3567
if e.errno == errno.ENOENT:
3568
# the path does not exist: let _process_entry know that.
3569
root_dir_info = None
3571
# some other random error: hand it up.
3574
root_dir_info = ('', current_root,
3575
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3577
if root_dir_info[2] == 'directory':
3578
if self.tree._directory_is_tree_reference(
3579
current_root.decode('utf8')):
3580
root_dir_info = root_dir_info[:2] + \
3581
('tree-reference',) + root_dir_info[3:]
3583
if not root_entries and not root_dir_info:
3584
# this specified path is not present at all, skip it.
3586
path_handled = False
3587
for entry in root_entries:
3588
result, changed = _process_entry(entry, root_dir_info)
3589
if changed is not None:
3592
self._gather_result_for_consistency(result)
3593
if changed or self.include_unchanged:
3595
if self.want_unversioned and not path_handled and root_dir_info:
3596
new_executable = bool(
3597
stat.S_ISREG(root_dir_info[3].st_mode)
3598
and stat.S_IEXEC & root_dir_info[3].st_mode)
3600
(None, current_root_unicode),
3604
(None, splitpath(current_root_unicode)[-1]),
3605
(None, root_dir_info[2]),
3606
(None, new_executable)
3608
initial_key = (current_root, '', '')
3609
block_index, _ = self.state._find_block_index_from_key(initial_key)
3610
if block_index == 0:
3611
# we have processed the total root already, but because the
3612
# initial key matched it we should skip it here.
3614
if root_dir_info and root_dir_info[2] == 'tree-reference':
3615
current_dir_info = None
3617
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3619
current_dir_info = dir_iterator.next()
3621
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3622
# python 2.5 has e.errno == EINVAL,
3623
# and e.winerror == ERROR_DIRECTORY
3624
e_winerror = getattr(e, 'winerror', None)
3625
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
3626
# there may be directories in the inventory even though
3627
# this path is not a file on disk: so mark it as end of
3629
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
3630
current_dir_info = None
3631
elif (sys.platform == 'win32'
3632
and (e.errno in win_errors
3633
or e_winerror in win_errors)):
3634
current_dir_info = None
3638
if current_dir_info[0][0] == '':
3639
# remove .bzr from iteration
3640
bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3641
if current_dir_info[1][bzr_index][0] != '.bzr':
3642
raise AssertionError()
3643
del current_dir_info[1][bzr_index]
3644
# walk until both the directory listing and the versioned metadata
3646
if (block_index < len(self.state._dirblocks) and
3647
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3648
current_block = self.state._dirblocks[block_index]
3650
current_block = None
3651
while (current_dir_info is not None or
3652
current_block is not None):
3653
if (current_dir_info and current_block
3654
and current_dir_info[0][0] != current_block[0]):
3655
if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
3656
# filesystem data refers to paths not covered by the dirblock.
3657
# this has two possibilities:
3658
# A) it is versioned but empty, so there is no block for it
3659
# B) it is not versioned.
3661
# if (A) then we need to recurse into it to check for
3662
# new unknown files or directories.
3663
# if (B) then we should ignore it, because we don't
3664
# recurse into unknown directories.
3666
while path_index < len(current_dir_info[1]):
3667
current_path_info = current_dir_info[1][path_index]
3668
if self.want_unversioned:
3669
if current_path_info[2] == 'directory':
3670
if self.tree._directory_is_tree_reference(
3671
current_path_info[0].decode('utf8')):
3672
current_path_info = current_path_info[:2] + \
3673
('tree-reference',) + current_path_info[3:]
3674
new_executable = bool(
3675
stat.S_ISREG(current_path_info[3].st_mode)
3676
and stat.S_IEXEC & current_path_info[3].st_mode)
3678
(None, utf8_decode(current_path_info[0])[0]),
3682
(None, utf8_decode(current_path_info[1])[0]),
3683
(None, current_path_info[2]),
3684
(None, new_executable))
3685
# dont descend into this unversioned path if it is
3687
if current_path_info[2] in ('directory',
3689
del current_dir_info[1][path_index]
3693
# This dir info has been handled, go to the next
3695
current_dir_info = dir_iterator.next()
3696
except StopIteration:
3697
current_dir_info = None
3699
# We have a dirblock entry for this location, but there
3700
# is no filesystem path for this. This is most likely
3701
# because a directory was removed from the disk.
3702
# We don't have to report the missing directory,
3703
# because that should have already been handled, but we
3704
# need to handle all of the files that are contained
3706
for current_entry in current_block[1]:
3707
# entry referring to file not present on disk.
3708
# advance the entry only, after processing.
3709
result, changed = _process_entry(current_entry, None)
3710
if changed is not None:
3712
self._gather_result_for_consistency(result)
3713
if changed or self.include_unchanged:
3716
if (block_index < len(self.state._dirblocks) and
3717
osutils.is_inside(current_root,
3718
self.state._dirblocks[block_index][0])):
3719
current_block = self.state._dirblocks[block_index]
3721
current_block = None
3724
if current_block and entry_index < len(current_block[1]):
3725
current_entry = current_block[1][entry_index]
3727
current_entry = None
3728
advance_entry = True
3730
if current_dir_info and path_index < len(current_dir_info[1]):
3731
current_path_info = current_dir_info[1][path_index]
3732
if current_path_info[2] == 'directory':
3733
if self.tree._directory_is_tree_reference(
3734
current_path_info[0].decode('utf8')):
3735
current_path_info = current_path_info[:2] + \
3736
('tree-reference',) + current_path_info[3:]
3738
current_path_info = None
3740
path_handled = False
3741
while (current_entry is not None or
3742
current_path_info is not None):
3743
if current_entry is None:
3744
# the check for path_handled when the path is advanced
3745
# will yield this path if needed.
3747
elif current_path_info is None:
3748
# no path is fine: the per entry code will handle it.
3749
result, changed = _process_entry(current_entry, current_path_info)
3750
if changed is not None:
3752
self._gather_result_for_consistency(result)
3753
if changed or self.include_unchanged:
3755
elif (current_entry[0][1] != current_path_info[1]
3756
or current_entry[1][self.target_index][0] in 'ar'):
3757
# The current path on disk doesn't match the dirblock
3758
# record. Either the dirblock is marked as absent, or
3759
# the file on disk is not present at all in the
3760
# dirblock. Either way, report about the dirblock
3761
# entry, and let other code handle the filesystem one.
3763
# Compare the basename for these files to determine
3765
if current_path_info[1] < current_entry[0][1]:
3766
# extra file on disk: pass for now, but only
3767
# increment the path, not the entry
3768
advance_entry = False
3770
# entry referring to file not present on disk.
3771
# advance the entry only, after processing.
3772
result, changed = _process_entry(current_entry, None)
3773
if changed is not None:
3775
self._gather_result_for_consistency(result)
3776
if changed or self.include_unchanged:
3778
advance_path = False
3780
result, changed = _process_entry(current_entry, current_path_info)
3781
if changed is not None:
3784
self._gather_result_for_consistency(result)
3785
if changed or self.include_unchanged:
3787
if advance_entry and current_entry is not None:
3789
if entry_index < len(current_block[1]):
3790
current_entry = current_block[1][entry_index]
3792
current_entry = None
3794
advance_entry = True # reset the advance flaga
3795
if advance_path and current_path_info is not None:
3796
if not path_handled:
3797
# unversioned in all regards
3798
if self.want_unversioned:
3799
new_executable = bool(
3800
stat.S_ISREG(current_path_info[3].st_mode)
3801
and stat.S_IEXEC & current_path_info[3].st_mode)
3803
relpath_unicode = utf8_decode(current_path_info[0])[0]
3804
except UnicodeDecodeError:
3805
raise errors.BadFilenameEncoding(
3806
current_path_info[0], osutils._fs_enc)
3808
(None, relpath_unicode),
3812
(None, utf8_decode(current_path_info[1])[0]),
3813
(None, current_path_info[2]),
3814
(None, new_executable))
3815
# dont descend into this unversioned path if it is
3817
if current_path_info[2] in ('directory'):
3818
del current_dir_info[1][path_index]
3820
# dont descend the disk iterator into any tree
3822
if current_path_info[2] == 'tree-reference':
3823
del current_dir_info[1][path_index]
3826
if path_index < len(current_dir_info[1]):
3827
current_path_info = current_dir_info[1][path_index]
3828
if current_path_info[2] == 'directory':
3829
if self.tree._directory_is_tree_reference(
3830
current_path_info[0].decode('utf8')):
3831
current_path_info = current_path_info[:2] + \
3832
('tree-reference',) + current_path_info[3:]
3834
current_path_info = None
3835
path_handled = False
3837
advance_path = True # reset the advance flagg.
3838
if current_block is not None:
3840
if (block_index < len(self.state._dirblocks) and
3841
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3842
current_block = self.state._dirblocks[block_index]
3844
current_block = None
3845
if current_dir_info is not None:
3847
current_dir_info = dir_iterator.next()
3848
except StopIteration:
3849
current_dir_info = None
3850
for result in self._iter_specific_file_parents():
3853
def _iter_specific_file_parents(self):
3854
"""Iter over the specific file parents."""
3855
while self.search_specific_file_parents:
3856
# Process the parent directories for the paths we were iterating.
3857
# Even in extremely large trees this should be modest, so currently
3858
# no attempt is made to optimise.
3859
path_utf8 = self.search_specific_file_parents.pop()
3860
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3861
# We've examined this path.
3863
if path_utf8 in self.searched_exact_paths:
3864
# We've examined this path.
3866
path_entries = self.state._entries_for_path(path_utf8)
3867
# We need either one or two entries. If the path in
3868
# self.target_index has moved (so the entry in source_index is in
3869
# 'ar') then we need to also look for the entry for this path in
3870
# self.source_index, to output the appropriate delete-or-rename.
3871
selected_entries = []
3873
for candidate_entry in path_entries:
3874
# Find entries present in target at this path:
3875
if candidate_entry[1][self.target_index][0] not in 'ar':
3877
selected_entries.append(candidate_entry)
3878
# Find entries present in source at this path:
3879
elif (self.source_index is not None and
3880
candidate_entry[1][self.source_index][0] not in 'ar'):
3882
if candidate_entry[1][self.target_index][0] == 'a':
3883
# Deleted, emit it here.
3884
selected_entries.append(candidate_entry)
3886
# renamed, emit it when we process the directory it
3888
self.search_specific_file_parents.add(
3889
candidate_entry[1][self.target_index][1])
3891
raise AssertionError(
3892
"Missing entry for specific path parent %r, %r" % (
3893
path_utf8, path_entries))
3894
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3895
for entry in selected_entries:
3896
if entry[0][2] in self.seen_ids:
3898
result, changed = self._process_entry(entry, path_info)
3900
raise AssertionError(
3901
"Got entry<->path mismatch for specific path "
3902
"%r entry %r path_info %r " % (
3903
path_utf8, entry, path_info))
3904
# Only include changes - we're outside the users requested
3907
self._gather_result_for_consistency(result)
3908
if (result[6][0] == 'directory' and
3909
result[6][1] != 'directory'):
3910
# This stopped being a directory, the old children have
3912
if entry[1][self.source_index][0] == 'r':
3913
# renamed, take the source path
3914
entry_path_utf8 = entry[1][self.source_index][1]
3916
entry_path_utf8 = path_utf8
3917
initial_key = (entry_path_utf8, '', '')
3918
block_index, _ = self.state._find_block_index_from_key(
3920
if block_index == 0:
3921
# The children of the root are in block index 1.
3923
current_block = None
3924
if block_index < len(self.state._dirblocks):
3925
current_block = self.state._dirblocks[block_index]
3926
if not osutils.is_inside(
3927
entry_path_utf8, current_block[0]):
3928
# No entries for this directory at all.
3929
current_block = None
3930
if current_block is not None:
3931
for entry in current_block[1]:
3932
if entry[1][self.source_index][0] in 'ar':
3933
# Not in the source tree, so doesn't have to be
3936
# Path of the entry itself.
3938
self.search_specific_file_parents.add(
3939
osutils.pathjoin(*entry[0][:2]))
3940
if changed or self.include_unchanged:
3942
self.searched_exact_paths.add(path_utf8)
3944
def _path_info(self, utf8_path, unicode_path):
3945
"""Generate path_info for unicode_path.
3947
:return: None if unicode_path does not exist, or a path_info tuple.
3949
abspath = self.tree.abspath(unicode_path)
3951
stat = os.lstat(abspath)
3953
if e.errno == errno.ENOENT:
3954
# the path does not exist.
3958
utf8_basename = utf8_path.rsplit('/', 1)[-1]
3959
dir_info = (utf8_path, utf8_basename,
3960
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3962
if dir_info[2] == 'directory':
3963
if self.tree._directory_is_tree_reference(
3965
self.root_dir_info = self.root_dir_info[:2] + \
3966
('tree-reference',) + self.root_dir_info[3:]
2286
3970
# Try to load the compiled form if possible
2288
from bzrlib._dirstate_helpers_c import (
2289
_read_dirblocks_c as _read_dirblocks,
2290
bisect_dirblock_c as bisect_dirblock,
2291
_bisect_path_left_c as _bisect_path_left,
2292
_bisect_path_right_c as _bisect_path_right,
2293
cmp_by_dirs_c as cmp_by_dirs,
3972
from bzrlib._dirstate_helpers_pyx import (
3978
ProcessEntryC as _process_entry,
3979
update_entry as update_entry,
3981
except ImportError, e:
3982
osutils.failed_to_load_extension(e)
2296
3983
from bzrlib._dirstate_helpers_py import (
2297
_read_dirblocks_py as _read_dirblocks,
2298
bisect_dirblock_py as bisect_dirblock,
2299
_bisect_path_left_py as _bisect_path_left,
2300
_bisect_path_right_py as _bisect_path_right,
2301
cmp_by_dirs_py as cmp_by_dirs,
3990
# FIXME: It would be nice to be able to track moved lines so that the
3991
# corresponding python code can be moved to the _dirstate_helpers_py
3992
# module. I don't want to break the history for this important piece of
3993
# code so I left the code here -- vila 20090622
3994
update_entry = py_update_entry
3995
_process_entry = ProcessEntryPython