650
656
# Build up the key that will be used.
651
657
# By using <object>(void *) Pyrex will automatically handle the
652
658
# Py_INCREF that we need.
653
path_name_file_id_key = (<object>p_current_dirname[0],
659
cur_dirname = <object>p_current_dirname[0]
660
# Use StaticTuple_New to pre-allocate, rather than creating a regular
661
# tuple and passing it to the StaticTuple constructor.
662
# path_name_file_id_key = StaticTuple(<object>p_current_dirname[0],
663
# self.get_next_str(),
664
# self.get_next_str(),
666
tmp = StaticTuple_New(3)
667
Py_INCREF(cur_dirname); StaticTuple_SET_ITEM(tmp, 0, cur_dirname)
668
cur_basename = self.get_next_str()
669
cur_file_id = self.get_next_str()
670
Py_INCREF(cur_basename); StaticTuple_SET_ITEM(tmp, 1, cur_basename)
671
Py_INCREF(cur_file_id); StaticTuple_SET_ITEM(tmp, 2, cur_file_id)
672
path_name_file_id_key = tmp
658
674
# Parse all of the per-tree information. current has the information in
659
675
# the same location as parent trees. The only difference is that 'info'
677
693
executable_cstr = self.get_next(&cur_size)
678
694
is_executable = (executable_cstr[0] == c'y')
679
695
info = self.get_next_str()
680
PyList_Append(trees, (
696
# TODO: If we want to use StaticTuple_New here we need to be pretty
697
# careful. We are relying on a bit of Pyrex
698
# automatic-conversion from 'int' to PyInt, and that doesn't
699
# play well with the StaticTuple_SET_ITEM macro.
700
# Timing doesn't (yet) show a worthwile improvement in speed
701
# versus complexity and maintainability.
702
# tmp = StaticTuple_New(5)
703
# Py_INCREF(minikind); StaticTuple_SET_ITEM(tmp, 0, minikind)
704
# Py_INCREF(fingerprint); StaticTuple_SET_ITEM(tmp, 1, fingerprint)
705
# Py_INCREF(entry_size); StaticTuple_SET_ITEM(tmp, 2, entry_size)
706
# Py_INCREF(is_executable); StaticTuple_SET_ITEM(tmp, 3, is_executable)
707
# Py_INCREF(info); StaticTuple_SET_ITEM(tmp, 4, info)
708
# PyList_Append(trees, tmp)
709
PyList_Append(trees, StaticTuple(
681
710
minikind, # minikind
682
711
fingerprint, # fingerprint
683
712
entry_size, # size
964
994
cdef class ProcessEntryC:
996
cdef int doing_consistency_expansion
966
997
cdef object old_dirname_to_file_id # dict
967
998
cdef object new_dirname_to_file_id # dict
968
999
cdef object last_source_parent
969
1000
cdef object last_target_parent
970
cdef object include_unchanged
1001
cdef int include_unchanged
971
1003
cdef object use_filesystem_for_exec
972
1004
cdef object utf8_decode
973
1005
cdef readonly object searched_specific_files
1006
cdef readonly object searched_exact_paths
974
1007
cdef object search_specific_files
1008
# The parents up to the root of the paths we are searching.
1009
# After all normal paths are returned, these specific items are returned.
1010
cdef object search_specific_file_parents
975
1011
cdef object state
976
1012
# Current iteration variables:
977
1013
cdef object current_root
989
1025
cdef object current_block_list
990
1026
cdef object current_dir_info
991
1027
cdef object current_dir_list
1028
cdef object _pending_consistent_entries # list
992
1029
cdef int path_index
993
1030
cdef object root_dir_info
994
1031
cdef object bisect_left
995
1032
cdef object pathjoin
996
1033
cdef object fstat
1034
# A set of the ids we've output when doing partial output.
1035
cdef object seen_ids
997
1036
cdef object sha_file
999
1038
def __init__(self, include_unchanged, use_filesystem_for_exec,
1000
1039
search_specific_files, state, source_index, target_index,
1001
1040
want_unversioned, tree):
1041
self.doing_consistency_expansion = 0
1002
1042
self.old_dirname_to_file_id = {}
1003
1043
self.new_dirname_to_file_id = {}
1044
# Are we doing a partial iter_changes?
1045
self.partial = set(['']).__ne__(search_specific_files)
1004
1046
# Using a list so that we can access the values and change them in
1005
1047
# nested scope. Each one is [path, file_id, entry]
1006
1048
self.last_source_parent = [None, None]
1007
1049
self.last_target_parent = [None, None]
1008
self.include_unchanged = include_unchanged
1050
if include_unchanged is None:
1051
self.include_unchanged = False
1053
self.include_unchanged = int(include_unchanged)
1009
1054
self.use_filesystem_for_exec = use_filesystem_for_exec
1010
1055
self.utf8_decode = cache_utf8._utf8_decode
1011
1056
# for all search_indexs in each path at or under each element of
1012
# search_specific_files, if the detail is relocated: add the id, and add the
1013
# relocated path as one to search if its not searched already. If the
1014
# detail is not relocated, add the id.
1057
# search_specific_files, if the detail is relocated: add the id, and
1058
# add the relocated path as one to search if its not searched already.
1059
# If the detail is not relocated, add the id.
1015
1060
self.searched_specific_files = set()
1061
# When we search exact paths without expanding downwards, we record
1063
self.searched_exact_paths = set()
1016
1064
self.search_specific_files = search_specific_files
1065
# The parents up to the root of the paths we are searching.
1066
# After all normal paths are returned, these specific items are returned.
1067
self.search_specific_file_parents = set()
1068
# The ids we've sent out in the delta.
1069
self.seen_ids = set()
1017
1070
self.state = state
1018
1071
self.current_root = None
1019
1072
self.current_root_unicode = None
1035
1088
self.current_block_pos = -1
1036
1089
self.current_dir_info = None
1037
1090
self.current_dir_list = None
1091
self._pending_consistent_entries = []
1038
1092
self.path_index = 0
1039
1093
self.root_dir_info = None
1040
1094
self.bisect_left = bisect.bisect_left
1041
1095
self.pathjoin = osutils.pathjoin
1042
1096
self.fstat = os.fstat
1043
1097
self.sha_file = osutils.sha_file
1098
if target_index != 0:
1099
# A lot of code in here depends on target_index == 0
1100
raise errors.BzrError('unsupported target index')
1045
1102
cdef _process_entry(self, entry, path_info):
1046
1103
"""Compare an entry and real disk to generate delta information.
1048
1105
:param path_info: top_relpath, basename, kind, lstat, abspath for
1049
the path of entry. If None, then the path is considered absent.
1050
(Perhaps we should pass in a concrete entry for this ?)
1106
the path of entry. If None, then the path is considered absent in
1107
the target (Perhaps we should pass in a concrete entry for this ?)
1051
1108
Basename is returned as a utf8 string because we expect this
1052
1109
tuple will be ignored, and don't want to take the time to
1054
1111
:return: (iter_changes_result, changed). If the entry has not been
1055
1112
handled then changed is None. Otherwise it is False if no content
1056
or metadata changes have occured, and None if any content or
1057
metadata change has occured. If self.include_unchanged is True then
1113
or metadata changes have occured, and True if any content or
1114
metadata change has occurred. If self.include_unchanged is True then
1058
1115
if changed is not None, iter_changes_result will always be a result
1059
1116
tuple. Otherwise, iter_changes_result is None unless changed is
1334
1403
def iter_changes(self):
1337
cdef void _update_current_block(self):
1406
cdef int _gather_result_for_consistency(self, result) except -1:
1407
"""Check a result we will yield to make sure we are consistent later.
1409
This gathers result's parents into a set to output later.
1411
:param result: A result tuple.
1413
if not self.partial or not result[0]:
1415
self.seen_ids.add(result[0])
1416
new_path = result[1][1]
1418
# Not the root and not a delete: queue up the parents of the path.
1419
self.search_specific_file_parents.update(
1420
osutils.parent_directories(new_path.encode('utf8')))
1421
# Add the root directory which parent_directories does not
1423
self.search_specific_file_parents.add('')
1426
cdef int _update_current_block(self) except -1:
1338
1427
if (self.block_index < len(self.state._dirblocks) and
1339
1428
osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1340
1429
self.current_block = self.state._dirblocks[self.block_index]
1406
1496
entry = self.root_entries[self.root_entries_pos]
1407
1497
self.root_entries_pos = self.root_entries_pos + 1
1408
1498
result, changed = self._process_entry(entry, self.root_dir_info)
1409
if changed is not None and changed or self.include_unchanged:
1499
if changed is not None:
1501
self._gather_result_for_consistency(result)
1502
if changed or self.include_unchanged:
1411
1504
# Have we finished the prior root, or never started one ?
1412
1505
if self.current_root is None:
1413
1506
# TODO: the pending list should be lexically sorted? the
1414
1507
# interface doesn't require it.
1416
1509
self.current_root = self.search_specific_files.pop()
1418
1511
raise StopIteration()
1419
self.current_root_unicode = self.current_root.decode('utf8')
1420
1512
self.searched_specific_files.add(self.current_root)
1421
1513
# process the entries for this containing directory: the rest will be
1422
1514
# found by their parents recursively.
1423
1515
self.root_entries = self.state._entries_for_path(self.current_root)
1424
1516
self.root_entries_len = len(self.root_entries)
1517
self.current_root_unicode = self.current_root.decode('utf8')
1425
1518
self.root_abspath = self.tree.abspath(self.current_root_unicode)
1427
1520
root_stat = os.lstat(self.root_abspath)
1768
1881
self.current_dir_info = self.dir_iterator.next()
1769
1882
self.current_dir_list = self.current_dir_info[1]
1770
except StopIteration:
1883
except StopIteration, _:
1771
1884
self.current_dir_info = None
1886
cdef object _next_consistent_entries(self):
1887
"""Grabs the next specific file parent case to consider.
1889
:return: A list of the results, each of which is as for _process_entry.
1892
while self.search_specific_file_parents:
1893
# Process the parent directories for the paths we were iterating.
1894
# Even in extremely large trees this should be modest, so currently
1895
# no attempt is made to optimise.
1896
path_utf8 = self.search_specific_file_parents.pop()
1897
if path_utf8 in self.searched_exact_paths:
1898
# We've examined this path.
1900
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1901
# We've examined this path.
1903
path_entries = self.state._entries_for_path(path_utf8)
1904
# We need either one or two entries. If the path in
1905
# self.target_index has moved (so the entry in source_index is in
1906
# 'ar') then we need to also look for the entry for this path in
1907
# self.source_index, to output the appropriate delete-or-rename.
1908
selected_entries = []
1910
for candidate_entry in path_entries:
1911
# Find entries present in target at this path:
1912
if candidate_entry[1][self.target_index][0] not in 'ar':
1914
selected_entries.append(candidate_entry)
1915
# Find entries present in source at this path:
1916
elif (self.source_index is not None and
1917
candidate_entry[1][self.source_index][0] not in 'ar'):
1919
if candidate_entry[1][self.target_index][0] == 'a':
1920
# Deleted, emit it here.
1921
selected_entries.append(candidate_entry)
1923
# renamed, emit it when we process the directory it
1925
self.search_specific_file_parents.add(
1926
candidate_entry[1][self.target_index][1])
1928
raise AssertionError(
1929
"Missing entry for specific path parent %r, %r" % (
1930
path_utf8, path_entries))
1931
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1932
for entry in selected_entries:
1933
if entry[0][2] in self.seen_ids:
1935
result, changed = self._process_entry(entry, path_info)
1937
raise AssertionError(
1938
"Got entry<->path mismatch for specific path "
1939
"%r entry %r path_info %r " % (
1940
path_utf8, entry, path_info))
1941
# Only include changes - we're outside the users requested
1944
self._gather_result_for_consistency(result)
1945
if (result[6][0] == 'directory' and
1946
result[6][1] != 'directory'):
1947
# This stopped being a directory, the old children have
1949
if entry[1][self.source_index][0] == 'r':
1950
# renamed, take the source path
1951
entry_path_utf8 = entry[1][self.source_index][1]
1953
entry_path_utf8 = path_utf8
1954
initial_key = (entry_path_utf8, '', '')
1955
block_index, _ = self.state._find_block_index_from_key(
1957
if block_index == 0:
1958
# The children of the root are in block index 1.
1959
block_index = block_index + 1
1960
current_block = None
1961
if block_index < len(self.state._dirblocks):
1962
current_block = self.state._dirblocks[block_index]
1963
if not osutils.is_inside(
1964
entry_path_utf8, current_block[0]):
1965
# No entries for this directory at all.
1966
current_block = None
1967
if current_block is not None:
1968
for entry in current_block[1]:
1969
if entry[1][self.source_index][0] in 'ar':
1970
# Not in the source tree, so doesn't have to be
1973
# Path of the entry itself.
1974
self.search_specific_file_parents.add(
1975
self.pathjoin(*entry[0][:2]))
1976
if changed or self.include_unchanged:
1977
results.append((result, changed))
1978
self.searched_exact_paths.add(path_utf8)
1981
cdef object _path_info(self, utf8_path, unicode_path):
1982
"""Generate path_info for unicode_path.
1984
:return: None if unicode_path does not exist, or a path_info tuple.
1986
abspath = self.tree.abspath(unicode_path)
1988
stat = os.lstat(abspath)
1990
if e.errno == errno.ENOENT:
1991
# the path does not exist.
1995
utf8_basename = utf8_path.rsplit('/', 1)[-1]
1996
dir_info = (utf8_path, utf8_basename,
1997
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
1999
if dir_info[2] == 'directory':
2000
if self.tree._directory_is_tree_reference(
2002
self.root_dir_info = self.root_dir_info[:2] + \
2003
('tree-reference',) + self.root_dir_info[3:]