1278
1277
def update_by_delta(self, delta):
1279
1278
"""Apply an inventory delta to the dirstate for tree 0
1281
This is the workhorse for apply_inventory_delta in dirstate based
1284
1280
:param delta: An inventory delta. See Inventory.apply_delta for
1287
1283
self._read_dirblocks_if_needed()
1288
encode = cache_utf8.encode
1289
1284
insertions = {}
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))))),
1286
for old_path, new_path, file_id, inv_entry in sorted(delta, reverse=True):
1308
1287
if (file_id in insertions) or (file_id in removals):
1309
1288
raise errors.InconsistentDelta(old_path or new_path, file_id,
1310
1289
"repeated file_id")
1311
1290
if old_path is not None:
1312
1291
old_path = old_path.encode('utf-8')
1313
1292
removals[file_id] = old_path
1315
new_ids.add(file_id)
1316
1293
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
1294
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)
1295
dirname, basename = osutils.split(new_path)
1296
key = (dirname, basename, file_id)
1325
1297
minikind = DirState._kind_to_minikind[inv_entry.kind]
1326
1298
if minikind == 't':
1327
fingerprint = inv_entry.reference_revision or ''
1299
fingerprint = inv_entry.reference_revision
1329
1301
fingerprint = ''
1330
1302
insertions[file_id] = (key, minikind, inv_entry.executable,
1339
1311
minikind = child[1][0][0]
1340
1312
fingerprint = child[1][0][4]
1341
1313
executable = child[1][0][3]
1342
old_child_path = osutils.pathjoin(child_dirname,
1314
old_child_path = osutils.pathjoin(child[0][0],
1344
1316
removals[child[0][2]] = old_child_path
1345
1317
child_suffix = child_dirname[len(old_path):]
1346
1318
new_child_dirname = (new_path + child_suffix)
1347
1319
key = (new_child_dirname, child_basename, child[0][2])
1348
new_child_path = osutils.pathjoin(new_child_dirname,
1320
new_child_path = os.path.join(new_child_dirname,
1350
1322
insertions[child[0][2]] = (key, minikind, executable,
1351
1323
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.")
1324
self._apply_removals(removals.values())
1325
self._apply_insertions(insertions.values())
1367
1327
def _apply_removals(self, removals):
1368
for file_id, path in sorted(removals, reverse=True,
1369
key=operator.itemgetter(1)):
1328
for path in sorted(removals, reverse=True):
1370
1329
dirname, basename = osutils.split(path)
1371
1330
block_i, entry_i, d_present, f_present = \
1372
1331
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."
1332
entry = self._dirblocks[block_i][1][entry_i]
1388
1333
self._make_absent(entry)
1389
1334
# See if we have a malformed delta: deleting a directory must not
1390
1335
# leave crud behind. This increases the number of bisects needed
1398
1343
# be due to it being in a parent tree, or a corrupt delta.
1399
1344
for child_entry in self._dirblocks[block_i][1]:
1400
1345
if child_entry[1][0][0] not in ('r', 'a'):
1401
self._changes_aborted = True
1402
1346
raise errors.InconsistentDelta(path, entry[0][2],
1403
1347
"The file id was deleted but its children were "
1404
1348
"not deleted.")
1406
1350
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],
1351
for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1352
self.update_minimal(key, minikind, executable, fingerprint,
1353
path_utf8=path_utf8)
1416
1355
def update_basis_by_delta(self, delta, new_revid):
1417
1356
"""Update the parents of this tree after a commit.
1461
1400
# At the same time, to reduce interface friction we convert the input
1462
1401
# inventory entries to dirstate.
1463
1402
root_only = ('', '')
1464
# Accumulate parent references (path_utf8, id), to check for parentless
1403
# Accumulate parent references (path and id), to check for parentless
1465
1404
# items or items placed under files/links/tree-references. We get
1466
1405
# references from every item in the delta that is not a deletion and
1467
1406
# is not itself the root.
1468
1407
parents = set()
1469
# Added ids must not be in the dirstate already. This set holds those
1472
1408
for old_path, new_path, file_id, inv_entry in delta:
1473
1409
if inv_entry is not None and file_id != inv_entry.file_id:
1474
1410
raise errors.InconsistentDelta(new_path, file_id,
1475
1411
"mismatched entry file_id %r" % inv_entry)
1476
1412
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
1413
new_path_utf8 = encode(new_path)
1481
1414
# note the parent for validation
1482
1415
dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
1560
1492
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
1495
def _update_basis_apply_adds(self, adds):
1587
1496
"""Apply a sequence of adds to tree 1 during update_basis_by_delta.
1692
1600
# it is being resurrected here, so blank it out temporarily.
1693
1601
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.
1603
def _update_basis_check_parents(self, parents):
1604
"""Check that parents required by the delta are all intact."""
1703
1605
for dirname_utf8, file_id in parents:
1704
1606
# Get the entry - the ensures that file_id, dirname_utf8 exists and
1705
1607
# has the right file id.
1706
entry = self._get_entry(index, file_id, dirname_utf8)
1608
entry = self._get_entry(1, file_id, dirname_utf8)
1707
1609
if entry[1] is None:
1708
1610
self._changes_aborted = True
1709
1611
raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1710
1612
file_id, "This parent is not present.")
1711
1613
# Parents of things must be directories
1712
if entry[1][index][0] != 'd':
1614
if entry[1][1][0] != 'd':
1713
1615
self._changes_aborted = True
1714
1616
raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1715
1617
file_id, "This parent is not a directory.")
2579
2476
# we make both end conditions explicit
2580
2477
if not current_old:
2581
2478
# old is finished: insert current_new into the state.
2583
trace.mutter("Appending from new '%s'.",
2584
new_path_utf8.decode('utf8'))
2585
2479
self.update_minimal(new_entry_key, current_new_minikind,
2586
2480
executable=current_new[1].executable,
2587
path_utf8=new_path_utf8, fingerprint=fingerprint,
2481
path_utf8=new_path_utf8, fingerprint=fingerprint)
2589
2482
current_new = advance(new_iterator)
2590
2483
elif not current_new:
2591
2484
# new is finished
2593
trace.mutter("Truncating from old '%s/%s'.",
2594
current_old[0][0].decode('utf8'),
2595
current_old[0][1].decode('utf8'))
2596
2485
self._make_absent(current_old)
2597
2486
current_old = advance(old_iterator)
2598
2487
elif new_entry_key == current_old[0]:
2620
2505
and new_entry_key[1:] < current_old[0][1:])):
2621
2506
# new comes before:
2622
2507
# add a entry for this and advance new
2624
trace.mutter("Inserting from new '%s'.",
2625
new_path_utf8.decode('utf8'))
2626
2508
self.update_minimal(new_entry_key, current_new_minikind,
2627
2509
executable=current_new[1].executable,
2628
path_utf8=new_path_utf8, fingerprint=fingerprint,
2510
path_utf8=new_path_utf8, fingerprint=fingerprint)
2630
2511
current_new = advance(new_iterator)
2632
2513
# we've advanced past the place where the old key would be,
2633
2514
# without seeing it in the new list. so it must be gone.
2635
trace.mutter("Deleting from old '%s/%s'.",
2636
current_old[0][0].decode('utf8'),
2637
current_old[0][1].decode('utf8'))
2638
2515
self._make_absent(current_old)
2639
2516
current_old = advance(old_iterator)
2640
2517
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2641
2518
self._id_index = None
2642
2519
self._packed_stat_index = None
2644
trace.mutter("set_state_from_inventory complete.")
2646
2521
def _make_absent(self, current_old):
2647
2522
"""Mark current_old - an entry - as absent for tree 0.
2730
2602
new_details = (minikind, fingerprint, size, executable, packed_stat)
2731
2603
id_index = self._get_id_index()
2732
2604
if not present:
2733
# New record. Check there isn't a entry at this path already.
2735
low_index, _ = self._find_entry_index(key[0:2] + ('',), block)
2736
while low_index < len(block):
2737
entry = block[low_index]
2738
if entry[0][0:2] == key[0:2]:
2739
if entry[1][0][0] not in 'ar':
2740
# This entry has the same path (but a different id) as
2741
# the new entry we're adding, and is present in ths
2743
raise errors.InconsistentDelta(
2744
("%s/%s" % key[0:2]).decode('utf8'), key[2],
2745
"Attempt to add item at path already occupied by "
2746
"id %r" % entry[0][2])
2750
2605
# new entry, synthesis cross reference here,
2751
2606
existing_keys = id_index.setdefault(key[2], set())
2752
2607
if not existing_keys:
2757
2612
# grab one of them and use it to generate parent
2758
2613
# relocation/absent entries.
2759
2614
new_entry = key, [new_details]
2760
# existing_keys can be changed as we iterate.
2761
for other_key in tuple(existing_keys):
2615
for other_key in existing_keys:
2762
2616
# change the record at other to be a pointer to this new
2763
2617
# record. The loop looks similar to the change to
2764
2618
# relocations when updating an existing record but its not:
2765
2619
# the test for existing kinds is different: this can be
2766
2620
# factored out to a helper though.
2767
other_block_index, present = self._find_block_index_from_key(
2770
raise AssertionError('could not find block for %s' % (
2772
other_block = self._dirblocks[other_block_index][1]
2773
other_entry_index, present = self._find_entry_index(
2774
other_key, other_block)
2776
raise AssertionError(
2777
'update_minimal: could not find other entry for %s'
2621
other_block_index, present = self._find_block_index_from_key(other_key)
2623
raise AssertionError('could not find block for %s' % (other_key,))
2624
other_entry_index, present = self._find_entry_index(other_key,
2625
self._dirblocks[other_block_index][1])
2627
raise AssertionError('could not find entry for %s' % (other_key,))
2779
2628
if path_utf8 is None:
2780
2629
raise AssertionError('no path')
2781
# Turn this other location into a reference to the new
2782
# location. This also updates the aliased iterator
2783
# (current_old in set_state_from_inventory) so that the old
2784
# entry, if not already examined, is skipped over by that
2786
other_entry = other_block[other_entry_index]
2787
other_entry[1][0] = ('r', path_utf8, 0, False, '')
2788
self._maybe_remove_row(other_block, other_entry_index,
2630
self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
2631
('r', path_utf8, 0, False, '')
2792
# adds a tuple to the new details for each column
2793
# - either by copying an existing relocation pointer inside that column
2794
# - or by creating a new pointer to the right row inside that column
2795
2633
num_present_parents = self._num_present_parents()
2796
if num_present_parents:
2797
other_key = list(existing_keys)[0]
2798
2634
for lookup_index in xrange(1, num_present_parents + 1):
2799
2635
# grab any one entry, use it to find the right path.
2800
2636
# TODO: optimise this to reduce memory use in highly
3172
2986
class ProcessEntryPython(object):
3174
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
2988
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
3175
2989
"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"]
2990
"use_filesystem_for_exec", "utf8_decode", "searched_specific_files",
2991
"search_specific_files", "state", "source_index", "target_index",
2992
"want_unversioned", "tree"]
3181
2994
def __init__(self, include_unchanged, use_filesystem_for_exec,
3182
2995
search_specific_files, state, source_index, target_index,
3183
2996
want_unversioned, tree):
3184
2997
self.old_dirname_to_file_id = {}
3185
2998
self.new_dirname_to_file_id = {}
3186
# Are we doing a partial iter_changes?
3187
self.partial = search_specific_files != set([''])
2999
# Just a sentry, so that _process_entry can say that this
3000
# record is handled, but isn't interesting to process (unchanged)
3001
self.uninteresting = object()
3188
3002
# Using a list so that we can access the values and change them in
3189
3003
# nested scope. Each one is [path, file_id, entry]
3190
3004
self.last_source_parent = [None, None]
3193
3007
self.use_filesystem_for_exec = use_filesystem_for_exec
3194
3008
self.utf8_decode = cache_utf8._utf8_decode
3195
3009
# 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.
3010
# search_specific_files, if the detail is relocated: add the id, and add the
3011
# relocated path as one to search if its not searched already. If the
3012
# detail is not relocated, add the id.
3199
3013
self.searched_specific_files = set()
3200
# When we search exact paths without expanding downwards, we record
3202
self.searched_exact_paths = set()
3203
3014
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()
3209
3015
self.state = state
3210
3016
self.source_index = source_index
3211
3017
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
3018
self.want_unversioned = want_unversioned
3216
3019
self.tree = tree
3219
3022
"""Compare an entry and real disk to generate delta information.
3221
3024
: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 ?)
3025
the path of entry. If None, then the path is considered absent.
3026
(Perhaps we should pass in a concrete entry for this ?)
3224
3027
Basename is returned as a utf8 string because we expect this
3225
3028
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
3030
:return: None if these don't match
3031
A tuple of information about the change, or
3032
the object 'uninteresting' if these match, but are
3033
basically identical.
3235
3035
if self.source_index is None:
3236
3036
source_details = DirState.NULL_PARENT_DETAILS
3481
3279
"source_minikind=%r, target_minikind=%r"
3482
3280
% (source_minikind, target_minikind))
3483
3281
## import pdb;pdb.set_trace()
3486
3284
def __iter__(self):
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
3287
def iter_changes(self):
3509
3288
"""Iterate over the changes."""
3510
3289
utf8_decode = cache_utf8._utf8_decode
3511
3290
_cmp_by_dirs = cmp_by_dirs
3512
3291
_process_entry = self._process_entry
3292
uninteresting = self.uninteresting
3513
3293
search_specific_files = self.search_specific_files
3514
3294
searched_specific_files = self.searched_specific_files
3515
3295
splitpath = osutils.splitpath
3770
3544
# entry referring to file not present on disk.
3771
3545
# 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:
3546
result = _process_entry(current_entry, None)
3547
if result is not None:
3548
if result is not uninteresting:
3778
3550
advance_path = False
3780
result, changed = _process_entry(current_entry, current_path_info)
3781
if changed is not None:
3552
result = _process_entry(current_entry, current_path_info)
3553
if result is not None:
3782
3554
path_handled = True
3784
self._gather_result_for_consistency(result)
3785
if changed or self.include_unchanged:
3555
if result is not uninteresting:
3787
3557
if advance_entry and current_entry is not None:
3788
3558
entry_index += 1
3847
3617
current_dir_info = dir_iterator.next()
3848
3618
except StopIteration:
3849
3619
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:]
3970
3622
# Try to load the compiled form if possible