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."""
548
560
self._ensure_block(block_index, entry_index, utf8path)
549
561
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
550
562
if self._id_index:
551
self._id_index.setdefault(entry_key[2], set()).add(entry_key)
563
self._add_to_id_index(self._id_index, entry_key)
553
565
def _bisect(self, paths):
554
566
"""Bisect through the disk structure for specific rows.
1567
1579
id_index = self._get_id_index()
1568
1580
for file_id in new_ids:
1569
for key in id_index.get(file_id, []):
1581
for key in id_index.get(file_id, ()):
1570
1582
block_i, entry_i, d_present, f_present = \
1571
1583
self._get_block_entry_index(key[0], key[1], tree_index)
1572
1584
if not f_present:
1980
1992
' tree_index, file_id and path')
1983
possible_keys = self._get_id_index().get(fileid_utf8, None)
1995
possible_keys = self._get_id_index().get(fileid_utf8, ())
1984
1996
if not possible_keys:
1985
1997
return None, None
1986
1998
for key in possible_keys:
2145
2157
def _get_id_index(self):
2146
"""Get an id index of self._dirblocks."""
2158
"""Get an id index of self._dirblocks.
2160
This maps from file_id => [(directory, name, file_id)] entries where
2161
that file_id appears in one of the trees.
2147
2163
if self._id_index is None:
2149
2165
for key, tree_details in self._iter_entries():
2150
id_index.setdefault(key[2], set()).add(key)
2166
self._add_to_id_index(id_index, key)
2151
2167
self._id_index = id_index
2152
2168
return self._id_index
2170
def _add_to_id_index(self, id_index, entry_key):
2171
"""Add this entry to the _id_index mapping."""
2172
# This code used to use a set for every entry in the id_index. However,
2173
# it is *rare* to have more than one entry. So a set is a large
2174
# overkill. And even when we do, we won't ever have more than the
2175
# number of parent trees. Which is still a small number (rarely >2). As
2176
# such, we use a simple tuple, and do our own uniqueness checks. While
2177
# the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2178
# cause quadratic failure.
2179
# TODO: This should use StaticTuple
2180
file_id = entry_key[2]
2181
entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2182
if file_id not in id_index:
2183
id_index[file_id] = static_tuple.StaticTuple(entry_key,)
2185
entry_keys = id_index[file_id]
2186
if entry_key not in entry_keys:
2187
id_index[file_id] = entry_keys + (entry_key,)
2189
def _remove_from_id_index(self, id_index, entry_key):
2190
"""Remove this entry from the _id_index mapping.
2192
It is an programming error to call this when the entry_key is not
2195
file_id = entry_key[2]
2196
entry_keys = list(id_index[file_id])
2197
entry_keys.remove(entry_key)
2198
id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
2154
2200
def _get_output_lines(self, lines):
2155
2201
"""Format lines for final output.
2415
2461
by_path[entry[0]] = [entry[1][0]] + \
2416
2462
[DirState.NULL_PARENT_DETAILS] * parent_count
2417
id_index[entry[0][2]] = set([entry[0]])
2463
# TODO: Possibly inline this, since we know it isn't present yet
2464
# id_index[entry[0][2]] = (entry[0],)
2465
self._add_to_id_index(id_index, entry[0])
2419
2467
# now the parent trees:
2420
2468
for tree_index, tree in enumerate(parent_trees):
2442
2490
new_entry_key = (dirname, basename, file_id)
2443
2491
# tree index consistency: All other paths for this id in this tree
2444
2492
# index must point to the correct path.
2445
for entry_key in id_index.setdefault(file_id, set()):
2493
for entry_key in id_index.get(file_id, ()):
2446
2494
# TODO:PROFILING: It might be faster to just update
2447
2495
# rather than checking if we need to, and then overwrite
2448
2496
# the one we are located at.
2454
2502
by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2455
2503
# by path consistency: Insert into an existing path record (trivial), or
2456
2504
# add a new one with relocation pointers for the other tree indexes.
2457
if new_entry_key in id_index[file_id]:
2505
entry_keys = id_index.get(file_id, ())
2506
if new_entry_key in entry_keys:
2458
2507
# there is already an entry where this data belongs, just insert it.
2459
2508
by_path[new_entry_key][tree_index] = \
2460
2509
self._inv_entry_to_details(entry)
2465
2514
new_details = []
2466
2515
for lookup_index in xrange(tree_index):
2467
2516
# boundary case: this is the first occurence of file_id
2468
# so there are no id_indexs, possibly take this out of
2517
# so there are no id_indexes, possibly take this out of
2470
if not len(id_index[file_id]):
2519
if not len(entry_keys):
2471
2520
new_details.append(DirState.NULL_PARENT_DETAILS)
2473
2522
# grab any one entry, use it to find the right path.
2474
2523
# TODO: optimise this to reduce memory use in highly
2475
2524
# fragmented situations by reusing the relocation
2477
a_key = iter(id_index[file_id]).next()
2526
a_key = iter(entry_keys).next()
2478
2527
if by_path[a_key][lookup_index][0] in ('r', 'a'):
2479
2528
# its a pointer or missing statement, use it as is.
2480
2529
new_details.append(by_path[a_key][lookup_index])
2485
2534
new_details.append(self._inv_entry_to_details(entry))
2486
2535
new_details.extend(new_location_suffix)
2487
2536
by_path[new_entry_key] = new_details
2488
id_index[file_id].add(new_entry_key)
2537
self._add_to_id_index(id_index, new_entry_key)
2489
2538
# --- end generation of full tree mappings
2491
2540
# sort and output all the entries
2644
2693
trace.mutter("set_state_from_inventory complete.")
2695
def set_state_from_scratch(self, working_inv, parent_trees, parent_ghosts):
2696
"""Wipe the currently stored state and set it to something new.
2698
This is a hard-reset for the data we are working with.
2700
# Technically, we really want a write lock, but until we write, we
2701
# don't really need it.
2702
self._requires_lock()
2703
# root dir and root dir contents with no children. We have to have a
2704
# root for set_state_from_inventory to work correctly.
2705
empty_root = (('', '', inventory.ROOT_ID),
2706
[('d', '', 0, False, DirState.NULLSTAT)])
2707
empty_tree_dirblocks = [('', [empty_root]), ('', [])]
2708
self._set_data([], empty_tree_dirblocks)
2709
self.set_state_from_inventory(working_inv)
2710
self.set_parent_trees(parent_trees, parent_ghosts)
2646
2712
def _make_absent(self, current_old):
2647
2713
"""Mark current_old - an entry - as absent for tree 0.
2673
2739
block[1].pop(entry_index)
2674
2740
# if we have an id_index in use, remove this key from it for this id.
2675
2741
if self._id_index is not None:
2676
self._id_index[current_old[0][2]].remove(current_old[0])
2742
self._remove_from_id_index(self._id_index, current_old[0])
2677
2743
# update all remaining keys for this id to record it as absent. The
2678
2744
# existing details may either be the record we are marking as deleted
2679
2745
# (if there were other trees with the id present at this path), or may
2750
2816
# new entry, synthesis cross reference here,
2751
existing_keys = id_index.setdefault(key[2], set())
2817
existing_keys = id_index.get(key[2], ())
2752
2818
if not existing_keys:
2753
2819
# not currently in the state, simplest case
2754
2820
new_entry = key, [new_details] + self._empty_parent_info()
2786
2852
other_entry = other_block[other_entry_index]
2787
2853
other_entry[1][0] = ('r', path_utf8, 0, False, '')
2788
self._maybe_remove_row(other_block, other_entry_index,
2854
if self._maybe_remove_row(other_block, other_entry_index,
2856
# If the row holding this was removed, we need to
2857
# recompute where this entry goes
2858
entry_index, _ = self._find_entry_index(key, block)
2792
2861
# adds a tuple to the new details for each column
2794
2863
# - or by creating a new pointer to the right row inside that column
2795
2864
num_present_parents = self._num_present_parents()
2796
2865
if num_present_parents:
2866
# TODO: This re-evaluates the existing_keys set, do we need
2867
# to do that ourselves?
2797
2868
other_key = list(existing_keys)[0]
2798
2869
for lookup_index in xrange(1, num_present_parents + 1):
2799
2870
# grab any one entry, use it to find the right path.
2818
2889
pointer_path = osutils.pathjoin(*other_key[0:2])
2819
2890
new_entry[1].append(('r', pointer_path, 0, False, ''))
2820
2891
block.insert(entry_index, new_entry)
2821
existing_keys.add(key)
2892
self._add_to_id_index(id_index, key)
2823
2894
# Does the new state matter?
2824
2895
block[entry_index][1][0] = new_details
2833
2904
# converted to relocated.
2834
2905
if path_utf8 is None:
2835
2906
raise AssertionError('no path')
2836
for entry_key in id_index.setdefault(key[2], set()):
2907
existing_keys = id_index.get(key[2], ())
2908
if key not in existing_keys:
2909
raise AssertionError('We found the entry in the blocks, but'
2910
' the key is not in the id_index.'
2911
' key: %s, existing_keys: %s' % (key, existing_keys))
2912
for entry_key in existing_keys:
2837
2913
# TODO:PROFILING: It might be faster to just update
2838
2914
# rather than checking if we need to, and then overwrite
2839
2915
# the one we are located at.