964
964
cdef class ProcessEntryC:
966
cdef int doing_consistency_expansion
966
967
cdef object old_dirname_to_file_id # dict
967
968
cdef object new_dirname_to_file_id # dict
968
cdef readonly object uninteresting
969
969
cdef object last_source_parent
970
970
cdef object last_target_parent
971
cdef object include_unchanged
971
cdef int include_unchanged
972
973
cdef object use_filesystem_for_exec
973
974
cdef object utf8_decode
974
975
cdef readonly object searched_specific_files
976
cdef readonly object searched_exact_paths
975
977
cdef object search_specific_files
978
# The parents up to the root of the paths we are searching.
979
# After all normal paths are returned, these specific items are returned.
980
cdef object search_specific_file_parents
976
981
cdef object state
977
982
# Current iteration variables:
978
983
cdef object current_root
990
995
cdef object current_block_list
991
996
cdef object current_dir_info
992
997
cdef object current_dir_list
998
cdef object _pending_consistent_entries # list
993
999
cdef int path_index
994
1000
cdef object root_dir_info
995
1001
cdef object bisect_left
996
1002
cdef object pathjoin
997
1003
cdef object fstat
1004
# A set of the ids we've output when doing partial output.
1005
cdef object seen_ids
998
1006
cdef object sha_file
1000
1008
def __init__(self, include_unchanged, use_filesystem_for_exec,
1001
1009
search_specific_files, state, source_index, target_index,
1002
1010
want_unversioned, tree):
1011
self.doing_consistency_expansion = 0
1003
1012
self.old_dirname_to_file_id = {}
1004
1013
self.new_dirname_to_file_id = {}
1005
# Just a sentry, so that _process_entry can say that this
1006
# record is handled, but isn't interesting to process (unchanged)
1007
self.uninteresting = object()
1014
# Are we doing a partial iter_changes?
1015
self.partial = set(['']).__ne__(search_specific_files)
1008
1016
# Using a list so that we can access the values and change them in
1009
1017
# nested scope. Each one is [path, file_id, entry]
1010
1018
self.last_source_parent = [None, None]
1011
1019
self.last_target_parent = [None, None]
1012
self.include_unchanged = include_unchanged
1020
if include_unchanged is None:
1021
self.include_unchanged = False
1023
self.include_unchanged = int(include_unchanged)
1013
1024
self.use_filesystem_for_exec = use_filesystem_for_exec
1014
1025
self.utf8_decode = cache_utf8._utf8_decode
1015
1026
# for all search_indexs in each path at or under each element of
1016
# search_specific_files, if the detail is relocated: add the id, and add the
1017
# relocated path as one to search if its not searched already. If the
1018
# detail is not relocated, add the id.
1027
# search_specific_files, if the detail is relocated: add the id, and
1028
# add the relocated path as one to search if its not searched already.
1029
# If the detail is not relocated, add the id.
1019
1030
self.searched_specific_files = set()
1031
# When we search exact paths without expanding downwards, we record
1033
self.searched_exact_paths = set()
1020
1034
self.search_specific_files = search_specific_files
1035
# The parents up to the root of the paths we are searching.
1036
# After all normal paths are returned, these specific items are returned.
1037
self.search_specific_file_parents = set()
1038
# The ids we've sent out in the delta.
1039
self.seen_ids = set()
1021
1040
self.state = state
1022
1041
self.current_root = None
1023
1042
self.current_root_unicode = None
1039
1058
self.current_block_pos = -1
1040
1059
self.current_dir_info = None
1041
1060
self.current_dir_list = None
1061
self._pending_consistent_entries = []
1042
1062
self.path_index = 0
1043
1063
self.root_dir_info = None
1044
1064
self.bisect_left = bisect.bisect_left
1045
1065
self.pathjoin = osutils.pathjoin
1046
1066
self.fstat = os.fstat
1047
1067
self.sha_file = osutils.sha_file
1068
if target_index != 0:
1069
# A lot of code in here depends on target_index == 0
1070
raise errors.BzrError('unsupported target index')
1049
1072
cdef _process_entry(self, entry, path_info):
1050
1073
"""Compare an entry and real disk to generate delta information.
1052
1075
:param path_info: top_relpath, basename, kind, lstat, abspath for
1053
the path of entry. If None, then the path is considered absent.
1054
(Perhaps we should pass in a concrete entry for this ?)
1076
the path of entry. If None, then the path is considered absent in
1077
the target (Perhaps we should pass in a concrete entry for this ?)
1055
1078
Basename is returned as a utf8 string because we expect this
1056
1079
tuple will be ignored, and don't want to take the time to
1058
:return: None if the these don't match
1059
A tuple of information about the change, or
1060
the object 'uninteresting' if these match, but are
1061
basically identical.
1081
:return: (iter_changes_result, changed). If the entry has not been
1082
handled then changed is None. Otherwise it is False if no content
1083
or metadata changes have occured, and True if any content or
1084
metadata change has occurred. If self.include_unchanged is True then
1085
if changed is not None, iter_changes_result will always be a result
1086
tuple. Otherwise, iter_changes_result is None unless changed is
1063
1089
cdef char target_minikind
1064
1090
cdef char source_minikind
1308
1341
(parent_id, None),
1309
1342
(self.utf8_decode(entry[0][1])[0], None),
1310
1343
(_minikind_to_kind(source_minikind), None),
1311
(source_details[3], None))
1344
(source_details[3], None)), True
1312
1345
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1313
1346
# a rename; could be a true rename, or a rename inherited from
1314
1347
# a renamed parent. TODO: handle this efficiently. Its not
1315
1348
# common case to rename dirs though, so a correct but slow
1316
1349
# implementation will do.
1317
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1350
if (not self.doing_consistency_expansion and
1351
not osutils.is_inside_any(self.searched_specific_files,
1352
target_details[1])):
1318
1353
self.search_specific_files.add(target_details[1])
1354
# We don't expand the specific files parents list here as
1355
# the path is absent in target and won't create a delta with
1319
1357
elif ((source_minikind == c'r' or source_minikind == c'a') and
1320
1358
(target_minikind == c'r' or target_minikind == c'a')):
1321
1359
# neither of the selected trees contain this path,
1401
1458
cdef char * current_dirname_c, * current_blockname_c
1402
1459
cdef int advance_entry, advance_path
1403
1460
cdef int path_handled
1404
uninteresting = self.uninteresting
1405
1461
searched_specific_files = self.searched_specific_files
1406
1462
# Are we walking a root?
1407
1463
while self.root_entries_pos < self.root_entries_len:
1408
1464
entry = self.root_entries[self.root_entries_pos]
1409
1465
self.root_entries_pos = self.root_entries_pos + 1
1410
result = self._process_entry(entry, self.root_dir_info)
1411
if result is not None and result is not self.uninteresting:
1466
result, changed = self._process_entry(entry, self.root_dir_info)
1467
if changed is not None:
1469
self._gather_result_for_consistency(result)
1470
if changed or self.include_unchanged:
1413
1472
# Have we finished the prior root, or never started one ?
1414
1473
if self.current_root is None:
1415
1474
# TODO: the pending list should be lexically sorted? the
1700
1771
# entry referring to file not present on disk.
1701
1772
# advance the entry only, after processing.
1702
result = self._process_entry(current_entry, None)
1703
if result is not None:
1704
if result is self.uninteresting:
1773
result, changed = self._process_entry(current_entry,
1706
1775
advance_path = 0
1708
1777
# paths are the same,and the dirstate entry is not
1709
1778
# absent or renamed.
1710
result = self._process_entry(current_entry, current_path_info)
1711
if result is not None:
1779
result, changed = self._process_entry(current_entry,
1781
if changed is not None:
1712
1782
path_handled = -1
1713
if result is self.uninteresting:
1783
if not changed and not self.include_unchanged:
1715
1785
# >- loop control starts here:
1717
1787
if advance_entry and current_entry is not None:
1776
1850
self.current_dir_list = self.current_dir_info[1]
1777
1851
except StopIteration:
1778
1852
self.current_dir_info = None
1854
cdef object _next_consistent_entries(self):
1855
"""Grabs the next specific file parent case to consider.
1857
:return: A list of the results, each of which is as for _process_entry.
1860
while self.search_specific_file_parents:
1861
# Process the parent directories for the paths we were iterating.
1862
# Even in extremely large trees this should be modest, so currently
1863
# no attempt is made to optimise.
1864
path_utf8 = self.search_specific_file_parents.pop()
1865
if path_utf8 in self.searched_exact_paths:
1866
# We've examined this path.
1868
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1869
# We've examined this path.
1871
path_entries = self.state._entries_for_path(path_utf8)
1872
# We need either one or two entries. If the path in
1873
# self.target_index has moved (so the entry in source_index is in
1874
# 'ar') then we need to also look for the entry for this path in
1875
# self.source_index, to output the appropriate delete-or-rename.
1876
selected_entries = []
1878
for candidate_entry in path_entries:
1879
# Find entries present in target at this path:
1880
if candidate_entry[1][self.target_index][0] not in 'ar':
1882
selected_entries.append(candidate_entry)
1883
# Find entries present in source at this path:
1884
elif (self.source_index is not None and
1885
candidate_entry[1][self.source_index][0] not in 'ar'):
1887
if candidate_entry[1][self.target_index][0] == 'a':
1888
# Deleted, emit it here.
1889
selected_entries.append(candidate_entry)
1891
# renamed, emit it when we process the directory it
1893
self.search_specific_file_parents.add(
1894
candidate_entry[1][self.target_index][1])
1896
raise AssertionError(
1897
"Missing entry for specific path parent %r, %r" % (
1898
path_utf8, path_entries))
1899
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1900
for entry in selected_entries:
1901
if entry[0][2] in self.seen_ids:
1903
result, changed = self._process_entry(entry, path_info)
1905
raise AssertionError(
1906
"Got entry<->path mismatch for specific path "
1907
"%r entry %r path_info %r " % (
1908
path_utf8, entry, path_info))
1909
# Only include changes - we're outside the users requested
1912
self._gather_result_for_consistency(result)
1913
if (result[6][0] == 'directory' and
1914
result[6][1] != 'directory'):
1915
# This stopped being a directory, the old children have
1917
if entry[1][self.source_index][0] == 'r':
1918
# renamed, take the source path
1919
entry_path_utf8 = entry[1][self.source_index][1]
1921
entry_path_utf8 = path_utf8
1922
initial_key = (entry_path_utf8, '', '')
1923
block_index, _ = self.state._find_block_index_from_key(
1925
if block_index == 0:
1926
# The children of the root are in block index 1.
1927
block_index = block_index + 1
1928
current_block = None
1929
if block_index < len(self.state._dirblocks):
1930
current_block = self.state._dirblocks[block_index]
1931
if not osutils.is_inside(
1932
entry_path_utf8, current_block[0]):
1933
# No entries for this directory at all.
1934
current_block = None
1935
if current_block is not None:
1936
for entry in current_block[1]:
1937
if entry[1][self.source_index][0] in 'ar':
1938
# Not in the source tree, so doesn't have to be
1941
# Path of the entry itself.
1942
self.search_specific_file_parents.add(
1943
self.pathjoin(*entry[0][:2]))
1944
if changed or self.include_unchanged:
1945
results.append((result, changed))
1946
self.searched_exact_paths.add(path_utf8)
1949
cdef object _path_info(self, utf8_path, unicode_path):
1950
"""Generate path_info for unicode_path.
1952
:return: None if unicode_path does not exist, or a path_info tuple.
1954
abspath = self.tree.abspath(unicode_path)
1956
stat = os.lstat(abspath)
1958
if e.errno == errno.ENOENT:
1959
# the path does not exist.
1963
utf8_basename = utf8_path.rsplit('/', 1)[-1]
1964
dir_info = (utf8_path, utf8_basename,
1965
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
1967
if dir_info[2] == 'directory':
1968
if self.tree._directory_is_tree_reference(
1970
self.root_dir_info = self.root_dir_info[:2] + \
1971
('tree-reference',) + self.root_dir_info[3:]