1340
1339
minikind = child[1][0][0]
1341
1340
fingerprint = child[1][0][4]
1342
1341
executable = child[1][0][3]
1343
old_child_path = osutils.pathjoin(child_dirname,
1342
old_child_path = osutils.pathjoin(child[0][0],
1345
1344
removals[child[0][2]] = old_child_path
1346
1345
child_suffix = child_dirname[len(old_path):]
1347
1346
new_child_dirname = (new_path + child_suffix)
1348
1347
key = (new_child_dirname, child_basename, child[0][2])
1349
new_child_path = osutils.pathjoin(new_child_dirname,
1348
new_child_path = os.path.join(new_child_dirname,
1351
1350
insertions[child[0][2]] = (key, minikind, executable,
1352
1351
fingerprint, new_child_path)
1353
1352
self._check_delta_ids_absent(new_ids, delta, 0)
2146
2143
def _get_id_index(self):
2147
"""Get an id index of self._dirblocks.
2149
This maps from file_id => [(directory, name, file_id)] entries where
2150
that file_id appears in one of the trees.
2144
"""Get an id index of self._dirblocks."""
2152
2145
if self._id_index is None:
2154
2147
for key, tree_details in self._iter_entries():
2155
self._add_to_id_index(id_index, key)
2148
id_index.setdefault(key[2], set()).add(key)
2156
2149
self._id_index = id_index
2157
2150
return self._id_index
2159
def _add_to_id_index(self, id_index, entry_key):
2160
"""Add this entry to the _id_index mapping."""
2161
# This code used to use a set for every entry in the id_index. However,
2162
# it is *rare* to have more than one entry. So a set is a large
2163
# overkill. And even when we do, we won't ever have more than the
2164
# number of parent trees. Which is still a small number (rarely >2). As
2165
# such, we use a simple tuple, and do our own uniqueness checks. While
2166
# the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2167
# cause quadratic failure.
2168
# TODO: This should use StaticTuple
2169
file_id = entry_key[2]
2170
entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2171
if file_id not in id_index:
2172
id_index[file_id] = static_tuple.StaticTuple(entry_key,)
2174
entry_keys = id_index[file_id]
2175
if entry_key not in entry_keys:
2176
id_index[file_id] = entry_keys + (entry_key,)
2178
def _remove_from_id_index(self, id_index, entry_key):
2179
"""Remove this entry from the _id_index mapping.
2181
It is an programming error to call this when the entry_key is not
2184
file_id = entry_key[2]
2185
entry_keys = list(id_index[file_id])
2186
entry_keys.remove(entry_key)
2187
id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
2189
2152
def _get_output_lines(self, lines):
2190
2153
"""Format lines for final output.
2503
2465
new_details = []
2504
2466
for lookup_index in xrange(tree_index):
2505
2467
# boundary case: this is the first occurence of file_id
2506
# so there are no id_indexes, possibly take this out of
2468
# so there are no id_indexs, possibly take this out of
2508
if not len(entry_keys):
2470
if not len(id_index[file_id]):
2509
2471
new_details.append(DirState.NULL_PARENT_DETAILS)
2511
2473
# grab any one entry, use it to find the right path.
2512
2474
# TODO: optimise this to reduce memory use in highly
2513
2475
# fragmented situations by reusing the relocation
2515
a_key = iter(entry_keys).next()
2477
a_key = iter(id_index[file_id]).next()
2516
2478
if by_path[a_key][lookup_index][0] in ('r', 'a'):
2517
2479
# its a pointer or missing statement, use it as is.
2518
2480
new_details.append(by_path[a_key][lookup_index])
3229
3167
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3230
3168
"last_source_parent", "last_target_parent", "include_unchanged",
3231
"partial", "use_filesystem_for_exec", "utf8_decode",
3232
"searched_specific_files", "search_specific_files",
3233
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3234
"state", "source_index", "target_index", "want_unversioned", "tree"]
3169
"use_filesystem_for_exec", "utf8_decode", "searched_specific_files",
3170
"search_specific_files", "state", "source_index", "target_index",
3171
"want_unversioned", "tree"]
3236
3173
def __init__(self, include_unchanged, use_filesystem_for_exec,
3237
3174
search_specific_files, state, source_index, target_index,
3238
3175
want_unversioned, tree):
3239
3176
self.old_dirname_to_file_id = {}
3240
3177
self.new_dirname_to_file_id = {}
3241
# Are we doing a partial iter_changes?
3242
self.partial = search_specific_files != set([''])
3243
3178
# Using a list so that we can access the values and change them in
3244
3179
# nested scope. Each one is [path, file_id, entry]
3245
3180
self.last_source_parent = [None, None]
3248
3183
self.use_filesystem_for_exec = use_filesystem_for_exec
3249
3184
self.utf8_decode = cache_utf8._utf8_decode
3250
3185
# for all search_indexs in each path at or under each element of
3251
# search_specific_files, if the detail is relocated: add the id, and
3252
# add the relocated path as one to search if its not searched already.
3253
# If the detail is not relocated, add the id.
3186
# search_specific_files, if the detail is relocated: add the id, and add the
3187
# relocated path as one to search if its not searched already. If the
3188
# detail is not relocated, add the id.
3254
3189
self.searched_specific_files = set()
3255
# When we search exact paths without expanding downwards, we record
3257
self.searched_exact_paths = set()
3258
3190
self.search_specific_files = search_specific_files
3259
# The parents up to the root of the paths we are searching.
3260
# After all normal paths are returned, these specific items are returned.
3261
self.search_specific_file_parents = set()
3262
# The ids we've sent out in the delta.
3263
self.seen_ids = set()
3264
3191
self.state = state
3265
3192
self.source_index = source_index
3266
3193
self.target_index = target_index
3267
if target_index != 0:
3268
# A lot of code in here depends on target_index == 0
3269
raise errors.BzrError('unsupported target index')
3270
3194
self.want_unversioned = want_unversioned
3271
3195
self.tree = tree
3274
3198
"""Compare an entry and real disk to generate delta information.
3276
3200
:param path_info: top_relpath, basename, kind, lstat, abspath for
3277
the path of entry. If None, then the path is considered absent in
3278
the target (Perhaps we should pass in a concrete entry for this ?)
3201
the path of entry. If None, then the path is considered absent.
3202
(Perhaps we should pass in a concrete entry for this ?)
3279
3203
Basename is returned as a utf8 string because we expect this
3280
3204
tuple will be ignored, and don't want to take the time to
3282
3206
:return: (iter_changes_result, changed). If the entry has not been
3283
3207
handled then changed is None. Otherwise it is False if no content
3284
or metadata changes have occurred, and True if any content or
3285
metadata change has occurred. If self.include_unchanged is True then
3208
or metadata changes have occured, and None if any content or
3209
metadata change has occured. If self.include_unchanged is True then
3286
3210
if changed is not None, iter_changes_result will always be a result
3287
3211
tuple. Otherwise, iter_changes_result is None unless changed is
3902
3795
current_dir_info = dir_iterator.next()
3903
3796
except StopIteration:
3904
3797
current_dir_info = None
3905
for result in self._iter_specific_file_parents():
3908
def _iter_specific_file_parents(self):
3909
"""Iter over the specific file parents."""
3910
while self.search_specific_file_parents:
3911
# Process the parent directories for the paths we were iterating.
3912
# Even in extremely large trees this should be modest, so currently
3913
# no attempt is made to optimise.
3914
path_utf8 = self.search_specific_file_parents.pop()
3915
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3916
# We've examined this path.
3918
if path_utf8 in self.searched_exact_paths:
3919
# We've examined this path.
3921
path_entries = self.state._entries_for_path(path_utf8)
3922
# We need either one or two entries. If the path in
3923
# self.target_index has moved (so the entry in source_index is in
3924
# 'ar') then we need to also look for the entry for this path in
3925
# self.source_index, to output the appropriate delete-or-rename.
3926
selected_entries = []
3928
for candidate_entry in path_entries:
3929
# Find entries present in target at this path:
3930
if candidate_entry[1][self.target_index][0] not in 'ar':
3932
selected_entries.append(candidate_entry)
3933
# Find entries present in source at this path:
3934
elif (self.source_index is not None and
3935
candidate_entry[1][self.source_index][0] not in 'ar'):
3937
if candidate_entry[1][self.target_index][0] == 'a':
3938
# Deleted, emit it here.
3939
selected_entries.append(candidate_entry)
3941
# renamed, emit it when we process the directory it
3943
self.search_specific_file_parents.add(
3944
candidate_entry[1][self.target_index][1])
3946
raise AssertionError(
3947
"Missing entry for specific path parent %r, %r" % (
3948
path_utf8, path_entries))
3949
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3950
for entry in selected_entries:
3951
if entry[0][2] in self.seen_ids:
3953
result, changed = self._process_entry(entry, path_info)
3955
raise AssertionError(
3956
"Got entry<->path mismatch for specific path "
3957
"%r entry %r path_info %r " % (
3958
path_utf8, entry, path_info))
3959
# Only include changes - we're outside the users requested
3962
self._gather_result_for_consistency(result)
3963
if (result[6][0] == 'directory' and
3964
result[6][1] != 'directory'):
3965
# This stopped being a directory, the old children have
3967
if entry[1][self.source_index][0] == 'r':
3968
# renamed, take the source path
3969
entry_path_utf8 = entry[1][self.source_index][1]
3971
entry_path_utf8 = path_utf8
3972
initial_key = (entry_path_utf8, '', '')
3973
block_index, _ = self.state._find_block_index_from_key(
3975
if block_index == 0:
3976
# The children of the root are in block index 1.
3978
current_block = None
3979
if block_index < len(self.state._dirblocks):
3980
current_block = self.state._dirblocks[block_index]
3981
if not osutils.is_inside(
3982
entry_path_utf8, current_block[0]):
3983
# No entries for this directory at all.
3984
current_block = None
3985
if current_block is not None:
3986
for entry in current_block[1]:
3987
if entry[1][self.source_index][0] in 'ar':
3988
# Not in the source tree, so doesn't have to be
3991
# Path of the entry itself.
3993
self.search_specific_file_parents.add(
3994
osutils.pathjoin(*entry[0][:2]))
3995
if changed or self.include_unchanged:
3997
self.searched_exact_paths.add(path_utf8)
3999
def _path_info(self, utf8_path, unicode_path):
4000
"""Generate path_info for unicode_path.
4002
:return: None if unicode_path does not exist, or a path_info tuple.
4004
abspath = self.tree.abspath(unicode_path)
4006
stat = os.lstat(abspath)
4008
if e.errno == errno.ENOENT:
4009
# the path does not exist.
4013
utf8_basename = utf8_path.rsplit('/', 1)[-1]
4014
dir_info = (utf8_path, utf8_basename,
4015
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
4017
if dir_info[2] == 'directory':
4018
if self.tree._directory_is_tree_reference(
4020
self.root_dir_info = self.root_dir_info[:2] + \
4021
('tree-reference',) + self.root_dir_info[3:]
4025
3800
# Try to load the compiled form if possible