293
288
if not PyString_CheckExact(path2):
294
289
raise TypeError("'path2' must be a plain string, not %s: %r"
295
290
% (type(path2), path2))
296
return _cmp_path_by_dirblock_intern(PyString_AsString(path1),
297
PyString_Size(path1),
298
PyString_AsString(path2),
299
PyString_Size(path2))
302
cdef int _cmp_path_by_dirblock_intern(char *path1, int path1_len,
303
char *path2, int path2_len): # cannot_raise
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):
304
299
"""Compare two paths by what directory they are in.
306
see ``_cmp_path_by_dirblock`` for details.
301
see ``_cmp_path_by_dirblock_c`` for details.
308
303
cdef char *dirname1
309
304
cdef int dirname1_len
656
648
# Build up the key that will be used.
657
649
# By using <object>(void *) Pyrex will automatically handle the
658
650
# Py_INCREF that we need.
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
651
path_name_file_id_key = (<object>p_current_dirname[0],
674
656
# Parse all of the per-tree information. current has the information in
675
657
# the same location as parent trees. The only difference is that 'info'
693
675
executable_cstr = self.get_next(&cur_size)
694
676
is_executable = (executable_cstr[0] == c'y')
695
677
info = self.get_next_str()
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(
678
PyList_Append(trees, (
710
679
minikind, # minikind
711
680
fingerprint, # fingerprint
712
681
entry_size, # size
918
885
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
919
886
executable, packed_stat)
921
# This file is not worth caching the sha1. Either it is too new, or
922
# it is newly added. Regardless, the only things we are changing
923
# are derived from the stat, and so are not worth caching. So we do
924
# *not* set the IN_MEMORY_MODIFIED flag. (But we'll save the
925
# updated values if there is *other* data worth saving.)
926
entry[1][0] = ('f', '', stat_value.st_size, executable,
888
entry[1][0] = ('f', '', stat_value.st_size,
889
executable, DirState.NULLSTAT)
929
890
elif minikind == c'd':
930
892
entry[1][0] = ('d', '', 0, False, packed_stat)
931
893
if saved_minikind != c'd':
932
894
# This changed from something into a directory. Make sure we
936
898
self._get_block_entry_index(entry[0][0], entry[0][1], 0)
937
899
self._ensure_block(block_index, entry_index,
938
900
pathjoin(entry[0][0], entry[0][1]))
940
# Any changes are derived trivially from the stat object, not worth
941
# re-writing a dirstate for just this
943
901
elif minikind == c'l':
944
if saved_minikind == c'l':
945
# If the object hasn't changed kind, it isn't worth saving the
946
# dirstate just for a symlink. The default is 'fast symlinks' which
947
# save the target in the inode entry, rather than separately. So to
948
# stat, we've already read everything off disk.
950
902
link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
951
903
if self._cutoff_time is None:
952
904
self._sha_cutoff_time()
1014
962
cdef class ProcessEntryC:
1016
cdef int doing_consistency_expansion
1017
964
cdef object old_dirname_to_file_id # dict
1018
965
cdef object new_dirname_to_file_id # dict
966
cdef readonly object uninteresting
1019
967
cdef object last_source_parent
1020
968
cdef object last_target_parent
1021
cdef int include_unchanged
969
cdef object include_unchanged
1023
970
cdef object use_filesystem_for_exec
1024
971
cdef object utf8_decode
1025
972
cdef readonly object searched_specific_files
1026
cdef readonly object searched_exact_paths
1027
973
cdef object search_specific_files
1028
# The parents up to the root of the paths we are searching.
1029
# After all normal paths are returned, these specific items are returned.
1030
cdef object search_specific_file_parents
1031
974
cdef object state
1032
975
# Current iteration variables:
1033
976
cdef object current_root
1045
988
cdef object current_block_list
1046
989
cdef object current_dir_info
1047
990
cdef object current_dir_list
1048
cdef object _pending_consistent_entries # list
1049
991
cdef int path_index
1050
992
cdef object root_dir_info
1051
993
cdef object bisect_left
1052
994
cdef object pathjoin
1053
995
cdef object fstat
1054
# A set of the ids we've output when doing partial output.
1055
cdef object seen_ids
1056
996
cdef object sha_file
1058
998
def __init__(self, include_unchanged, use_filesystem_for_exec,
1059
999
search_specific_files, state, source_index, target_index,
1060
1000
want_unversioned, tree):
1061
self.doing_consistency_expansion = 0
1062
1001
self.old_dirname_to_file_id = {}
1063
1002
self.new_dirname_to_file_id = {}
1064
# Are we doing a partial iter_changes?
1065
self.partial = set(['']).__ne__(search_specific_files)
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()
1066
1006
# Using a list so that we can access the values and change them in
1067
1007
# nested scope. Each one is [path, file_id, entry]
1068
1008
self.last_source_parent = [None, None]
1069
1009
self.last_target_parent = [None, None]
1070
if include_unchanged is None:
1071
self.include_unchanged = False
1073
self.include_unchanged = int(include_unchanged)
1010
self.include_unchanged = include_unchanged
1074
1011
self.use_filesystem_for_exec = use_filesystem_for_exec
1075
1012
self.utf8_decode = cache_utf8._utf8_decode
1076
1013
# for all search_indexs in each path at or under each element of
1077
# search_specific_files, if the detail is relocated: add the id, and
1078
# add the relocated path as one to search if its not searched already.
1079
# If the detail is not relocated, add the id.
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.
1080
1017
self.searched_specific_files = set()
1081
# When we search exact paths without expanding downwards, we record
1083
self.searched_exact_paths = set()
1084
1018
self.search_specific_files = search_specific_files
1085
# The parents up to the root of the paths we are searching.
1086
# After all normal paths are returned, these specific items are returned.
1087
self.search_specific_file_parents = set()
1088
# The ids we've sent out in the delta.
1089
self.seen_ids = set()
1090
1019
self.state = state
1091
1020
self.current_root = None
1092
1021
self.current_root_unicode = None
1108
1037
self.current_block_pos = -1
1109
1038
self.current_dir_info = None
1110
1039
self.current_dir_list = None
1111
self._pending_consistent_entries = []
1112
1040
self.path_index = 0
1113
1041
self.root_dir_info = None
1114
1042
self.bisect_left = bisect.bisect_left
1115
1043
self.pathjoin = osutils.pathjoin
1116
1044
self.fstat = os.fstat
1117
1045
self.sha_file = osutils.sha_file
1118
if target_index != 0:
1119
# A lot of code in here depends on target_index == 0
1120
raise errors.BzrError('unsupported target index')
1122
1047
cdef _process_entry(self, entry, path_info):
1123
1048
"""Compare an entry and real disk to generate delta information.
1125
1050
:param path_info: top_relpath, basename, kind, lstat, abspath for
1126
the path of entry. If None, then the path is considered absent in
1127
the target (Perhaps we should pass in a concrete entry for this ?)
1051
the path of entry. If None, then the path is considered absent.
1052
(Perhaps we should pass in a concrete entry for this ?)
1128
1053
Basename is returned as a utf8 string because we expect this
1129
1054
tuple will be ignored, and don't want to take the time to
1131
:return: (iter_changes_result, changed). If the entry has not been
1132
handled then changed is None. Otherwise it is False if no content
1133
or metadata changes have occured, and True if any content or
1134
metadata change has occurred. If self.include_unchanged is True then
1135
if changed is not None, iter_changes_result will always be a result
1136
tuple. Otherwise, iter_changes_result is None unless changed is
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.
1139
1061
cdef char target_minikind
1140
1062
cdef char source_minikind
1391
1306
(parent_id, None),
1392
1307
(self.utf8_decode(entry[0][1])[0], None),
1393
1308
(_minikind_to_kind(source_minikind), None),
1394
(source_details[3], None)), True
1309
(source_details[3], None))
1395
1310
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1396
1311
# a rename; could be a true rename, or a rename inherited from
1397
1312
# a renamed parent. TODO: handle this efficiently. Its not
1398
1313
# common case to rename dirs though, so a correct but slow
1399
1314
# implementation will do.
1400
if (not self.doing_consistency_expansion and
1401
not osutils.is_inside_any(self.searched_specific_files,
1402
target_details[1])):
1315
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1403
1316
self.search_specific_files.add(target_details[1])
1404
# We don't expand the specific files parents list here as
1405
# the path is absent in target and won't create a delta with
1407
1317
elif ((source_minikind == c'r' or source_minikind == c'a') and
1408
1318
(target_minikind == c'r' or target_minikind == c'a')):
1409
1319
# neither of the selected trees contain this path,
1423
1333
def iter_changes(self):
1426
cdef int _gather_result_for_consistency(self, result) except -1:
1427
"""Check a result we will yield to make sure we are consistent later.
1429
This gathers result's parents into a set to output later.
1431
:param result: A result tuple.
1433
if not self.partial or not result[0]:
1435
self.seen_ids.add(result[0])
1436
new_path = result[1][1]
1438
# Not the root and not a delete: queue up the parents of the path.
1439
self.search_specific_file_parents.update(
1440
osutils.parent_directories(new_path.encode('utf8')))
1441
# Add the root directory which parent_directories does not
1443
self.search_specific_file_parents.add('')
1446
cdef int _update_current_block(self) except -1:
1336
cdef void _update_current_block(self):
1447
1337
if (self.block_index < len(self.state._dirblocks) and
1448
1338
osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1449
1339
self.current_block = self.state._dirblocks[self.block_index]
1510
1399
cdef char * current_dirname_c, * current_blockname_c
1511
1400
cdef int advance_entry, advance_path
1512
1401
cdef int path_handled
1402
uninteresting = self.uninteresting
1513
1403
searched_specific_files = self.searched_specific_files
1514
1404
# Are we walking a root?
1515
1405
while self.root_entries_pos < self.root_entries_len:
1516
1406
entry = self.root_entries[self.root_entries_pos]
1517
1407
self.root_entries_pos = self.root_entries_pos + 1
1518
result, changed = self._process_entry(entry, self.root_dir_info)
1519
if changed is not None:
1521
self._gather_result_for_consistency(result)
1522
if changed or self.include_unchanged:
1408
result = self._process_entry(entry, self.root_dir_info)
1409
if result is not None and result is not self.uninteresting:
1524
1411
# Have we finished the prior root, or never started one ?
1525
1412
if self.current_root is None:
1526
1413
# TODO: the pending list should be lexically sorted? the
1527
1414
# interface doesn't require it.
1529
1416
self.current_root = self.search_specific_files.pop()
1531
1418
raise StopIteration()
1419
self.current_root_unicode = self.current_root.decode('utf8')
1532
1420
self.searched_specific_files.add(self.current_root)
1533
1421
# process the entries for this containing directory: the rest will be
1534
1422
# found by their parents recursively.
1535
1423
self.root_entries = self.state._entries_for_path(self.current_root)
1536
1424
self.root_entries_len = len(self.root_entries)
1537
self.current_root_unicode = self.current_root.decode('utf8')
1538
1425
self.root_abspath = self.tree.abspath(self.current_root_unicode)
1540
1427
root_stat = os.lstat(self.root_abspath)
1901
1773
self.current_dir_info = self.dir_iterator.next()
1902
1774
self.current_dir_list = self.current_dir_info[1]
1903
except StopIteration, _:
1775
except StopIteration:
1904
1776
self.current_dir_info = None
1906
cdef object _next_consistent_entries(self):
1907
"""Grabs the next specific file parent case to consider.
1909
:return: A list of the results, each of which is as for _process_entry.
1912
while self.search_specific_file_parents:
1913
# Process the parent directories for the paths we were iterating.
1914
# Even in extremely large trees this should be modest, so currently
1915
# no attempt is made to optimise.
1916
path_utf8 = self.search_specific_file_parents.pop()
1917
if path_utf8 in self.searched_exact_paths:
1918
# We've examined this path.
1920
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1921
# We've examined this path.
1923
path_entries = self.state._entries_for_path(path_utf8)
1924
# We need either one or two entries. If the path in
1925
# self.target_index has moved (so the entry in source_index is in
1926
# 'ar') then we need to also look for the entry for this path in
1927
# self.source_index, to output the appropriate delete-or-rename.
1928
selected_entries = []
1930
for candidate_entry in path_entries:
1931
# Find entries present in target at this path:
1932
if candidate_entry[1][self.target_index][0] not in 'ar':
1934
selected_entries.append(candidate_entry)
1935
# Find entries present in source at this path:
1936
elif (self.source_index is not None and
1937
candidate_entry[1][self.source_index][0] not in 'ar'):
1939
if candidate_entry[1][self.target_index][0] == 'a':
1940
# Deleted, emit it here.
1941
selected_entries.append(candidate_entry)
1943
# renamed, emit it when we process the directory it
1945
self.search_specific_file_parents.add(
1946
candidate_entry[1][self.target_index][1])
1948
raise AssertionError(
1949
"Missing entry for specific path parent %r, %r" % (
1950
path_utf8, path_entries))
1951
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1952
for entry in selected_entries:
1953
if entry[0][2] in self.seen_ids:
1955
result, changed = self._process_entry(entry, path_info)
1957
raise AssertionError(
1958
"Got entry<->path mismatch for specific path "
1959
"%r entry %r path_info %r " % (
1960
path_utf8, entry, path_info))
1961
# Only include changes - we're outside the users requested
1964
self._gather_result_for_consistency(result)
1965
if (result[6][0] == 'directory' and
1966
result[6][1] != 'directory'):
1967
# This stopped being a directory, the old children have
1969
if entry[1][self.source_index][0] == 'r':
1970
# renamed, take the source path
1971
entry_path_utf8 = entry[1][self.source_index][1]
1973
entry_path_utf8 = path_utf8
1974
initial_key = (entry_path_utf8, '', '')
1975
block_index, _ = self.state._find_block_index_from_key(
1977
if block_index == 0:
1978
# The children of the root are in block index 1.
1979
block_index = block_index + 1
1980
current_block = None
1981
if block_index < len(self.state._dirblocks):
1982
current_block = self.state._dirblocks[block_index]
1983
if not osutils.is_inside(
1984
entry_path_utf8, current_block[0]):
1985
# No entries for this directory at all.
1986
current_block = None
1987
if current_block is not None:
1988
for entry in current_block[1]:
1989
if entry[1][self.source_index][0] in 'ar':
1990
# Not in the source tree, so doesn't have to be
1993
# Path of the entry itself.
1994
self.search_specific_file_parents.add(
1995
self.pathjoin(*entry[0][:2]))
1996
if changed or self.include_unchanged:
1997
results.append((result, changed))
1998
self.searched_exact_paths.add(path_utf8)
2001
cdef object _path_info(self, utf8_path, unicode_path):
2002
"""Generate path_info for unicode_path.
2004
:return: None if unicode_path does not exist, or a path_info tuple.
2006
abspath = self.tree.abspath(unicode_path)
2008
stat = os.lstat(abspath)
2010
if e.errno == errno.ENOENT:
2011
# the path does not exist.
2015
utf8_basename = utf8_path.rsplit('/', 1)[-1]
2016
dir_info = (utf8_path, utf8_basename,
2017
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
2019
if dir_info[2] == 'directory':
2020
if self.tree._directory_is_tree_reference(
2022
self.root_dir_info = self.root_dir_info[:2] + \
2023
('tree-reference',) + self.root_dir_info[3:]