288
287
if not PyString_CheckExact(path2):
289
288
raise TypeError("'path2' must be a plain string, not %s: %r"
290
289
% (type(path2), path2))
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): # cannot_raise
290
return _cmp_path_by_dirblock(PyString_AsString(path1),
291
PyString_Size(path1),
292
PyString_AsString(path2),
293
PyString_Size(path2))
296
cdef int _cmp_path_by_dirblock(char *path1, int path1_len,
297
char *path2, int path2_len):
299
298
"""Compare two paths by what directory they are in.
301
see ``_cmp_path_by_dirblock`` for details.
300
see ``_cmp_path_by_dirblock_c`` for details.
303
302
cdef char *dirname1
304
303
cdef int dirname1_len
805
802
def update_entry(self, entry, abspath, stat_value):
806
803
"""Update the entry based on what is actually on disk.
808
This function only calculates the sha if it needs to - if the entry is
809
uncachable, or clearly different to the first parent's entry, no sha
810
is calculated, and None is returned.
812
805
:param entry: This is the dirblock entry for the file in question.
813
806
:param abspath: The path on disk for this file.
814
807
:param stat_value: (optional) if we already have done a stat on the
816
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
809
:return: The sha1 hexdigest of the file (40 bytes) or link target of a
819
812
return _update_entry(self, entry, abspath, stat_value)
822
815
cdef _update_entry(self, entry, abspath, stat_value):
823
816
"""Update the entry based on what is actually on disk.
825
This function only calculates the sha if it needs to - if the entry is
826
uncachable, or clearly different to the first parent's entry, no sha
827
is calculated, and None is returned.
829
818
:param self: The dirstate object this is operating on.
830
819
:param entry: This is the dirblock entry for the file in question.
831
820
:param abspath: The path on disk for this file.
832
821
:param stat_value: The stat value done on the path.
833
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
822
:return: The sha1 hexdigest of the file (40 bytes) or link target of a
836
825
# TODO - require pyrex 0.9.8, then use a pyd file to define access to the
837
826
# _st mode of the compiled stat objects.
965
946
cdef class ProcessEntryC:
967
cdef int doing_consistency_expansion
968
948
cdef object old_dirname_to_file_id # dict
969
949
cdef object new_dirname_to_file_id # dict
950
cdef readonly object uninteresting
970
951
cdef object last_source_parent
971
952
cdef object last_target_parent
972
cdef int include_unchanged
953
cdef object include_unchanged
974
954
cdef object use_filesystem_for_exec
975
955
cdef object utf8_decode
976
956
cdef readonly object searched_specific_files
977
cdef readonly object searched_exact_paths
978
957
cdef object search_specific_files
979
# The parents up to the root of the paths we are searching.
980
# After all normal paths are returned, these specific items are returned.
981
cdef object search_specific_file_parents
982
958
cdef object state
983
959
# Current iteration variables:
984
960
cdef object current_root
996
972
cdef object current_block_list
997
973
cdef object current_dir_info
998
974
cdef object current_dir_list
999
cdef object _pending_consistent_entries # list
1000
975
cdef int path_index
1001
976
cdef object root_dir_info
1002
977
cdef object bisect_left
1003
978
cdef object pathjoin
1005
# A set of the ids we've output when doing partial output.
1006
cdef object seen_ids
1007
cdef object sha_file
1009
980
def __init__(self, include_unchanged, use_filesystem_for_exec,
1010
981
search_specific_files, state, source_index, target_index,
1011
982
want_unversioned, tree):
1012
self.doing_consistency_expansion = 0
1013
983
self.old_dirname_to_file_id = {}
1014
984
self.new_dirname_to_file_id = {}
1015
# Are we doing a partial iter_changes?
1016
self.partial = set(['']).__ne__(search_specific_files)
985
# Just a sentry, so that _process_entry can say that this
986
# record is handled, but isn't interesting to process (unchanged)
987
self.uninteresting = object()
1017
988
# Using a list so that we can access the values and change them in
1018
989
# nested scope. Each one is [path, file_id, entry]
1019
990
self.last_source_parent = [None, None]
1020
991
self.last_target_parent = [None, None]
1021
if include_unchanged is None:
1022
self.include_unchanged = False
1024
self.include_unchanged = int(include_unchanged)
992
self.include_unchanged = include_unchanged
1025
993
self.use_filesystem_for_exec = use_filesystem_for_exec
1026
994
self.utf8_decode = cache_utf8._utf8_decode
1027
995
# for all search_indexs in each path at or under each element of
1028
# search_specific_files, if the detail is relocated: add the id, and
1029
# add the relocated path as one to search if its not searched already.
1030
# If the detail is not relocated, add the id.
996
# search_specific_files, if the detail is relocated: add the id, and add the
997
# relocated path as one to search if its not searched already. If the
998
# detail is not relocated, add the id.
1031
999
self.searched_specific_files = set()
1032
# When we search exact paths without expanding downwards, we record
1034
self.searched_exact_paths = set()
1035
1000
self.search_specific_files = search_specific_files
1036
# The parents up to the root of the paths we are searching.
1037
# After all normal paths are returned, these specific items are returned.
1038
self.search_specific_file_parents = set()
1039
# The ids we've sent out in the delta.
1040
self.seen_ids = set()
1041
1001
self.state = state
1042
1002
self.current_root = None
1043
1003
self.current_root_unicode = None
1059
1019
self.current_block_pos = -1
1060
1020
self.current_dir_info = None
1061
1021
self.current_dir_list = None
1062
self._pending_consistent_entries = []
1063
1022
self.path_index = 0
1064
1023
self.root_dir_info = None
1065
1024
self.bisect_left = bisect.bisect_left
1066
1025
self.pathjoin = osutils.pathjoin
1067
self.fstat = os.fstat
1068
self.sha_file = osutils.sha_file
1069
if target_index != 0:
1070
# A lot of code in here depends on target_index == 0
1071
raise errors.BzrError('unsupported target index')
1073
1027
cdef _process_entry(self, entry, path_info):
1074
1028
"""Compare an entry and real disk to generate delta information.
1076
1030
:param path_info: top_relpath, basename, kind, lstat, abspath for
1077
the path of entry. If None, then the path is considered absent in
1078
the target (Perhaps we should pass in a concrete entry for this ?)
1031
the path of entry. If None, then the path is considered absent.
1032
(Perhaps we should pass in a concrete entry for this ?)
1079
1033
Basename is returned as a utf8 string because we expect this
1080
1034
tuple will be ignored, and don't want to take the time to
1082
:return: (iter_changes_result, changed). If the entry has not been
1083
handled then changed is None. Otherwise it is False if no content
1084
or metadata changes have occured, and True if any content or
1085
metadata change has occurred. If self.include_unchanged is True then
1086
if changed is not None, iter_changes_result will always be a result
1087
tuple. Otherwise, iter_changes_result is None unless changed is
1036
:return: None if the these don't match
1037
A tuple of information about the change, or
1038
the object 'uninteresting' if these match, but are
1039
basically identical.
1090
1041
cdef char target_minikind
1091
1042
cdef char source_minikind
1281
1214
(source_parent_id, target_parent_id),
1282
1215
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
1283
1216
(source_kind, target_kind),
1284
(source_exec, target_exec)), changed
1217
(source_exec, target_exec))
1219
return self.uninteresting
1285
1220
elif source_minikind == c'a' and _versioned_minikind(target_minikind):
1286
1221
# looks like a new file
1287
1222
path = self.pathjoin(entry[0][0], entry[0][1])
1288
1223
# parent id is the entry for the path in the target tree
1289
1224
# TODO: these are the same for an entire directory: cache em.
1290
parent_entry = self.state._get_entry(self.target_index,
1291
path_utf8=entry[0][0])
1292
if parent_entry is None:
1293
raise errors.DirstateCorrupt(self.state,
1294
"We could not find the parent entry in index %d"
1295
" for the entry: %s"
1296
% (self.target_index, entry[0]))
1297
parent_id = parent_entry[0][2]
1225
parent_id = self.state._get_entry(self.target_index,
1226
path_utf8=entry[0][0])[0][2]
1298
1227
if parent_id == entry[0][2]:
1299
1228
parent_id = None
1300
1229
if path_info is not None:
1342
1271
(parent_id, None),
1343
1272
(self.utf8_decode(entry[0][1])[0], None),
1344
1273
(_minikind_to_kind(source_minikind), None),
1345
(source_details[3], None)), True
1274
(source_details[3], None))
1346
1275
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1347
1276
# a rename; could be a true rename, or a rename inherited from
1348
1277
# a renamed parent. TODO: handle this efficiently. Its not
1349
1278
# common case to rename dirs though, so a correct but slow
1350
1279
# implementation will do.
1351
if (not self.doing_consistency_expansion and
1352
not osutils.is_inside_any(self.searched_specific_files,
1353
target_details[1])):
1280
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1354
1281
self.search_specific_files.add(target_details[1])
1355
# We don't expand the specific files parents list here as
1356
# the path is absent in target and won't create a delta with
1358
1282
elif ((source_minikind == c'r' or source_minikind == c'a') and
1359
1283
(target_minikind == c'r' or target_minikind == c'a')):
1360
1284
# neither of the selected trees contain this path,
1374
1298
def iter_changes(self):
1377
cdef int _gather_result_for_consistency(self, result) except -1:
1378
"""Check a result we will yield to make sure we are consistent later.
1380
This gathers result's parents into a set to output later.
1382
:param result: A result tuple.
1384
if not self.partial or not result[0]:
1386
self.seen_ids.add(result[0])
1387
new_path = result[1][1]
1389
# Not the root and not a delete: queue up the parents of the path.
1390
self.search_specific_file_parents.update(
1391
osutils.parent_directories(new_path.encode('utf8')))
1392
# Add the root directory which parent_directories does not
1394
self.search_specific_file_parents.add('')
1397
cdef int _update_current_block(self) except -1:
1301
cdef void _update_current_block(self):
1398
1302
if (self.block_index < len(self.state._dirblocks) and
1399
1303
osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1400
1304
self.current_block = self.state._dirblocks[self.block_index]
1461
1364
cdef char * current_dirname_c, * current_blockname_c
1462
1365
cdef int advance_entry, advance_path
1463
1366
cdef int path_handled
1367
uninteresting = self.uninteresting
1464
1368
searched_specific_files = self.searched_specific_files
1465
1369
# Are we walking a root?
1466
1370
while self.root_entries_pos < self.root_entries_len:
1467
1371
entry = self.root_entries[self.root_entries_pos]
1468
1372
self.root_entries_pos = self.root_entries_pos + 1
1469
result, changed = self._process_entry(entry, self.root_dir_info)
1470
if changed is not None:
1472
self._gather_result_for_consistency(result)
1473
if changed or self.include_unchanged:
1373
result = self._process_entry(entry, self.root_dir_info)
1374
if result is not None and result is not self.uninteresting:
1475
1376
# Have we finished the prior root, or never started one ?
1476
1377
if self.current_root is None:
1477
1378
# TODO: the pending list should be lexically sorted? the
1774
1663
# entry referring to file not present on disk.
1775
1664
# advance the entry only, after processing.
1776
result, changed = self._process_entry(current_entry,
1665
result = self._process_entry(current_entry, None)
1666
if result is not None:
1667
if result is self.uninteresting:
1778
1669
advance_path = 0
1780
1671
# paths are the same,and the dirstate entry is not
1781
1672
# absent or renamed.
1782
result, changed = self._process_entry(current_entry,
1784
if changed is not None:
1673
result = self._process_entry(current_entry, current_path_info)
1674
if result is not None:
1785
1675
path_handled = -1
1786
if not changed and not self.include_unchanged:
1676
if result is self.uninteresting:
1788
1678
# >- loop control starts here:
1790
1680
if advance_entry and current_entry is not None:
1853
1739
self.current_dir_list = self.current_dir_info[1]
1854
1740
except StopIteration:
1855
1741
self.current_dir_info = None
1857
cdef object _next_consistent_entries(self):
1858
"""Grabs the next specific file parent case to consider.
1860
:return: A list of the results, each of which is as for _process_entry.
1863
while self.search_specific_file_parents:
1864
# Process the parent directories for the paths we were iterating.
1865
# Even in extremely large trees this should be modest, so currently
1866
# no attempt is made to optimise.
1867
path_utf8 = self.search_specific_file_parents.pop()
1868
if path_utf8 in self.searched_exact_paths:
1869
# We've examined this path.
1871
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
1872
# We've examined this path.
1874
path_entries = self.state._entries_for_path(path_utf8)
1875
# We need either one or two entries. If the path in
1876
# self.target_index has moved (so the entry in source_index is in
1877
# 'ar') then we need to also look for the entry for this path in
1878
# self.source_index, to output the appropriate delete-or-rename.
1879
selected_entries = []
1881
for candidate_entry in path_entries:
1882
# Find entries present in target at this path:
1883
if candidate_entry[1][self.target_index][0] not in 'ar':
1885
selected_entries.append(candidate_entry)
1886
# Find entries present in source at this path:
1887
elif (self.source_index is not None and
1888
candidate_entry[1][self.source_index][0] not in 'ar'):
1890
if candidate_entry[1][self.target_index][0] == 'a':
1891
# Deleted, emit it here.
1892
selected_entries.append(candidate_entry)
1894
# renamed, emit it when we process the directory it
1896
self.search_specific_file_parents.add(
1897
candidate_entry[1][self.target_index][1])
1899
raise AssertionError(
1900
"Missing entry for specific path parent %r, %r" % (
1901
path_utf8, path_entries))
1902
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
1903
for entry in selected_entries:
1904
if entry[0][2] in self.seen_ids:
1906
result, changed = self._process_entry(entry, path_info)
1908
raise AssertionError(
1909
"Got entry<->path mismatch for specific path "
1910
"%r entry %r path_info %r " % (
1911
path_utf8, entry, path_info))
1912
# Only include changes - we're outside the users requested
1915
self._gather_result_for_consistency(result)
1916
if (result[6][0] == 'directory' and
1917
result[6][1] != 'directory'):
1918
# This stopped being a directory, the old children have
1920
if entry[1][self.source_index][0] == 'r':
1921
# renamed, take the source path
1922
entry_path_utf8 = entry[1][self.source_index][1]
1924
entry_path_utf8 = path_utf8
1925
initial_key = (entry_path_utf8, '', '')
1926
block_index, _ = self.state._find_block_index_from_key(
1928
if block_index == 0:
1929
# The children of the root are in block index 1.
1930
block_index = block_index + 1
1931
current_block = None
1932
if block_index < len(self.state._dirblocks):
1933
current_block = self.state._dirblocks[block_index]
1934
if not osutils.is_inside(
1935
entry_path_utf8, current_block[0]):
1936
# No entries for this directory at all.
1937
current_block = None
1938
if current_block is not None:
1939
for entry in current_block[1]:
1940
if entry[1][self.source_index][0] in 'ar':
1941
# Not in the source tree, so doesn't have to be
1944
# Path of the entry itself.
1945
self.search_specific_file_parents.add(
1946
self.pathjoin(*entry[0][:2]))
1947
if changed or self.include_unchanged:
1948
results.append((result, changed))
1949
self.searched_exact_paths.add(path_utf8)
1952
cdef object _path_info(self, utf8_path, unicode_path):
1953
"""Generate path_info for unicode_path.
1955
:return: None if unicode_path does not exist, or a path_info tuple.
1957
abspath = self.tree.abspath(unicode_path)
1959
stat = os.lstat(abspath)
1961
if e.errno == errno.ENOENT:
1962
# the path does not exist.
1966
utf8_basename = utf8_path.rsplit('/', 1)[-1]
1967
dir_info = (utf8_path, utf8_basename,
1968
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
1970
if dir_info[2] == 'directory':
1971
if self.tree._directory_is_tree_reference(
1973
self.root_dir_info = self.root_dir_info[:2] + \
1974
('tree-reference',) + self.root_dir_info[3:]