264
265
# return '%X.%X' % (int(st.st_mtime), st.st_mode)
268
def _unpack_stat(packed_stat):
269
"""Turn a packed_stat back into the stat fields.
271
This is meant as a debugging tool, should not be used in real code.
273
(st_size, st_mtime, st_ctime, st_dev, st_ino,
274
st_mode) = struct.unpack('>LLLLLL', binascii.a2b_base64(packed_stat))
275
return dict(st_size=st_size, st_mtime=st_mtime, st_ctime=st_ctime,
276
st_dev=st_dev, st_ino=st_ino, st_mode=st_mode)
267
279
class SHA1Provider(object):
268
280
"""An interface for getting sha1s of a file."""
363
376
HEADER_FORMAT_2 = '#bazaar dirstate flat format 2\n'
364
377
HEADER_FORMAT_3 = '#bazaar dirstate flat format 3\n'
366
def __init__(self, path, sha1_provider):
379
def __init__(self, path, sha1_provider, worth_saving_limit=0):
367
380
"""Create a DirState object.
369
382
:param path: The path at which the dirstate file on disk should live.
370
383
:param sha1_provider: an object meeting the SHA1Provider interface.
384
:param worth_saving_limit: when the exact number of hash changed
385
entries is known, only bother saving the dirstate if more than
386
this count of entries have changed.
387
-1 means never save hash changes, 0 means always save hash changes.
372
389
# _header_state and _dirblock_state represent the current state
373
390
# of the dirstate metadata and the per-row data respectiely.
411
428
self._last_block_index = None
412
429
self._last_entry_index = None
430
# The set of known hash changes
431
self._known_hash_changes = set()
432
# How many hash changed entries can we have without saving
433
self._worth_saving_limit = worth_saving_limit
414
435
def __repr__(self):
415
436
return "%s(%r)" % \
416
437
(self.__class__.__name__, self._filename)
439
def _mark_modified(self, hash_changed_entries=None, header_modified=False):
440
"""Mark this dirstate as modified.
442
:param hash_changed_entries: if non-None, mark just these entries as
443
having their hash modified.
444
:param header_modified: mark the header modified as well, not just the
447
#trace.mutter_callsite(3, "modified hash entries: %s", hash_changed_entries)
448
if hash_changed_entries:
449
self._known_hash_changes.update([e[0] for e in hash_changed_entries])
450
if self._dirblock_state in (DirState.NOT_IN_MEMORY,
451
DirState.IN_MEMORY_UNMODIFIED):
452
# If the dirstate is already marked a IN_MEMORY_MODIFIED, then
453
# that takes precedence.
454
self._dirblock_state = DirState.IN_MEMORY_HASH_MODIFIED
456
# TODO: Since we now have a IN_MEMORY_HASH_MODIFIED state, we
457
# should fail noisily if someone tries to set
458
# IN_MEMORY_MODIFIED but we don't have a write-lock!
459
# We don't know exactly what changed so disable smart saving
460
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
462
self._header_state = DirState.IN_MEMORY_MODIFIED
464
def _mark_unmodified(self):
465
"""Mark this dirstate as unmodified."""
466
self._header_state = DirState.IN_MEMORY_UNMODIFIED
467
self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
468
self._known_hash_changes = set()
418
470
def add(self, path, file_id, kind, stat, fingerprint):
419
471
"""Add a path to be tracked.
546
598
if kind == 'directory':
547
599
# insert a new dirblock
548
600
self._ensure_block(block_index, entry_index, utf8path)
549
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
601
self._mark_modified()
550
602
if self._id_index:
551
self._id_index.setdefault(entry_key[2], set()).add(entry_key)
603
self._add_to_id_index(self._id_index, entry_key)
553
605
def _bisect(self, paths):
554
606
"""Bisect through the disk structure for specific rows.
1019
1071
self._ghosts = []
1020
1072
self._parents = [parents[0]]
1021
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1022
self._header_state = DirState.IN_MEMORY_MODIFIED
1073
self._mark_modified(header_modified=True)
1024
1075
def _empty_parent_info(self):
1025
1076
return [DirState.NULL_PARENT_DETAILS] * (len(self._parents) -
1329
def _check_delta_is_valid(self, delta):
1330
return list(inventory._check_delta_unique_ids(
1331
inventory._check_delta_unique_old_paths(
1332
inventory._check_delta_unique_new_paths(
1333
inventory._check_delta_ids_match_entry(
1334
inventory._check_delta_ids_are_valid(
1335
inventory._check_delta_new_path_entry_both_or_None(delta)))))))
1278
1337
def update_by_delta(self, delta):
1279
1338
"""Apply an inventory delta to the dirstate for tree 0
1298
1357
new_ids = set()
1299
1358
# This loop transforms the delta to single atomic operations that can
1300
1359
# 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))))),
1360
delta = sorted(self._check_delta_is_valid(delta), reverse=True)
1361
for old_path, new_path, file_id, inv_entry in delta:
1308
1362
if (file_id in insertions) or (file_id in removals):
1309
1363
raise errors.InconsistentDelta(old_path or new_path, file_id,
1310
1364
"repeated file_id")
1424
1478
Note that an exception during the operation of this method will leave
1425
1479
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
1481
:param new_revid: The new revision id for the trees parent.
1431
1482
:param delta: An inventory delta (see apply_inventory_delta) describing
1432
1483
the changes from the current left most parent revision to new_revid.
1567
1617
id_index = self._get_id_index()
1568
1618
for file_id in new_ids:
1569
for key in id_index.get(file_id, []):
1619
for key in id_index.get(file_id, ()):
1570
1620
block_i, entry_i, d_present, f_present = \
1571
1621
self._get_block_entry_index(key[0], key[1], tree_index)
1572
1622
if not f_present:
1603
1653
for old_path, new_path, file_id, new_details, real_add in adds:
1604
1654
# the entry for this file_id must be in tree 0.
1605
1655
entry = self._get_entry(0, file_id, new_path)
1606
if entry[0] is None or entry[0][2] != file_id:
1656
if entry[0] is None:
1657
# new_path is not versioned in the active WT state,
1658
# but we are adding it to the basis tree state, we
1659
# need to create a new entry record for it.
1660
dirname, basename = osutils.split(new_path)
1661
entry_key = (dirname, basename, file_id)
1662
_, block = self._find_block(entry_key, add_if_missing=True)
1663
index, present = self._find_entry_index(entry_key, block)
1665
raise AssertionError('entry was missing, but now was found')
1667
entry = (entry_key, [DirState.NULL_PARENT_DETAILS]*2)
1668
block.insert(index, entry)
1669
elif entry[0][2] != file_id:
1607
1670
self._changes_aborted = True
1608
1671
raise errors.InconsistentDelta(new_path, file_id,
1609
1672
'working tree does not contain new entry')
1669
1732
raise errors.InconsistentDelta(old_path, file_id,
1670
1733
'mismatched file_id in tree 1')
1671
1734
if real_delete:
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]
1735
if entry[1][0][0] == 'a':
1736
# The file was marked as deleted in the active
1737
# state, and it is now deleted in the basis state,
1738
# so just remove the record entirely
1739
del self._dirblocks[block_index][1][entry_index]
1741
# The basis entry needs to be marked deleted
1743
# If we are deleting a directory, we need to make sure
1744
# that all of its children are already deleted
1745
block_i, entry_i, d_present, f_present = \
1746
self._get_block_entry_index(old_path, '', 0)
1748
# The dir block is still present in the dirstate; this could
1749
# be due to it being in a parent tree, or a corrupt delta.
1750
for child_entry in self._dirblocks[block_i][1]:
1751
if child_entry[1][1][0] not in ('r', 'a'):
1752
self._changes_aborted = True
1753
raise errors.InconsistentDelta(old_path, entry[0][2],
1754
"The file id was deleted but its children were "
1679
1757
if entry[1][0][0] == 'a':
1680
1758
self._changes_aborted = True
1690
1768
del self._dirblocks[block_index][1][entry_index]
1692
1770
# it is being resurrected here, so blank it out temporarily.
1771
# should be equivalent to entry[1][1] = null
1693
1772
self._dirblocks[block_index][1][entry_index][1][1] = null
1695
1774
def _after_delta_check_parents(self, parents, index):
1733
1812
self._sha_cutoff_time()
1734
1813
if (stat_value.st_mtime < self._cutoff_time
1735
1814
and stat_value.st_ctime < self._cutoff_time):
1736
entry[1][0] = ('f', sha1, entry[1][0][2], entry[1][0][3],
1738
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1815
entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
1817
self._mark_modified([entry])
1740
1819
def _sha_cutoff_time(self):
1741
1820
"""Return cutoff time.
1799
1878
"""Serialise the entire dirstate to a sequence of lines."""
1800
1879
if (self._header_state == DirState.IN_MEMORY_UNMODIFIED and
1801
1880
self._dirblock_state == DirState.IN_MEMORY_UNMODIFIED):
1802
# read whats on disk.
1881
# read what's on disk.
1803
1882
self._state_file.seek(0)
1804
1883
return self._state_file.readlines()
1806
1885
lines.append(self._get_parents_line(self.get_parent_ids()))
1807
1886
lines.append(self._get_ghosts_line(self._ghosts))
1808
# append the root line which is special cased
1809
lines.extend(map(self._entry_to_line, self._iter_entries()))
1887
lines.extend(self._get_entry_lines())
1810
1888
return self._get_output_lines(lines)
1812
1890
def _get_ghosts_line(self, ghost_ids):
1817
1895
"""Create a line for the state file for parents information."""
1818
1896
return '\0'.join([str(len(parent_ids))] + parent_ids)
1898
def _get_entry_lines(self):
1899
"""Create lines for entries."""
1900
return map(self._entry_to_line, self._iter_entries())
1820
1902
def _get_fields_to_entry(self):
1821
1903
"""Get a function which converts entry fields into a entry record.
1980
2062
' tree_index, file_id and path')
1983
possible_keys = self._get_id_index().get(fileid_utf8, None)
2065
possible_keys = self._get_id_index().get(fileid_utf8, ())
1984
2066
if not possible_keys:
1985
2067
return None, None
1986
2068
for key in possible_keys:
2088
2170
executable = False
2090
2172
raise Exception("can't pack %s" % inv_entry)
2091
return (minikind, fingerprint, size, executable, tree_data)
2173
return static_tuple.StaticTuple(minikind, fingerprint, size,
2174
executable, tree_data)
2093
2176
def _iter_child_entries(self, tree_index, path_utf8):
2094
2177
"""Iterate over all the entries that are children of path_utf.
2145
2228
def _get_id_index(self):
2146
"""Get an id index of self._dirblocks."""
2229
"""Get an id index of self._dirblocks.
2231
This maps from file_id => [(directory, name, file_id)] entries where
2232
that file_id appears in one of the trees.
2147
2234
if self._id_index is None:
2149
2236
for key, tree_details in self._iter_entries():
2150
id_index.setdefault(key[2], set()).add(key)
2237
self._add_to_id_index(id_index, key)
2151
2238
self._id_index = id_index
2152
2239
return self._id_index
2241
def _add_to_id_index(self, id_index, entry_key):
2242
"""Add this entry to the _id_index mapping."""
2243
# This code used to use a set for every entry in the id_index. However,
2244
# it is *rare* to have more than one entry. So a set is a large
2245
# overkill. And even when we do, we won't ever have more than the
2246
# number of parent trees. Which is still a small number (rarely >2). As
2247
# such, we use a simple tuple, and do our own uniqueness checks. While
2248
# the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2249
# cause quadratic failure.
2250
# TODO: This should use StaticTuple
2251
file_id = entry_key[2]
2252
entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2253
if file_id not in id_index:
2254
id_index[file_id] = static_tuple.StaticTuple(entry_key,)
2256
entry_keys = id_index[file_id]
2257
if entry_key not in entry_keys:
2258
id_index[file_id] = entry_keys + (entry_key,)
2260
def _remove_from_id_index(self, id_index, entry_key):
2261
"""Remove this entry from the _id_index mapping.
2263
It is an programming error to call this when the entry_key is not
2266
file_id = entry_key[2]
2267
entry_keys = list(id_index[file_id])
2268
entry_keys.remove(entry_key)
2269
id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
2154
2271
def _get_output_lines(self, lines):
2155
2272
"""Format lines for final output.
2176
2293
"""The number of parent entries in each record row."""
2177
2294
return len(self._parents) - len(self._ghosts)
2180
def on_file(path, sha1_provider=None):
2297
def on_file(cls, path, sha1_provider=None, worth_saving_limit=0):
2181
2298
"""Construct a DirState on the file at path "path".
2183
2300
:param path: The path at which the dirstate file on disk should live.
2184
2301
:param sha1_provider: an object meeting the SHA1Provider interface.
2185
2302
If None, a DefaultSHA1Provider is used.
2303
:param worth_saving_limit: when the exact number of hash changed
2304
entries is known, only bother saving the dirstate if more than
2305
this count of entries have changed. -1 means never save.
2186
2306
:return: An unlocked DirState object, associated with the given path.
2188
2308
if sha1_provider is None:
2189
2309
sha1_provider = DefaultSHA1Provider()
2190
result = DirState(path, sha1_provider)
2310
result = cls(path, sha1_provider,
2311
worth_saving_limit=worth_saving_limit)
2193
2314
def _read_dirblocks_if_needed(self):
2285
2406
trace.mutter('Not saving DirState because '
2286
2407
'_changes_aborted is set.')
2288
if (self._header_state == DirState.IN_MEMORY_MODIFIED or
2289
self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
2409
# TODO: Since we now distinguish IN_MEMORY_MODIFIED from
2410
# IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail
2411
# to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we
2412
# fail to save IN_MEMORY_MODIFIED
2413
if self._worth_saving():
2291
2414
grabbed_write_lock = False
2292
2415
if self._lock_state != 'w':
2293
2416
grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
2301
2424
# We couldn't grab a write lock, so we switch back to a read one
2427
lines = self.get_lines()
2304
2428
self._state_file.seek(0)
2305
self._state_file.writelines(self.get_lines())
2429
self._state_file.writelines(lines)
2306
2430
self._state_file.truncate()
2307
2431
self._state_file.flush()
2308
self._header_state = DirState.IN_MEMORY_UNMODIFIED
2309
self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
2432
self._mark_unmodified()
2311
2434
if grabbed_write_lock:
2312
2435
self._lock_token = self._lock_token.restore_read_lock()
2315
2438
# not changed contents. Since restore_read_lock may
2316
2439
# not be an atomic operation.
2441
def _worth_saving(self):
2442
"""Is it worth saving the dirstate or not?"""
2443
if (self._header_state == DirState.IN_MEMORY_MODIFIED
2444
or self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
2446
if self._dirblock_state == DirState.IN_MEMORY_HASH_MODIFIED:
2447
if self._worth_saving_limit == -1:
2448
# We never save hash changes when the limit is -1
2450
# If we're using smart saving and only a small number of
2451
# entries have changed their hash, don't bother saving. John has
2452
# suggested using a heuristic here based on the size of the
2453
# changed files and/or tree. For now, we go with a configurable
2454
# number of changes, keeping the calculation time
2455
# as low overhead as possible. (This also keeps all existing
2456
# tests passing as the default is 0, i.e. always save.)
2457
if len(self._known_hash_changes) >= self._worth_saving_limit:
2318
2461
def _set_data(self, parent_ids, dirblocks):
2319
2462
"""Set the full dirstate data in memory.
2329
2472
# our memory copy is now authoritative.
2330
2473
self._dirblocks = dirblocks
2331
self._header_state = DirState.IN_MEMORY_MODIFIED
2332
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2474
self._mark_modified(header_modified=True)
2333
2475
self._parents = list(parent_ids)
2334
2476
self._id_index = None
2335
2477
self._packed_stat_index = None
2355
2497
self._make_absent(entry)
2356
2498
self.update_minimal(('', '', new_id), 'd',
2357
2499
path_utf8='', packed_stat=entry[1][0][4])
2358
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2500
self._mark_modified()
2501
# XXX: This was added by Ian, we need to make sure there
2502
# are tests for it, because it isn't in bzr.dev TRUNK
2503
# It looks like the only place it is called is in setting the root
2504
# id of the tree. So probably we never had an _id_index when we
2505
# don't even have a root yet.
2506
if self._id_index is not None:
2507
self._add_to_id_index(self._id_index, entry[0])
2360
2509
def set_parent_trees(self, trees, ghosts):
2361
2510
"""Set the parent trees for the dirstate.
2406
2555
parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
2407
2556
# how many trees do we end up with
2408
2557
parent_count = len(parent_trees)
2558
st = static_tuple.StaticTuple
2410
2560
# one: the current tree
2411
2561
for entry in self._iter_entries():
2415
2565
by_path[entry[0]] = [entry[1][0]] + \
2416
2566
[DirState.NULL_PARENT_DETAILS] * parent_count
2417
id_index[entry[0][2]] = set([entry[0]])
2567
# TODO: Possibly inline this, since we know it isn't present yet
2568
# id_index[entry[0][2]] = (entry[0],)
2569
self._add_to_id_index(id_index, entry[0])
2419
2571
# now the parent trees:
2420
2572
for tree_index, tree in enumerate(parent_trees):
2426
2578
# the suffix is from tree_index+1:parent_count+1.
2427
2579
new_location_suffix = [DirState.NULL_PARENT_DETAILS] * (parent_count - tree_index)
2428
2580
# now stitch in all the entries from this tree
2429
for path, entry in tree.inventory.iter_entries_by_dir():
2582
for path, entry in tree.iter_entries_by_dir():
2430
2583
# here we process each trees details for each item in the tree.
2431
2584
# we first update any existing entries for the id at other paths,
2432
2585
# then we either create or update the entry for the id at the
2439
2592
file_id = entry.file_id
2440
2593
path_utf8 = path.encode('utf8')
2441
2594
dirname, basename = osutils.split(path_utf8)
2442
new_entry_key = (dirname, basename, file_id)
2595
if dirname == last_dirname:
2596
# Try to re-use objects as much as possible
2597
dirname = last_dirname
2599
last_dirname = dirname
2600
new_entry_key = st(dirname, basename, file_id)
2443
2601
# tree index consistency: All other paths for this id in this tree
2444
2602
# index must point to the correct path.
2445
for entry_key in id_index.setdefault(file_id, set()):
2603
entry_keys = id_index.get(file_id, ())
2604
for entry_key in entry_keys:
2446
2605
# TODO:PROFILING: It might be faster to just update
2447
2606
# rather than checking if we need to, and then overwrite
2448
2607
# the one we are located at.
2451
2610
# other trees, so put absent pointers there
2452
2611
# This is the vertical axis in the matrix, all pointing
2453
2612
# to the real path.
2454
by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2455
# by path consistency: Insert into an existing path record (trivial), or
2456
# add a new one with relocation pointers for the other tree indexes.
2457
if new_entry_key in id_index[file_id]:
2458
# there is already an entry where this data belongs, just insert it.
2613
by_path[entry_key][tree_index] = st('r', path_utf8, 0,
2615
# by path consistency: Insert into an existing path record
2616
# (trivial), or add a new one with relocation pointers for the
2617
# other tree indexes.
2618
if new_entry_key in entry_keys:
2619
# there is already an entry where this data belongs, just
2459
2621
by_path[new_entry_key][tree_index] = \
2460
2622
self._inv_entry_to_details(entry)
2465
2627
new_details = []
2466
2628
for lookup_index in xrange(tree_index):
2467
2629
# boundary case: this is the first occurence of file_id
2468
# so there are no id_indexs, possibly take this out of
2630
# so there are no id_indexes, possibly take this out of
2470
if not len(id_index[file_id]):
2632
if not len(entry_keys):
2471
2633
new_details.append(DirState.NULL_PARENT_DETAILS)
2473
2635
# grab any one entry, use it to find the right path.
2474
# TODO: optimise this to reduce memory use in highly
2475
# fragmented situations by reusing the relocation
2477
a_key = iter(id_index[file_id]).next()
2636
a_key = iter(entry_keys).next()
2478
2637
if by_path[a_key][lookup_index][0] in ('r', 'a'):
2479
# its a pointer or missing statement, use it as is.
2638
# its a pointer or missing statement, use it as
2480
2640
new_details.append(by_path[a_key][lookup_index])
2482
2642
# we have the right key, make a pointer to it.
2483
2643
real_path = ('/'.join(a_key[0:2])).strip('/')
2484
new_details.append(('r', real_path, 0, False, ''))
2644
new_details.append(st('r', real_path, 0, False,
2485
2646
new_details.append(self._inv_entry_to_details(entry))
2486
2647
new_details.extend(new_location_suffix)
2487
2648
by_path[new_entry_key] = new_details
2488
id_index[file_id].add(new_entry_key)
2649
self._add_to_id_index(id_index, new_entry_key)
2489
2650
# --- end generation of full tree mappings
2491
2652
# sort and output all the entries
2493
2654
self._entries_to_current_state(new_entries)
2494
2655
self._parents = [rev_id for rev_id, tree in trees]
2495
2656
self._ghosts = list(ghosts)
2496
self._header_state = DirState.IN_MEMORY_MODIFIED
2497
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2657
self._mark_modified(header_modified=True)
2498
2658
self._id_index = id_index
2500
2660
def _sort_entries(self, entry_list):
2504
2664
try to keep everything in sorted blocks all the time, but sometimes
2505
2665
it's easier to sort after the fact.
2667
# When sorting, we usually have 10x more entries than directories. (69k
2668
# total entries, 4k directories). So cache the results of splitting.
2669
# Saving time and objects. Also, use StaticTuple to avoid putting all
2670
# of these object into python's garbage collector.
2672
def _key(entry, _split_dirs=split_dirs, _st=static_tuple.StaticTuple):
2508
2673
# sort by: directory parts, file name, file id
2509
return entry[0][0].split('/'), entry[0][1], entry[0][2]
2674
dirpath, fname, file_id = entry[0]
2676
split = _split_dirs[dirpath]
2678
split = _st.from_sequence(dirpath.split('/'))
2679
_split_dirs[dirpath] = split
2680
return _st(split, fname, file_id)
2510
2681
return sorted(entry_list, key=_key)
2512
2683
def set_state_from_inventory(self, new_inv):
2637
2808
current_old[0][1].decode('utf8'))
2638
2809
self._make_absent(current_old)
2639
2810
current_old = advance(old_iterator)
2640
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2811
self._mark_modified()
2641
2812
self._id_index = None
2642
2813
self._packed_stat_index = None
2644
2815
trace.mutter("set_state_from_inventory complete.")
2817
def set_state_from_scratch(self, working_inv, parent_trees, parent_ghosts):
2818
"""Wipe the currently stored state and set it to something new.
2820
This is a hard-reset for the data we are working with.
2822
# Technically, we really want a write lock, but until we write, we
2823
# don't really need it.
2824
self._requires_lock()
2825
# root dir and root dir contents with no children. We have to have a
2826
# root for set_state_from_inventory to work correctly.
2827
empty_root = (('', '', inventory.ROOT_ID),
2828
[('d', '', 0, False, DirState.NULLSTAT)])
2829
empty_tree_dirblocks = [('', [empty_root]), ('', [])]
2830
self._set_data([], empty_tree_dirblocks)
2831
self.set_state_from_inventory(working_inv)
2832
self.set_parent_trees(parent_trees, parent_ghosts)
2646
2834
def _make_absent(self, current_old):
2647
2835
"""Mark current_old - an entry - as absent for tree 0.
2673
2861
block[1].pop(entry_index)
2674
2862
# if we have an id_index in use, remove this key from it for this id.
2675
2863
if self._id_index is not None:
2676
self._id_index[current_old[0][2]].remove(current_old[0])
2864
self._remove_from_id_index(self._id_index, current_old[0])
2677
2865
# update all remaining keys for this id to record it as absent. The
2678
2866
# existing details may either be the record we are marking as deleted
2679
2867
# (if there were other trees with the id present at this path), or may
2692
2880
if update_tree_details[0][0] == 'a': # absent
2693
2881
raise AssertionError('bad row %r' % (update_tree_details,))
2694
2882
update_tree_details[0] = DirState.NULL_PARENT_DETAILS
2695
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2883
self._mark_modified()
2696
2884
return last_reference
2698
2886
def update_minimal(self, key, minikind, executable=False, fingerprint='',
2750
2938
# new entry, synthesis cross reference here,
2751
existing_keys = id_index.setdefault(key[2], set())
2939
existing_keys = id_index.get(key[2], ())
2752
2940
if not existing_keys:
2753
2941
# not currently in the state, simplest case
2754
2942
new_entry = key, [new_details] + self._empty_parent_info()
2786
2974
other_entry = other_block[other_entry_index]
2787
2975
other_entry[1][0] = ('r', path_utf8, 0, False, '')
2788
self._maybe_remove_row(other_block, other_entry_index,
2976
if self._maybe_remove_row(other_block, other_entry_index,
2978
# If the row holding this was removed, we need to
2979
# recompute where this entry goes
2980
entry_index, _ = self._find_entry_index(key, block)
2792
2983
# adds a tuple to the new details for each column
2794
2985
# - or by creating a new pointer to the right row inside that column
2795
2986
num_present_parents = self._num_present_parents()
2796
2987
if num_present_parents:
2988
# TODO: This re-evaluates the existing_keys set, do we need
2989
# to do that ourselves?
2797
2990
other_key = list(existing_keys)[0]
2798
2991
for lookup_index in xrange(1, num_present_parents + 1):
2799
2992
# grab any one entry, use it to find the right path.
2818
3011
pointer_path = osutils.pathjoin(*other_key[0:2])
2819
3012
new_entry[1].append(('r', pointer_path, 0, False, ''))
2820
3013
block.insert(entry_index, new_entry)
2821
existing_keys.add(key)
3014
self._add_to_id_index(id_index, key)
2823
3016
# Does the new state matter?
2824
3017
block[entry_index][1][0] = new_details
2833
3026
# converted to relocated.
2834
3027
if path_utf8 is None:
2835
3028
raise AssertionError('no path')
2836
for entry_key in id_index.setdefault(key[2], set()):
3029
existing_keys = id_index.get(key[2], ())
3030
if key not in existing_keys:
3031
raise AssertionError('We found the entry in the blocks, but'
3032
' the key is not in the id_index.'
3033
' key: %s, existing_keys: %s' % (key, existing_keys))
3034
for entry_key in existing_keys:
2837
3035
# TODO:PROFILING: It might be faster to just update
2838
3036
# rather than checking if we need to, and then overwrite
2839
3037
# the one we are located at.
2857
3055
if not present:
2858
3056
self._dirblocks.insert(block_index, (subdir_key[0], []))
2860
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
3058
self._mark_modified()
2862
3060
def _maybe_remove_row(self, block, index, id_index):
2863
3061
"""Remove index if it is absent or relocated across the row.
2865
3063
id_index is updated accordingly.
3064
:return: True if we removed the row, False otherwise
2867
3066
present_in_row = False
2868
3067
entry = block[index]
3144
3350
entry[1][0] = ('f', '', stat_value.st_size,
3145
3351
executable, DirState.NULLSTAT)
3352
worth_saving = False
3146
3353
elif minikind == 'd':
3147
3354
link_or_sha1 = None
3148
3355
entry[1][0] = ('d', '', 0, False, packed_stat)
3154
3361
state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3155
3362
state._ensure_block(block_index, entry_index,
3156
3363
osutils.pathjoin(entry[0][0], entry[0][1]))
3365
worth_saving = False
3157
3366
elif minikind == 'l':
3367
if saved_minikind == 'l':
3368
worth_saving = False
3158
3369
link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3159
3370
if state._cutoff_time is None:
3160
3371
state._sha_cutoff_time()
3166
3377
entry[1][0] = ('l', '', stat_value.st_size,
3167
3378
False, DirState.NULLSTAT)
3168
state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3380
state._mark_modified([entry])
3169
3381
return link_or_sha1