760
689
text = state._state_file.read()
761
690
# TODO: check the crc checksums. crc_measured = zlib.crc32(text)
763
reader = Reader(text, state)
692
reader = Reader(text)
765
reader._parse_dirblocks()
694
reader._parse_dirblocks(state)
766
695
state._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
769
cdef int minikind_from_mode(int mode):
770
# in order of frequency:
780
_encode = binascii.b2a_base64
783
from struct import pack
784
cdef _pack_stat(stat_value):
785
"""return a string representing the stat value's key fields.
787
:param stat_value: A stat oject with st_size, st_mtime, st_ctime, st_dev,
788
st_ino and st_mode fields.
790
cdef char result[6*4] # 6 long ints
792
aliased = <int *>result
793
aliased[0] = htonl(stat_value.st_size)
794
aliased[1] = htonl(int(stat_value.st_mtime))
795
aliased[2] = htonl(int(stat_value.st_ctime))
796
aliased[3] = htonl(stat_value.st_dev)
797
aliased[4] = htonl(stat_value.st_ino & 0xFFFFFFFF)
798
aliased[5] = htonl(stat_value.st_mode)
799
packed = PyString_FromStringAndSize(result, 6*4)
800
return _encode(packed)[:-1]
803
def update_entry(self, entry, abspath, stat_value):
804
"""Update the entry based on what is actually on disk.
806
This function only calculates the sha if it needs to - if the entry is
807
uncachable, or clearly different to the first parent's entry, no sha
808
is calculated, and None is returned.
810
:param entry: This is the dirblock entry for the file in question.
811
:param abspath: The path on disk for this file.
812
:param stat_value: (optional) if we already have done a stat on the
814
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
817
return _update_entry(self, entry, abspath, stat_value)
820
cdef _update_entry(self, entry, abspath, stat_value):
821
"""Update the entry based on what is actually on disk.
823
This function only calculates the sha if it needs to - if the entry is
824
uncachable, or clearly different to the first parent's entry, no sha
825
is calculated, and None is returned.
827
:param self: The dirstate object this is operating on.
828
:param entry: This is the dirblock entry for the file in question.
829
:param abspath: The path on disk for this file.
830
:param stat_value: The stat value done on the path.
831
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
834
# TODO - require pyrex 0.9.8, then use a pyd file to define access to the
835
# _st mode of the compiled stat objects.
836
cdef int minikind, saved_minikind
838
minikind = minikind_from_mode(stat_value.st_mode)
841
packed_stat = _pack_stat(stat_value)
842
details = PyList_GetItem_void_void(PyTuple_GetItem_void_void(<void *>entry, 1), 0)
843
saved_minikind = PyString_AsString_obj(<PyObject *>PyTuple_GetItem_void_void(details, 0))[0]
844
if minikind == c'd' and saved_minikind == c't':
846
saved_link_or_sha1 = PyTuple_GetItem_void_object(details, 1)
847
saved_file_size = PyTuple_GetItem_void_object(details, 2)
848
saved_executable = PyTuple_GetItem_void_object(details, 3)
849
saved_packed_stat = PyTuple_GetItem_void_object(details, 4)
850
# Deal with pyrex decrefing the objects
851
Py_INCREF(saved_link_or_sha1)
852
Py_INCREF(saved_file_size)
853
Py_INCREF(saved_executable)
854
Py_INCREF(saved_packed_stat)
855
#(saved_minikind, saved_link_or_sha1, saved_file_size,
856
# saved_executable, saved_packed_stat) = entry[1][0]
858
if (minikind == saved_minikind
859
and packed_stat == saved_packed_stat):
860
# The stat hasn't changed since we saved, so we can re-use the
865
# size should also be in packed_stat
866
if saved_file_size == stat_value.st_size:
867
return saved_link_or_sha1
869
# If we have gotten this far, that means that we need to actually
870
# process this entry.
873
executable = self._is_executable(stat_value.st_mode,
875
if self._cutoff_time is None:
876
self._sha_cutoff_time()
877
if (stat_value.st_mtime < self._cutoff_time
878
and stat_value.st_ctime < self._cutoff_time
879
and len(entry[1]) > 1
880
and entry[1][1][0] != 'a'):
881
# Could check for size changes for further optimised
882
# avoidance of sha1's. However the most prominent case of
883
# over-shaing is during initial add, which this catches.
884
link_or_sha1 = self._sha1_file(abspath)
885
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
886
executable, packed_stat)
888
entry[1][0] = ('f', '', stat_value.st_size,
889
executable, DirState.NULLSTAT)
890
elif minikind == c'd':
892
entry[1][0] = ('d', '', 0, False, packed_stat)
893
if saved_minikind != c'd':
894
# This changed from something into a directory. Make sure we
895
# have a directory block for it. This doesn't happen very
896
# often, so this doesn't have to be super fast.
897
block_index, entry_index, dir_present, file_present = \
898
self._get_block_entry_index(entry[0][0], entry[0][1], 0)
899
self._ensure_block(block_index, entry_index,
900
pathjoin(entry[0][0], entry[0][1]))
901
elif minikind == c'l':
902
link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
903
if self._cutoff_time is None:
904
self._sha_cutoff_time()
905
if (stat_value.st_mtime < self._cutoff_time
906
and stat_value.st_ctime < self._cutoff_time):
907
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
910
entry[1][0] = ('l', '', stat_value.st_size,
911
False, DirState.NULLSTAT)
912
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
916
cdef char _minikind_from_string(object string):
917
"""Convert a python string to a char."""
918
return PyString_AsString(string)[0]
921
cdef object _kind_absent
922
cdef object _kind_file
923
cdef object _kind_directory
924
cdef object _kind_symlink
925
cdef object _kind_relocated
926
cdef object _kind_tree_reference
927
_kind_absent = "absent"
929
_kind_directory = "directory"
930
_kind_symlink = "symlink"
931
_kind_relocated = "relocated"
932
_kind_tree_reference = "tree-reference"
935
cdef object _minikind_to_kind(char minikind):
936
"""Create a string kind for minikind."""
937
cdef char _minikind[1]
940
elif minikind == c'd':
941
return _kind_directory
942
elif minikind == c'a':
944
elif minikind == c'r':
945
return _kind_relocated
946
elif minikind == c'l':
948
elif minikind == c't':
949
return _kind_tree_reference
950
_minikind[0] = minikind
951
raise KeyError(PyString_FromStringAndSize(_minikind, 1))
954
cdef int _versioned_minikind(char minikind):
955
"""Return non-zero if minikind is in fltd"""
956
return (minikind == c'f' or
962
cdef class ProcessEntryC:
964
cdef object old_dirname_to_file_id # dict
965
cdef object new_dirname_to_file_id # dict
966
cdef readonly object uninteresting
967
cdef object last_source_parent
968
cdef object last_target_parent
969
cdef object include_unchanged
970
cdef object use_filesystem_for_exec
971
cdef object utf8_decode
972
cdef readonly object searched_specific_files
973
cdef object search_specific_files
975
# Current iteration variables:
976
cdef object current_root
977
cdef object current_root_unicode
978
cdef object root_entries
979
cdef int root_entries_pos, root_entries_len
980
cdef object root_abspath
981
cdef int source_index, target_index
982
cdef int want_unversioned
984
cdef object dir_iterator
986
cdef object current_block
987
cdef int current_block_pos
988
cdef object current_block_list
989
cdef object current_dir_info
990
cdef object current_dir_list
992
cdef object root_dir_info
993
cdef object bisect_left
998
def __init__(self, include_unchanged, use_filesystem_for_exec,
999
search_specific_files, state, source_index, target_index,
1000
want_unversioned, tree):
1001
self.old_dirname_to_file_id = {}
1002
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()
1006
# Using a list so that we can access the values and change them in
1007
# nested scope. Each one is [path, file_id, entry]
1008
self.last_source_parent = [None, None]
1009
self.last_target_parent = [None, None]
1010
self.include_unchanged = include_unchanged
1011
self.use_filesystem_for_exec = use_filesystem_for_exec
1012
self.utf8_decode = cache_utf8._utf8_decode
1013
# 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.
1017
self.searched_specific_files = set()
1018
self.search_specific_files = search_specific_files
1020
self.current_root = None
1021
self.current_root_unicode = None
1022
self.root_entries = None
1023
self.root_entries_pos = 0
1024
self.root_entries_len = 0
1025
self.root_abspath = None
1026
if source_index is None:
1027
self.source_index = -1
1029
self.source_index = source_index
1030
self.target_index = target_index
1031
self.want_unversioned = want_unversioned
1033
self.dir_iterator = None
1034
self.block_index = -1
1035
self.current_block = None
1036
self.current_block_list = None
1037
self.current_block_pos = -1
1038
self.current_dir_info = None
1039
self.current_dir_list = None
1041
self.root_dir_info = None
1042
self.bisect_left = bisect.bisect_left
1043
self.pathjoin = osutils.pathjoin
1044
self.fstat = os.fstat
1045
self.sha_file = osutils.sha_file
1047
cdef _process_entry(self, entry, path_info):
1048
"""Compare an entry and real disk to generate delta information.
1050
: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 ?)
1053
Basename is returned as a utf8 string because we expect this
1054
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.
1061
cdef char target_minikind
1062
cdef char source_minikind
1064
cdef int content_change
1065
cdef object details_list
1067
details_list = entry[1]
1068
if -1 == self.source_index:
1069
source_details = DirState.NULL_PARENT_DETAILS
1071
source_details = details_list[self.source_index]
1072
target_details = details_list[self.target_index]
1073
target_minikind = _minikind_from_string(target_details[0])
1074
if path_info is not None and _versioned_minikind(target_minikind):
1075
if self.target_index != 0:
1076
raise AssertionError("Unsupported target index %d" %
1078
link_or_sha1 = _update_entry(self.state, entry, path_info[4], path_info[3])
1079
# The entry may have been modified by update_entry
1080
target_details = details_list[self.target_index]
1081
target_minikind = _minikind_from_string(target_details[0])
1084
# the rest of this function is 0.3 seconds on 50K paths, or
1085
# 0.000006 seconds per call.
1086
source_minikind = _minikind_from_string(source_details[0])
1087
if ((_versioned_minikind(source_minikind) or source_minikind == c'r')
1088
and _versioned_minikind(target_minikind)):
1089
# claimed content in both: diff
1090
# r | fdlt | | add source to search, add id path move and perform
1091
# | | | diff check on source-target
1092
# r | fdlt | a | dangling file that was present in the basis.
1094
if source_minikind != c'r':
1095
old_dirname = entry[0][0]
1096
old_basename = entry[0][1]
1097
old_path = path = None
1099
# add the source to the search path to find any children it
1100
# has. TODO ? : only add if it is a container ?
1101
if not osutils.is_inside_any(self.searched_specific_files,
1103
self.search_specific_files.add(source_details[1])
1104
# generate the old path; this is needed for stating later
1106
old_path = source_details[1]
1107
old_dirname, old_basename = os.path.split(old_path)
1108
path = self.pathjoin(entry[0][0], entry[0][1])
1109
old_entry = self.state._get_entry(self.source_index,
1111
# update the source details variable to be the real
1113
if old_entry == (None, None):
1114
raise errors.CorruptDirstate(self.state._filename,
1115
"entry '%s/%s' is considered renamed from %r"
1116
" but source does not exist\n"
1117
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
1118
source_details = old_entry[1][self.source_index]
1119
source_minikind = _minikind_from_string(source_details[0])
1120
if path_info is None:
1121
# the file is missing on disk, show as removed.
1126
# source and target are both versioned and disk file is present.
1127
target_kind = path_info[2]
1128
if target_kind == 'directory':
1130
old_path = path = self.pathjoin(old_dirname, old_basename)
1131
file_id = entry[0][2]
1132
self.new_dirname_to_file_id[path] = file_id
1133
if source_minikind != c'd':
1136
# directories have no fingerprint
1139
elif target_kind == 'file':
1140
if source_minikind != c'f':
1143
# If the size is the same, check the sha:
1144
if target_details[2] == source_details[2]:
1145
if link_or_sha1 is None:
1147
statvalue, link_or_sha1 = \
1148
self.state._sha1_provider.stat_and_sha1(
1150
self.state._observed_sha1(entry, link_or_sha1,
1152
content_change = (link_or_sha1 != source_details[1])
1154
# Size changed, so must be different
1156
# Target details is updated at update_entry time
1157
if self.use_filesystem_for_exec:
1158
# We don't need S_ISREG here, because we are sure
1159
# we are dealing with a file.
1160
target_exec = bool(S_IXUSR & path_info[3].st_mode)
1162
target_exec = target_details[3]
1163
elif target_kind == 'symlink':
1164
if source_minikind != c'l':
1167
content_change = (link_or_sha1 != source_details[1])
1169
elif target_kind == 'tree-reference':
1170
if source_minikind != c't':
1176
raise Exception, "unknown kind %s" % path_info[2]
1177
if source_minikind == c'd':
1179
old_path = path = self.pathjoin(old_dirname, old_basename)
1181
file_id = entry[0][2]
1182
self.old_dirname_to_file_id[old_path] = file_id
1183
# parent id is the entry for the path in the target tree
1184
if old_dirname == self.last_source_parent[0]:
1185
source_parent_id = self.last_source_parent[1]
1188
source_parent_id = self.old_dirname_to_file_id[old_dirname]
1190
source_parent_entry = self.state._get_entry(self.source_index,
1191
path_utf8=old_dirname)
1192
source_parent_id = source_parent_entry[0][2]
1193
if source_parent_id == entry[0][2]:
1194
# This is the root, so the parent is None
1195
source_parent_id = None
1197
self.last_source_parent[0] = old_dirname
1198
self.last_source_parent[1] = source_parent_id
1199
new_dirname = entry[0][0]
1200
if new_dirname == self.last_target_parent[0]:
1201
target_parent_id = self.last_target_parent[1]
1204
target_parent_id = self.new_dirname_to_file_id[new_dirname]
1206
# TODO: We don't always need to do the lookup, because the
1207
# parent entry will be the same as the source entry.
1208
target_parent_entry = self.state._get_entry(self.target_index,
1209
path_utf8=new_dirname)
1210
if target_parent_entry == (None, None):
1211
raise AssertionError(
1212
"Could not find target parent in wt: %s\nparent of: %s"
1213
% (new_dirname, entry))
1214
target_parent_id = target_parent_entry[0][2]
1215
if target_parent_id == entry[0][2]:
1216
# This is the root, so the parent is None
1217
target_parent_id = None
1219
self.last_target_parent[0] = new_dirname
1220
self.last_target_parent[1] = target_parent_id
1222
source_exec = source_details[3]
1223
if (self.include_unchanged
1225
or source_parent_id != target_parent_id
1226
or old_basename != entry[0][1]
1227
or source_exec != target_exec
1229
if old_path is None:
1230
path = self.pathjoin(old_dirname, old_basename)
1232
old_path_u = self.utf8_decode(old_path)[0]
1235
old_path_u = self.utf8_decode(old_path)[0]
1236
if old_path == path:
1239
path_u = self.utf8_decode(path)[0]
1240
source_kind = _minikind_to_kind(source_minikind)
1241
return (entry[0][2],
1242
(old_path_u, path_u),
1245
(source_parent_id, target_parent_id),
1246
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
1247
(source_kind, target_kind),
1248
(source_exec, target_exec))
1250
return self.uninteresting
1251
elif source_minikind == c'a' and _versioned_minikind(target_minikind):
1252
# looks like a new file
1253
path = self.pathjoin(entry[0][0], entry[0][1])
1254
# parent id is the entry for the path in the target tree
1255
# TODO: these are the same for an entire directory: cache em.
1256
parent_entry = self.state._get_entry(self.target_index,
1257
path_utf8=entry[0][0])
1258
if parent_entry is None:
1259
raise errors.DirstateCorrupt(self.state,
1260
"We could not find the parent entry in index %d"
1261
" for the entry: %s"
1262
% (self.target_index, entry[0]))
1263
parent_id = parent_entry[0][2]
1264
if parent_id == entry[0][2]:
1266
if path_info is not None:
1268
if self.use_filesystem_for_exec:
1269
# We need S_ISREG here, because we aren't sure if this
1272
S_ISREG(path_info[3].st_mode)
1273
and S_IXUSR & path_info[3].st_mode)
1275
target_exec = target_details[3]
1276
return (entry[0][2],
1277
(None, self.utf8_decode(path)[0]),
1281
(None, self.utf8_decode(entry[0][1])[0]),
1282
(None, path_info[2]),
1283
(None, target_exec))
1285
# Its a missing file, report it as such.
1286
return (entry[0][2],
1287
(None, self.utf8_decode(path)[0]),
1291
(None, self.utf8_decode(entry[0][1])[0]),
1294
elif _versioned_minikind(source_minikind) and target_minikind == c'a':
1295
# unversioned, possibly, or possibly not deleted: we dont care.
1296
# if its still on disk, *and* theres no other entry at this
1297
# path [we dont know this in this routine at the moment -
1298
# perhaps we should change this - then it would be an unknown.
1299
old_path = self.pathjoin(entry[0][0], entry[0][1])
1300
# parent id is the entry for the path in the target tree
1301
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
1302
if parent_id == entry[0][2]:
1304
return (entry[0][2],
1305
(self.utf8_decode(old_path)[0], None),
1309
(self.utf8_decode(entry[0][1])[0], None),
1310
(_minikind_to_kind(source_minikind), None),
1311
(source_details[3], None))
1312
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1313
# a rename; could be a true rename, or a rename inherited from
1314
# a renamed parent. TODO: handle this efficiently. Its not
1315
# common case to rename dirs though, so a correct but slow
1316
# implementation will do.
1317
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1318
self.search_specific_files.add(target_details[1])
1319
elif ((source_minikind == c'r' or source_minikind == c'a') and
1320
(target_minikind == c'r' or target_minikind == c'a')):
1321
# neither of the selected trees contain this path,
1322
# so skip over it. This is not currently directly tested, but
1323
# is indirectly via test_too_much.TestCommands.test_conflicts.
1326
raise AssertionError("don't know how to compare "
1327
"source_minikind=%r, target_minikind=%r"
1328
% (source_minikind, target_minikind))
1329
## import pdb;pdb.set_trace()
1335
def iter_changes(self):
1338
cdef void _update_current_block(self):
1339
if (self.block_index < len(self.state._dirblocks) and
1340
osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1341
self.current_block = self.state._dirblocks[self.block_index]
1342
self.current_block_list = self.current_block[1]
1343
self.current_block_pos = 0
1345
self.current_block = None
1346
self.current_block_list = None
1349
# Simple thunk to allow tail recursion without pyrex confusion
1350
return self._iter_next()
1352
cdef _iter_next(self):
1353
"""Iterate over the changes."""
1354
# This function single steps through an iterator. As such while loops
1355
# are often exited by 'return' - the code is structured so that the
1356
# next call into the function will return to the same while loop. Note
1357
# that all flow control needed to re-reach that step is reexecuted,
1358
# which can be a performance problem. It has not yet been tuned to
1359
# minimise this; a state machine is probably the simplest restructuring
1360
# to both minimise this overhead and make the code considerably more
1364
# compare source_index and target_index at or under each element of search_specific_files.
1365
# follow the following comparison table. Note that we only want to do diff operations when
1366
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1370
# Source | Target | disk | action
1371
# r | fdlt | | add source to search, add id path move and perform
1372
# | | | diff check on source-target
1373
# r | fdlt | a | dangling file that was present in the basis.
1375
# r | a | | add source to search
1377
# r | r | | this path is present in a non-examined tree, skip.
1378
# r | r | a | this path is present in a non-examined tree, skip.
1379
# a | fdlt | | add new id
1380
# a | fdlt | a | dangling locally added file, skip
1381
# a | a | | not present in either tree, skip
1382
# a | a | a | not present in any tree, skip
1383
# a | r | | not present in either tree at this path, skip as it
1384
# | | | may not be selected by the users list of paths.
1385
# a | r | a | not present in either tree at this path, skip as it
1386
# | | | may not be selected by the users list of paths.
1387
# fdlt | fdlt | | content in both: diff them
1388
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1389
# fdlt | a | | unversioned: output deleted id for now
1390
# fdlt | a | a | unversioned and deleted: output deleted id
1391
# fdlt | r | | relocated in this tree, so add target to search.
1392
# | | | Dont diff, we will see an r,fd; pair when we reach
1393
# | | | this id at the other path.
1394
# fdlt | r | a | relocated in this tree, so add target to search.
1395
# | | | Dont diff, we will see an r,fd; pair when we reach
1396
# | | | this id at the other path.
1398
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1399
# keeping a cache of directories that we have seen.
1400
cdef object current_dirname, current_blockname
1401
cdef char * current_dirname_c, * current_blockname_c
1402
cdef int advance_entry, advance_path
1403
cdef int path_handled
1404
uninteresting = self.uninteresting
1405
searched_specific_files = self.searched_specific_files
1406
# Are we walking a root?
1407
while self.root_entries_pos < self.root_entries_len:
1408
entry = self.root_entries[self.root_entries_pos]
1409
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:
1413
# Have we finished the prior root, or never started one ?
1414
if self.current_root is None:
1415
# TODO: the pending list should be lexically sorted? the
1416
# interface doesn't require it.
1418
self.current_root = self.search_specific_files.pop()
1420
raise StopIteration()
1421
self.current_root_unicode = self.current_root.decode('utf8')
1422
self.searched_specific_files.add(self.current_root)
1423
# process the entries for this containing directory: the rest will be
1424
# found by their parents recursively.
1425
self.root_entries = self.state._entries_for_path(self.current_root)
1426
self.root_entries_len = len(self.root_entries)
1427
self.root_abspath = self.tree.abspath(self.current_root_unicode)
1429
root_stat = os.lstat(self.root_abspath)
1431
if e.errno == errno.ENOENT:
1432
# the path does not exist: let _process_entry know that.
1433
self.root_dir_info = None
1435
# some other random error: hand it up.
1438
self.root_dir_info = ('', self.current_root,
1439
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
1441
if self.root_dir_info[2] == 'directory':
1442
if self.tree._directory_is_tree_reference(
1443
self.current_root_unicode):
1444
self.root_dir_info = self.root_dir_info[:2] + \
1445
('tree-reference',) + self.root_dir_info[3:]
1446
if not self.root_entries and not self.root_dir_info:
1447
# this specified path is not present at all, skip it.
1448
# (tail recursion, can do a loop once the full structure is
1450
return self._iter_next()
1452
self.root_entries_pos = 0
1453
# XXX Clarity: This loop is duplicated a out the self.current_root
1454
# is None guard above: if we return from it, it completes there
1455
# (and the following if block cannot trigger because
1456
# path_handled must be true, so the if block is not # duplicated.
1457
while self.root_entries_pos < self.root_entries_len:
1458
entry = self.root_entries[self.root_entries_pos]
1459
self.root_entries_pos = self.root_entries_pos + 1
1460
result = self._process_entry(entry, self.root_dir_info)
1461
if result is not None:
1463
if result is not self.uninteresting:
1465
# handle unversioned specified paths:
1466
if self.want_unversioned and not path_handled and self.root_dir_info:
1467
new_executable = bool(
1468
stat.S_ISREG(self.root_dir_info[3].st_mode)
1469
and stat.S_IEXEC & self.root_dir_info[3].st_mode)
1471
(None, self.current_root_unicode),
1475
(None, splitpath(self.current_root_unicode)[-1]),
1476
(None, self.root_dir_info[2]),
1477
(None, new_executable)
1479
# If we reach here, the outer flow continues, which enters into the
1480
# per-root setup logic.
1481
if self.current_dir_info is None and self.current_block is None:
1482
# setup iteration of this root:
1483
self.current_dir_list = None
1484
if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
1485
self.current_dir_info = None
1487
self.dir_iterator = osutils._walkdirs_utf8(self.root_abspath,
1488
prefix=self.current_root)
1491
self.current_dir_info = self.dir_iterator.next()
1492
self.current_dir_list = self.current_dir_info[1]
1494
# there may be directories in the inventory even though
1495
# this path is not a file on disk: so mark it as end of
1497
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
1498
self.current_dir_info = None
1499
elif sys.platform == 'win32':
1500
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
1501
# python 2.5 has e.errno == EINVAL,
1502
# and e.winerror == ERROR_DIRECTORY
1504
e_winerror = e.winerror
1505
except AttributeError:
1507
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
1508
if (e.errno in win_errors or e_winerror in win_errors):
1509
self.current_dir_info = None
1511
# Will this really raise the right exception ?
1516
if self.current_dir_info[0][0] == '':
1517
# remove .bzr from iteration
1518
bzr_index = self.bisect_left(self.current_dir_list, ('.bzr',))
1519
if self.current_dir_list[bzr_index][0] != '.bzr':
1520
raise AssertionError()
1521
del self.current_dir_list[bzr_index]
1522
initial_key = (self.current_root, '', '')
1523
self.block_index, _ = self.state._find_block_index_from_key(initial_key)
1524
if self.block_index == 0:
1525
# we have processed the total root already, but because the
1526
# initial key matched it we should skip it here.
1527
self.block_index = self.block_index + 1
1528
self._update_current_block()
1529
# walk until both the directory listing and the versioned metadata
1531
while (self.current_dir_info is not None
1532
or self.current_block is not None):
1533
# Uncommon case - a missing directory or an unversioned directory:
1534
if (self.current_dir_info and self.current_block
1535
and self.current_dir_info[0][0] != self.current_block[0]):
1536
# Work around pyrex broken heuristic - current_dirname has
1537
# the same scope as current_dirname_c
1538
current_dirname = self.current_dir_info[0][0]
1539
current_dirname_c = PyString_AS_STRING_void(
1540
<void *>current_dirname)
1541
current_blockname = self.current_block[0]
1542
current_blockname_c = PyString_AS_STRING_void(
1543
<void *>current_blockname)
1544
# In the python generator we evaluate this if block once per
1545
# dir+block; because we reenter in the pyrex version its being
1546
# evaluated once per path: we could cache the result before
1547
# doing the while loop and probably save time.
1548
if _cmp_by_dirs(current_dirname_c,
1549
PyString_Size(current_dirname),
1550
current_blockname_c,
1551
PyString_Size(current_blockname)) < 0:
1552
# filesystem data refers to paths not covered by the
1553
# dirblock. this has two possibilities:
1554
# A) it is versioned but empty, so there is no block for it
1555
# B) it is not versioned.
1557
# if (A) then we need to recurse into it to check for
1558
# new unknown files or directories.
1559
# if (B) then we should ignore it, because we don't
1560
# recurse into unknown directories.
1561
# We are doing a loop
1562
while self.path_index < len(self.current_dir_list):
1563
current_path_info = self.current_dir_list[self.path_index]
1564
# dont descend into this unversioned path if it is
1566
if current_path_info[2] in ('directory',
1568
del self.current_dir_list[self.path_index]
1569
self.path_index = self.path_index - 1
1570
self.path_index = self.path_index + 1
1571
if self.want_unversioned:
1572
if current_path_info[2] == 'directory':
1573
if self.tree._directory_is_tree_reference(
1574
self.utf8_decode(current_path_info[0])[0]):
1575
current_path_info = current_path_info[:2] + \
1576
('tree-reference',) + current_path_info[3:]
1577
new_executable = bool(
1578
stat.S_ISREG(current_path_info[3].st_mode)
1579
and stat.S_IEXEC & current_path_info[3].st_mode)
1581
(None, self.utf8_decode(current_path_info[0])[0]),
1585
(None, self.utf8_decode(current_path_info[1])[0]),
1586
(None, current_path_info[2]),
1587
(None, new_executable))
1588
# This dir info has been handled, go to the next
1590
self.current_dir_list = None
1592
self.current_dir_info = self.dir_iterator.next()
1593
self.current_dir_list = self.current_dir_info[1]
1594
except StopIteration:
1595
self.current_dir_info = None
1597
# We have a dirblock entry for this location, but there
1598
# is no filesystem path for this. This is most likely
1599
# because a directory was removed from the disk.
1600
# We don't have to report the missing directory,
1601
# because that should have already been handled, but we
1602
# need to handle all of the files that are contained
1604
while self.current_block_pos < len(self.current_block_list):
1605
current_entry = self.current_block_list[self.current_block_pos]
1606
self.current_block_pos = self.current_block_pos + 1
1607
# entry referring to file not present on disk.
1608
# advance the entry only, after processing.
1609
result = self._process_entry(current_entry, None)
1610
if result is not None:
1611
if result is not self.uninteresting:
1613
self.block_index = self.block_index + 1
1614
self._update_current_block()
1615
continue # next loop-on-block/dir
1616
result = self._loop_one_block()
1617
if result is not None:
1619
if len(self.search_specific_files):
1620
# More supplied paths to process
1621
self.current_root = None
1622
return self._iter_next()
1623
raise StopIteration()
1625
cdef object _maybe_tree_ref(self, current_path_info):
1626
if self.tree._directory_is_tree_reference(
1627
self.utf8_decode(current_path_info[0])[0]):
1628
return current_path_info[:2] + \
1629
('tree-reference',) + current_path_info[3:]
1631
return current_path_info
1633
cdef object _loop_one_block(self):
1634
# current_dir_info and current_block refer to the same directory -
1635
# this is the common case code.
1636
# Assign local variables for current path and entry:
1637
cdef object current_entry
1638
cdef object current_path_info
1639
cdef int path_handled
1642
# cdef char * temp_str
1643
# cdef Py_ssize_t temp_str_length
1644
# PyString_AsStringAndSize(disk_kind, &temp_str, &temp_str_length)
1645
# if not strncmp(temp_str, "directory", temp_str_length):
1646
if (self.current_block is not None and
1647
self.current_block_pos < PyList_GET_SIZE(self.current_block_list)):
1648
current_entry = PyList_GET_ITEM(self.current_block_list,
1649
self.current_block_pos)
1651
Py_INCREF(current_entry)
1653
current_entry = None
1654
if (self.current_dir_info is not None and
1655
self.path_index < PyList_GET_SIZE(self.current_dir_list)):
1656
current_path_info = PyList_GET_ITEM(self.current_dir_list,
1659
Py_INCREF(current_path_info)
1660
disk_kind = PyTuple_GET_ITEM(current_path_info, 2)
1662
Py_INCREF(disk_kind)
1663
if disk_kind == "directory":
1664
current_path_info = self._maybe_tree_ref(current_path_info)
1666
current_path_info = None
1667
while (current_entry is not None or current_path_info is not None):
1672
if current_entry is None:
1673
# unversioned - the check for path_handled when the path
1674
# is advanced will yield this path if needed.
1676
elif current_path_info is None:
1677
# no path is fine: the per entry code will handle it.
1678
result = self._process_entry(current_entry, current_path_info)
1679
if result is not None:
1680
if result is self.uninteresting:
1683
minikind = _minikind_from_string(
1684
current_entry[1][self.target_index][0])
1685
cmp_result = cmp(current_path_info[1], current_entry[0][1])
1686
if (cmp_result or minikind == c'a' or minikind == c'r'):
1687
# The current path on disk doesn't match the dirblock
1688
# record. Either the dirblock record is marked as
1689
# absent/renamed, or the file on disk is not present at all
1690
# in the dirblock. Either way, report about the dirblock
1691
# entry, and let other code handle the filesystem one.
1693
# Compare the basename for these files to determine
1696
# extra file on disk: pass for now, but only
1697
# increment the path, not the entry
1700
# entry referring to file not present on disk.
1701
# 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:
1708
# paths are the same,and the dirstate entry is not
1709
# absent or renamed.
1710
result = self._process_entry(current_entry, current_path_info)
1711
if result is not None:
1713
if result is self.uninteresting:
1715
# >- loop control starts here:
1717
if advance_entry and current_entry is not None:
1718
self.current_block_pos = self.current_block_pos + 1
1719
if self.current_block_pos < PyList_GET_SIZE(self.current_block_list):
1720
current_entry = self.current_block_list[self.current_block_pos]
1722
current_entry = None
1724
if advance_path and current_path_info is not None:
1725
if not path_handled:
1726
# unversioned in all regards
1727
if self.want_unversioned:
1728
new_executable = bool(
1729
stat.S_ISREG(current_path_info[3].st_mode)
1730
and stat.S_IEXEC & current_path_info[3].st_mode)
1732
relpath_unicode = self.utf8_decode(current_path_info[0])[0]
1733
except UnicodeDecodeError:
1734
raise errors.BadFilenameEncoding(
1735
current_path_info[0], osutils._fs_enc)
1736
if result is not None:
1737
raise AssertionError(
1738
"result is not None: %r" % result)
1740
(None, relpath_unicode),
1744
(None, self.utf8_decode(current_path_info[1])[0]),
1745
(None, current_path_info[2]),
1746
(None, new_executable))
1747
# dont descend into this unversioned path if it is
1749
if current_path_info[2] in ('directory'):
1750
del self.current_dir_list[self.path_index]
1751
self.path_index = self.path_index - 1
1752
# dont descend the disk iterator into any tree
1754
if current_path_info[2] == 'tree-reference':
1755
del self.current_dir_list[self.path_index]
1756
self.path_index = self.path_index - 1
1757
self.path_index = self.path_index + 1
1758
if self.path_index < len(self.current_dir_list):
1759
current_path_info = self.current_dir_list[self.path_index]
1760
if current_path_info[2] == 'directory':
1761
current_path_info = self._maybe_tree_ref(
1764
current_path_info = None
1765
if result is not None:
1766
# Found a result on this pass, yield it
1768
if self.current_block is not None:
1769
self.block_index = self.block_index + 1
1770
self._update_current_block()
1771
if self.current_dir_info is not None:
1773
self.current_dir_list = None
1775
self.current_dir_info = self.dir_iterator.next()
1776
self.current_dir_list = self.current_dir_info[1]
1777
except StopIteration:
1778
self.current_dir_info = None