288
288
if not PyString_CheckExact(path2):
289
289
raise TypeError("'path2' must be a plain string, not %s: %r"
290
290
% (type(path2), path2))
291
return _cmp_path_by_dirblock(PyString_AsString(path1),
292
PyString_Size(path1),
293
PyString_AsString(path2),
294
PyString_Size(path2))
297
cdef int _cmp_path_by_dirblock(char *path1, int path1_len,
298
char *path2, int path2_len):
291
return _cmp_path_by_dirblock_intern(PyString_AsString(path1),
292
PyString_Size(path1),
293
PyString_AsString(path2),
294
PyString_Size(path2))
297
cdef int _cmp_path_by_dirblock_intern(char *path1, int path1_len,
298
char *path2, int path2_len):
299
299
"""Compare two paths by what directory they are in.
301
see ``_cmp_path_by_dirblock_c`` for details.
301
see ``_cmp_path_by_dirblock`` for details.
303
303
cdef char *dirname1
304
304
cdef int dirname1_len
962
964
cdef class ProcessEntryC:
966
cdef int doing_consistency_expansion
964
967
cdef object old_dirname_to_file_id # dict
965
968
cdef object new_dirname_to_file_id # dict
966
cdef readonly object uninteresting
967
969
cdef object last_source_parent
968
970
cdef object last_target_parent
969
cdef object include_unchanged
971
cdef int include_unchanged
970
973
cdef object use_filesystem_for_exec
971
974
cdef object utf8_decode
972
975
cdef readonly object searched_specific_files
976
cdef readonly object searched_exact_paths
973
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
974
981
cdef object state
975
982
# Current iteration variables:
976
983
cdef object current_root
988
995
cdef object current_block_list
989
996
cdef object current_dir_info
990
997
cdef object current_dir_list
998
cdef object _pending_consistent_entries # list
991
999
cdef int path_index
992
1000
cdef object root_dir_info
993
1001
cdef object bisect_left
994
1002
cdef object pathjoin
995
1003
cdef object fstat
1004
# A set of the ids we've output when doing partial output.
1005
cdef object seen_ids
996
1006
cdef object sha_file
998
1008
def __init__(self, include_unchanged, use_filesystem_for_exec,
999
1009
search_specific_files, state, source_index, target_index,
1000
1010
want_unversioned, tree):
1011
self.doing_consistency_expansion = 0
1001
1012
self.old_dirname_to_file_id = {}
1002
1013
self.new_dirname_to_file_id = {}
1003
# Just a sentry, so that _process_entry can say that this
1004
# record is handled, but isn't interesting to process (unchanged)
1005
self.uninteresting = object()
1014
# Are we doing a partial iter_changes?
1015
self.partial = set(['']).__ne__(search_specific_files)
1006
1016
# Using a list so that we can access the values and change them in
1007
1017
# nested scope. Each one is [path, file_id, entry]
1008
1018
self.last_source_parent = [None, None]
1009
1019
self.last_target_parent = [None, None]
1010
self.include_unchanged = include_unchanged
1020
if include_unchanged is None:
1021
self.include_unchanged = False
1023
self.include_unchanged = int(include_unchanged)
1011
1024
self.use_filesystem_for_exec = use_filesystem_for_exec
1012
1025
self.utf8_decode = cache_utf8._utf8_decode
1013
1026
# for all search_indexs in each path at or under each element of
1014
# search_specific_files, if the detail is relocated: add the id, and add the
1015
# relocated path as one to search if its not searched already. If the
1016
# 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.
1017
1030
self.searched_specific_files = set()
1031
# When we search exact paths without expanding downwards, we record
1033
self.searched_exact_paths = set()
1018
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()
1019
1040
self.state = state
1020
1041
self.current_root = None
1021
1042
self.current_root_unicode = None
1037
1058
self.current_block_pos = -1
1038
1059
self.current_dir_info = None
1039
1060
self.current_dir_list = None
1061
self._pending_consistent_entries = []
1040
1062
self.path_index = 0
1041
1063
self.root_dir_info = None
1042
1064
self.bisect_left = bisect.bisect_left
1043
1065
self.pathjoin = osutils.pathjoin
1044
1066
self.fstat = os.fstat
1045
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')
1047
1072
cdef _process_entry(self, entry, path_info):
1048
1073
"""Compare an entry and real disk to generate delta information.
1050
1075
:param path_info: top_relpath, basename, kind, lstat, abspath for
1051
the path of entry. If None, then the path is considered absent.
1052
(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 ?)
1053
1078
Basename is returned as a utf8 string because we expect this
1054
1079
tuple will be ignored, and don't want to take the time to
1056
:return: None if the these don't match
1057
A tuple of information about the change, or
1058
the object 'uninteresting' if these match, but are
1059
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
1061
1089
cdef char target_minikind
1062
1090
cdef char source_minikind
1306
1339
(parent_id, None),
1307
1340
(self.utf8_decode(entry[0][1])[0], None),
1308
1341
(_minikind_to_kind(source_minikind), None),
1309
(source_details[3], None))
1342
(source_details[3], None)), True
1310
1343
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1311
1344
# a rename; could be a true rename, or a rename inherited from
1312
1345
# a renamed parent. TODO: handle this efficiently. Its not
1313
1346
# common case to rename dirs though, so a correct but slow
1314
1347
# implementation will do.
1315
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1348
if (not self.doing_consistency_expansion and
1349
not osutils.is_inside_any(self.searched_specific_files,
1350
target_details[1])):
1316
1351
self.search_specific_files.add(target_details[1])
1352
# We don't expand the specific files parents list here as
1353
# the path is absent in target and won't create a delta with
1317
1355
elif ((source_minikind == c'r' or source_minikind == c'a') and
1318
1356
(target_minikind == c'r' or target_minikind == c'a')):
1319
1357
# neither of the selected trees contain this path,
1399
1456
cdef char * current_dirname_c, * current_blockname_c
1400
1457
cdef int advance_entry, advance_path
1401
1458
cdef int path_handled
1402
uninteresting = self.uninteresting
1403
1459
searched_specific_files = self.searched_specific_files
1404
1460
# Are we walking a root?
1405
1461
while self.root_entries_pos < self.root_entries_len:
1406
1462
entry = self.root_entries[self.root_entries_pos]
1407
1463
self.root_entries_pos = self.root_entries_pos + 1
1408
result = self._process_entry(entry, self.root_dir_info)
1409
if result is not None and result is not self.uninteresting:
1464
result, changed = self._process_entry(entry, self.root_dir_info)
1465
if changed is not None:
1467
self._gather_result_for_consistency(result)
1468
if changed or self.include_unchanged:
1411
1470
# Have we finished the prior root, or never started one ?
1412
1471
if self.current_root is None:
1413
1472
# TODO: the pending list should be lexically sorted? the
1698
1769
# entry referring to file not present on disk.
1699
1770
# advance the entry only, after processing.
1700
result = self._process_entry(current_entry, None)
1701
if result is not None:
1702
if result is self.uninteresting:
1771
result, changed = self._process_entry(current_entry,
1704
1773
advance_path = 0
1706
1775
# paths are the same,and the dirstate entry is not
1707
1776
# absent or renamed.
1708
result = self._process_entry(current_entry, current_path_info)
1709
if result is not None:
1777
result, changed = self._process_entry(current_entry,
1779
if changed is not None:
1710
1780
path_handled = -1
1711
if result is self.uninteresting:
1781
if not changed and not self.include_unchanged:
1713
1783
# >- loop control starts here:
1715
1785
if advance_entry and current_entry is not None:
1774
1848
self.current_dir_list = self.current_dir_info[1]
1775
1849
except StopIteration:
1776
1850
self.current_dir_info = None
1852
cdef object _next_consistent_entries(self):
1853
"""Grabs the next specific file parent case to consider.
1855
:return: A list of the results, each of which is as for _process_entry.
1858
while self.search_specific_file_parents:
1859
# Process the parent directories for the paths we were iterating.
1860
# Even in extremely large trees this should be modest, so currently
1861
# no attempt is made to optimise.
1862
path_utf8 = self.search_specific_file_parents.pop()
1863
if path_utf8 in self.searched_exact_paths:
1864
# We've examined this path.
1866
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1867
# We've examined this path.
1869
path_entries = self.state._entries_for_path(path_utf8)
1870
# We need either one or two entries. If the path in
1871
# self.target_index has moved (so the entry in source_index is in
1872
# 'ar') then we need to also look for the entry for this path in
1873
# self.source_index, to output the appropriate delete-or-rename.
1874
selected_entries = []
1876
for candidate_entry in path_entries:
1877
# Find entries present in target at this path:
1878
if candidate_entry[1][self.target_index][0] not in 'ar':
1880
selected_entries.append(candidate_entry)
1881
# Find entries present in source at this path:
1882
elif (self.source_index is not None and
1883
candidate_entry[1][self.source_index][0] not in 'ar'):
1885
if candidate_entry[1][self.target_index][0] == 'a':
1886
# Deleted, emit it here.
1887
selected_entries.append(candidate_entry)
1889
# renamed, emit it when we process the directory it
1891
self.search_specific_file_parents.add(
1892
candidate_entry[1][self.target_index][1])
1894
raise AssertionError(
1895
"Missing entry for specific path parent %r, %r" % (
1896
path_utf8, path_entries))
1897
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1898
for entry in selected_entries:
1899
if entry[0][2] in self.seen_ids:
1901
result, changed = self._process_entry(entry, path_info)
1903
raise AssertionError(
1904
"Got entry<->path mismatch for specific path "
1905
"%r entry %r path_info %r " % (
1906
path_utf8, entry, path_info))
1907
# Only include changes - we're outside the users requested
1910
self._gather_result_for_consistency(result)
1911
if (result[6][0] == 'directory' and
1912
result[6][1] != 'directory'):
1913
# This stopped being a directory, the old children have
1915
if entry[1][self.source_index][0] == 'r':
1916
# renamed, take the source path
1917
entry_path_utf8 = entry[1][self.source_index][1]
1919
entry_path_utf8 = path_utf8
1920
initial_key = (entry_path_utf8, '', '')
1921
block_index, _ = self.state._find_block_index_from_key(
1923
if block_index == 0:
1924
# The children of the root are in block index 1.
1925
block_index = block_index + 1
1926
current_block = None
1927
if block_index < len(self.state._dirblocks):
1928
current_block = self.state._dirblocks[block_index]
1929
if not osutils.is_inside(
1930
entry_path_utf8, current_block[0]):
1931
# No entries for this directory at all.
1932
current_block = None
1933
if current_block is not None:
1934
for entry in current_block[1]:
1935
if entry[1][self.source_index][0] in 'ar':
1936
# Not in the source tree, so doesn't have to be
1939
# Path of the entry itself.
1940
self.search_specific_file_parents.add(
1941
self.pathjoin(*entry[0][:2]))
1942
if changed or self.include_unchanged:
1943
results.append((result, changed))
1944
self.searched_exact_paths.add(path_utf8)
1947
cdef object _path_info(self, utf8_path, unicode_path):
1948
"""Generate path_info for unicode_path.
1950
:return: None if unicode_path does not exist, or a path_info tuple.
1952
abspath = self.tree.abspath(unicode_path)
1954
stat = os.lstat(abspath)
1956
if e.errno == errno.ENOENT:
1957
# the path does not exist.
1961
utf8_basename = utf8_path.rsplit('/', 1)[-1]
1962
dir_info = (utf8_path, utf8_basename,
1963
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
1965
if dir_info[2] == 'directory':
1966
if self.tree._directory_is_tree_reference(
1968
self.root_dir_info = self.root_dir_info[:2] + \
1969
('tree-reference',) + self.root_dir_info[3:]