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])
3227
3165
class ProcessEntryPython(object):
3229
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3167
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
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([''])
3178
# Just a sentry, so that _process_entry can say that this
3179
# record is handled, but isn't interesting to process (unchanged)
3180
self.uninteresting = object()
3243
3181
# Using a list so that we can access the values and change them in
3244
3182
# nested scope. Each one is [path, file_id, entry]
3245
3183
self.last_source_parent = [None, None]
3248
3186
self.use_filesystem_for_exec = use_filesystem_for_exec
3249
3187
self.utf8_decode = cache_utf8._utf8_decode
3250
3188
# 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.
3189
# search_specific_files, if the detail is relocated: add the id, and add the
3190
# relocated path as one to search if its not searched already. If the
3191
# detail is not relocated, add the id.
3254
3192
self.searched_specific_files = set()
3255
# When we search exact paths without expanding downwards, we record
3257
self.searched_exact_paths = set()
3258
3193
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
3194
self.state = state
3265
3195
self.source_index = source_index
3266
3196
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
3197
self.want_unversioned = want_unversioned
3271
3198
self.tree = tree
3274
3201
"""Compare an entry and real disk to generate delta information.
3276
3203
: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 ?)
3204
the path of entry. If None, then the path is considered absent.
3205
(Perhaps we should pass in a concrete entry for this ?)
3279
3206
Basename is returned as a utf8 string because we expect this
3280
3207
tuple will be ignored, and don't want to take the time to
3282
:return: (iter_changes_result, changed). If the entry has not been
3283
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
3286
if changed is not None, iter_changes_result will always be a result
3287
tuple. Otherwise, iter_changes_result is None unless changed is
3209
:return: None if these don't match
3210
A tuple of information about the change, or
3211
the object 'uninteresting' if these match, but are
3212
basically identical.
3290
3214
if self.source_index is None:
3291
3215
source_details = DirState.NULL_PARENT_DETAILS
3536
3458
"source_minikind=%r, target_minikind=%r"
3537
3459
% (source_minikind, target_minikind))
3538
3460
## import pdb;pdb.set_trace()
3541
3463
def __iter__(self):
3544
def _gather_result_for_consistency(self, result):
3545
"""Check a result we will yield to make sure we are consistent later.
3547
This gathers result's parents into a set to output later.
3549
:param result: A result tuple.
3551
if not self.partial or not result[0]:
3553
self.seen_ids.add(result[0])
3554
new_path = result[1][1]
3556
# Not the root and not a delete: queue up the parents of the path.
3557
self.search_specific_file_parents.update(
3558
osutils.parent_directories(new_path.encode('utf8')))
3559
# Add the root directory which parent_directories does not
3561
self.search_specific_file_parents.add('')
3563
3466
def iter_changes(self):
3564
3467
"""Iterate over the changes."""
3565
3468
utf8_decode = cache_utf8._utf8_decode
3566
3469
_cmp_by_dirs = cmp_by_dirs
3567
3470
_process_entry = self._process_entry
3471
uninteresting = self.uninteresting
3568
3472
search_specific_files = self.search_specific_files
3569
3473
searched_specific_files = self.searched_specific_files
3570
3474
splitpath = osutils.splitpath
3825
3723
# entry referring to file not present on disk.
3826
3724
# advance the entry only, after processing.
3827
result, changed = _process_entry(current_entry, None)
3828
if changed is not None:
3830
self._gather_result_for_consistency(result)
3831
if changed or self.include_unchanged:
3725
result = _process_entry(current_entry, None)
3726
if result is not None:
3727
if result is not uninteresting:
3833
3729
advance_path = False
3835
result, changed = _process_entry(current_entry, current_path_info)
3836
if changed is not None:
3731
result = _process_entry(current_entry, current_path_info)
3732
if result is not None:
3837
3733
path_handled = True
3839
self._gather_result_for_consistency(result)
3840
if changed or self.include_unchanged:
3734
if result is not uninteresting:
3842
3736
if advance_entry and current_entry is not None:
3843
3737
entry_index += 1
3902
3796
current_dir_info = dir_iterator.next()
3903
3797
except StopIteration:
3904
3798
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
3801
# Try to load the compiled form if possible