724
764
reader._parse_dirblocks()
725
765
state._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
768
cdef int minikind_from_mode(int mode):
769
# in order of frequency:
779
_encode = binascii.b2a_base64
782
from struct import pack
783
cdef _pack_stat(stat_value):
784
"""return a string representing the stat value's key fields.
786
:param stat_value: A stat oject with st_size, st_mtime, st_ctime, st_dev,
787
st_ino and st_mode fields.
789
cdef char result[6*4] # 6 long ints
791
aliased = <int *>result
792
aliased[0] = htonl(stat_value.st_size)
793
aliased[1] = htonl(int(stat_value.st_mtime))
794
aliased[2] = htonl(int(stat_value.st_ctime))
795
aliased[3] = htonl(stat_value.st_dev)
796
aliased[4] = htonl(stat_value.st_ino & 0xFFFFFFFF)
797
aliased[5] = htonl(stat_value.st_mode)
798
packed = PyString_FromStringAndSize(result, 6*4)
799
return _encode(packed)[:-1]
802
def update_entry(self, entry, abspath, stat_value):
803
"""Update the entry based on what is actually on disk.
805
This function only calculates the sha if it needs to - if the entry is
806
uncachable, or clearly different to the first parent's entry, no sha
807
is calculated, and None is returned.
809
:param entry: This is the dirblock entry for the file in question.
810
:param abspath: The path on disk for this file.
811
:param stat_value: (optional) if we already have done a stat on the
813
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
816
return _update_entry(self, entry, abspath, stat_value)
819
cdef _update_entry(self, entry, abspath, stat_value):
820
"""Update the entry based on what is actually on disk.
822
This function only calculates the sha if it needs to - if the entry is
823
uncachable, or clearly different to the first parent's entry, no sha
824
is calculated, and None is returned.
826
:param self: The dirstate object this is operating on.
827
:param entry: This is the dirblock entry for the file in question.
828
:param abspath: The path on disk for this file.
829
:param stat_value: The stat value done on the path.
830
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
833
# TODO - require pyrex 0.9.8, then use a pyd file to define access to the
834
# _st mode of the compiled stat objects.
835
cdef int minikind, saved_minikind
837
minikind = minikind_from_mode(stat_value.st_mode)
840
packed_stat = _pack_stat(stat_value)
841
details = PyList_GetItem_void_void(PyTuple_GetItem_void_void(<void *>entry, 1), 0)
842
saved_minikind = PyString_AsString_obj(<PyObject *>PyTuple_GetItem_void_void(details, 0))[0]
843
saved_link_or_sha1 = PyTuple_GetItem_void_object(details, 1)
844
saved_file_size = PyTuple_GetItem_void_object(details, 2)
845
saved_executable = PyTuple_GetItem_void_object(details, 3)
846
saved_packed_stat = PyTuple_GetItem_void_object(details, 4)
847
# Deal with pyrex decrefing the objects
848
Py_INCREF(saved_link_or_sha1)
849
Py_INCREF(saved_file_size)
850
Py_INCREF(saved_executable)
851
Py_INCREF(saved_packed_stat)
852
#(saved_minikind, saved_link_or_sha1, saved_file_size,
853
# saved_executable, saved_packed_stat) = entry[1][0]
855
if (minikind == saved_minikind
856
and packed_stat == saved_packed_stat):
857
# The stat hasn't changed since we saved, so we can re-use the
862
# size should also be in packed_stat
863
if saved_file_size == stat_value.st_size:
864
return saved_link_or_sha1
866
# If we have gotten this far, that means that we need to actually
867
# process this entry.
870
executable = self._is_executable(stat_value.st_mode,
872
if self._cutoff_time is None:
873
self._sha_cutoff_time()
874
if (stat_value.st_mtime < self._cutoff_time
875
and stat_value.st_ctime < self._cutoff_time
876
and len(entry[1]) > 1
877
and entry[1][1][0] != 'a'):
878
# Could check for size changes for further optimised
879
# avoidance of sha1's. However the most prominent case of
880
# over-shaing is during initial add, which this catches.
881
link_or_sha1 = self._sha1_file(abspath)
882
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
883
executable, packed_stat)
885
entry[1][0] = ('f', '', stat_value.st_size,
886
executable, DirState.NULLSTAT)
887
elif minikind == c'd':
889
entry[1][0] = ('d', '', 0, False, packed_stat)
890
if saved_minikind != c'd':
891
# This changed from something into a directory. Make sure we
892
# have a directory block for it. This doesn't happen very
893
# often, so this doesn't have to be super fast.
894
block_index, entry_index, dir_present, file_present = \
895
self._get_block_entry_index(entry[0][0], entry[0][1], 0)
896
self._ensure_block(block_index, entry_index,
897
pathjoin(entry[0][0], entry[0][1]))
898
elif minikind == c'l':
899
link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
900
if self._cutoff_time is None:
901
self._sha_cutoff_time()
902
if (stat_value.st_mtime < self._cutoff_time
903
and stat_value.st_ctime < self._cutoff_time):
904
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
907
entry[1][0] = ('l', '', stat_value.st_size,
908
False, DirState.NULLSTAT)
909
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
913
cdef char _minikind_from_string(object string):
914
"""Convert a python string to a char."""
915
return PyString_AsString(string)[0]
918
cdef object _kind_absent
919
cdef object _kind_file
920
cdef object _kind_directory
921
cdef object _kind_symlink
922
cdef object _kind_relocated
923
cdef object _kind_tree_reference
924
_kind_absent = "absent"
926
_kind_directory = "directory"
927
_kind_symlink = "symlink"
928
_kind_relocated = "relocated"
929
_kind_tree_reference = "tree-reference"
932
cdef object _minikind_to_kind(char minikind):
933
"""Create a string kind for minikind."""
934
cdef char _minikind[1]
937
elif minikind == c'd':
938
return _kind_directory
939
elif minikind == c'a':
941
elif minikind == c'r':
942
return _kind_relocated
943
elif minikind == c'l':
945
elif minikind == c't':
946
return _kind_tree_reference
947
_minikind[0] = minikind
948
raise KeyError(PyString_FromStringAndSize(_minikind, 1))
951
cdef int _versioned_minikind(char minikind):
952
"""Return non-zero if minikind is in fltd"""
953
return (minikind == c'f' or
959
cdef class ProcessEntryC:
961
cdef object old_dirname_to_file_id # dict
962
cdef object new_dirname_to_file_id # dict
963
cdef readonly object uninteresting
964
cdef object last_source_parent
965
cdef object last_target_parent
966
cdef object include_unchanged
967
cdef object use_filesystem_for_exec
968
cdef object utf8_decode
969
cdef readonly object searched_specific_files
970
cdef object search_specific_files
972
# Current iteration variables:
973
cdef object current_root
974
cdef object current_root_unicode
975
cdef object root_entries
976
cdef int root_entries_pos, root_entries_len
977
cdef object root_abspath
978
cdef int source_index, target_index
979
cdef int want_unversioned
981
cdef object dir_iterator
983
cdef object current_block
984
cdef int current_block_pos
985
cdef object current_block_list
986
cdef object current_dir_info
987
cdef object current_dir_list
989
cdef object root_dir_info
990
cdef object bisect_left
995
def __init__(self, include_unchanged, use_filesystem_for_exec,
996
search_specific_files, state, source_index, target_index,
997
want_unversioned, tree):
998
self.old_dirname_to_file_id = {}
999
self.new_dirname_to_file_id = {}
1000
# Just a sentry, so that _process_entry can say that this
1001
# record is handled, but isn't interesting to process (unchanged)
1002
self.uninteresting = object()
1003
# Using a list so that we can access the values and change them in
1004
# nested scope. Each one is [path, file_id, entry]
1005
self.last_source_parent = [None, None]
1006
self.last_target_parent = [None, None]
1007
self.include_unchanged = include_unchanged
1008
self.use_filesystem_for_exec = use_filesystem_for_exec
1009
self.utf8_decode = cache_utf8._utf8_decode
1010
# for all search_indexs in each path at or under each element of
1011
# search_specific_files, if the detail is relocated: add the id, and add the
1012
# relocated path as one to search if its not searched already. If the
1013
# detail is not relocated, add the id.
1014
self.searched_specific_files = set()
1015
self.search_specific_files = search_specific_files
1017
self.current_root = None
1018
self.current_root_unicode = None
1019
self.root_entries = None
1020
self.root_entries_pos = 0
1021
self.root_entries_len = 0
1022
self.root_abspath = None
1023
if source_index is None:
1024
self.source_index = -1
1026
self.source_index = source_index
1027
self.target_index = target_index
1028
self.want_unversioned = want_unversioned
1030
self.dir_iterator = None
1031
self.block_index = -1
1032
self.current_block = None
1033
self.current_block_list = None
1034
self.current_block_pos = -1
1035
self.current_dir_info = None
1036
self.current_dir_list = None
1038
self.root_dir_info = None
1039
self.bisect_left = bisect.bisect_left
1040
self.pathjoin = osutils.pathjoin
1041
self.fstat = os.fstat
1042
self.sha_file = osutils.sha_file
1044
cdef _process_entry(self, entry, path_info):
1045
"""Compare an entry and real disk to generate delta information.
1047
:param path_info: top_relpath, basename, kind, lstat, abspath for
1048
the path of entry. If None, then the path is considered absent.
1049
(Perhaps we should pass in a concrete entry for this ?)
1050
Basename is returned as a utf8 string because we expect this
1051
tuple will be ignored, and don't want to take the time to
1053
:return: None if the these don't match
1054
A tuple of information about the change, or
1055
the object 'uninteresting' if these match, but are
1056
basically identical.
1058
cdef char target_minikind
1059
cdef char source_minikind
1061
cdef int content_change
1062
cdef object details_list
1064
details_list = entry[1]
1065
if -1 == self.source_index:
1066
source_details = DirState.NULL_PARENT_DETAILS
1068
source_details = details_list[self.source_index]
1069
target_details = details_list[self.target_index]
1070
target_minikind = _minikind_from_string(target_details[0])
1071
if path_info is not None and _versioned_minikind(target_minikind):
1072
if self.target_index != 0:
1073
raise AssertionError("Unsupported target index %d" % target_index)
1074
link_or_sha1 = _update_entry(self.state, entry, path_info[4], path_info[3])
1075
# The entry may have been modified by update_entry
1076
target_details = details_list[self.target_index]
1077
target_minikind = _minikind_from_string(target_details[0])
1080
# the rest of this function is 0.3 seconds on 50K paths, or
1081
# 0.000006 seconds per call.
1082
source_minikind = _minikind_from_string(source_details[0])
1083
if ((_versioned_minikind(source_minikind) or source_minikind == c'r')
1084
and _versioned_minikind(target_minikind)):
1085
# claimed content in both: diff
1086
# r | fdlt | | add source to search, add id path move and perform
1087
# | | | diff check on source-target
1088
# r | fdlt | a | dangling file that was present in the basis.
1090
if source_minikind != c'r':
1091
old_dirname = entry[0][0]
1092
old_basename = entry[0][1]
1093
old_path = path = None
1095
# add the source to the search path to find any children it
1096
# has. TODO ? : only add if it is a container ?
1097
if not osutils.is_inside_any(self.searched_specific_files,
1099
self.search_specific_files.add(source_details[1])
1100
# generate the old path; this is needed for stating later
1102
old_path = source_details[1]
1103
old_dirname, old_basename = os.path.split(old_path)
1104
path = self.pathjoin(entry[0][0], entry[0][1])
1105
old_entry = self.state._get_entry(self.source_index,
1107
# update the source details variable to be the real
1109
if old_entry == (None, None):
1110
raise errors.CorruptDirstate(self.state._filename,
1111
"entry '%s/%s' is considered renamed from %r"
1112
" but source does not exist\n"
1113
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
1114
source_details = old_entry[1][self.source_index]
1115
source_minikind = _minikind_from_string(source_details[0])
1116
if path_info is None:
1117
# the file is missing on disk, show as removed.
1122
# source and target are both versioned and disk file is present.
1123
target_kind = path_info[2]
1124
if target_kind == 'directory':
1126
old_path = path = self.pathjoin(old_dirname, old_basename)
1127
file_id = entry[0][2]
1128
self.new_dirname_to_file_id[path] = file_id
1129
if source_minikind != c'd':
1132
# directories have no fingerprint
1135
elif target_kind == 'file':
1136
if source_minikind != c'f':
1139
# If the size is the same, check the sha:
1140
if target_details[2] == source_details[2]:
1141
if link_or_sha1 is None:
1143
file_obj = file(path_info[4], 'rb')
1145
# XXX: TODO: Use lower level file IO rather
1146
# than python objects for sha-misses.
1147
statvalue = self.fstat(file_obj.fileno())
1148
link_or_sha1 = self.sha_file(file_obj)
1151
self.state._observed_sha1(entry, link_or_sha1,
1153
content_change = (link_or_sha1 != source_details[1])
1155
# Size changed, so must be different
1157
# Target details is updated at update_entry time
1158
if self.use_filesystem_for_exec:
1159
# We don't need S_ISREG here, because we are sure
1160
# we are dealing with a file.
1161
target_exec = bool(S_IXUSR & path_info[3].st_mode)
1163
target_exec = target_details[3]
1164
elif target_kind == 'symlink':
1165
if source_minikind != c'l':
1168
content_change = (link_or_sha1 != source_details[1])
1170
elif target_kind == 'tree-reference':
1171
if source_minikind != c't':
1177
raise Exception, "unknown kind %s" % path_info[2]
1178
if source_minikind == c'd':
1180
old_path = path = self.pathjoin(old_dirname, old_basename)
1182
file_id = entry[0][2]
1183
self.old_dirname_to_file_id[old_path] = file_id
1184
# parent id is the entry for the path in the target tree
1185
if old_dirname == self.last_source_parent[0]:
1186
source_parent_id = self.last_source_parent[1]
1189
source_parent_id = self.old_dirname_to_file_id[old_dirname]
1191
source_parent_entry = self.state._get_entry(self.source_index,
1192
path_utf8=old_dirname)
1193
source_parent_id = source_parent_entry[0][2]
1194
if source_parent_id == entry[0][2]:
1195
# This is the root, so the parent is None
1196
source_parent_id = None
1198
self.last_source_parent[0] = old_dirname
1199
self.last_source_parent[1] = source_parent_id
1200
new_dirname = entry[0][0]
1201
if new_dirname == self.last_target_parent[0]:
1202
target_parent_id = self.last_target_parent[1]
1205
target_parent_id = self.new_dirname_to_file_id[new_dirname]
1207
# TODO: We don't always need to do the lookup, because the
1208
# parent entry will be the same as the source entry.
1209
target_parent_entry = self.state._get_entry(self.target_index,
1210
path_utf8=new_dirname)
1211
if target_parent_entry == (None, None):
1212
raise AssertionError(
1213
"Could not find target parent in wt: %s\nparent of: %s"
1214
% (new_dirname, entry))
1215
target_parent_id = target_parent_entry[0][2]
1216
if target_parent_id == entry[0][2]:
1217
# This is the root, so the parent is None
1218
target_parent_id = None
1220
self.last_target_parent[0] = new_dirname
1221
self.last_target_parent[1] = target_parent_id
1223
source_exec = source_details[3]
1224
if (self.include_unchanged
1226
or source_parent_id != target_parent_id
1227
or old_basename != entry[0][1]
1228
or source_exec != target_exec
1230
if old_path is None:
1231
path = self.pathjoin(old_dirname, old_basename)
1233
old_path_u = self.utf8_decode(old_path)[0]
1236
old_path_u = self.utf8_decode(old_path)[0]
1237
if old_path == path:
1240
path_u = self.utf8_decode(path)[0]
1241
source_kind = _minikind_to_kind(source_minikind)
1242
return (entry[0][2],
1243
(old_path_u, path_u),
1246
(source_parent_id, target_parent_id),
1247
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
1248
(source_kind, target_kind),
1249
(source_exec, target_exec))
1251
return self.uninteresting
1252
elif source_minikind == c'a' and _versioned_minikind(target_minikind):
1253
# looks like a new file
1254
path = self.pathjoin(entry[0][0], entry[0][1])
1255
# parent id is the entry for the path in the target tree
1256
# TODO: these are the same for an entire directory: cache em.
1257
parent_id = self.state._get_entry(self.target_index,
1258
path_utf8=entry[0][0])[0][2]
1259
if parent_id == entry[0][2]:
1261
if path_info is not None:
1263
if self.use_filesystem_for_exec:
1264
# We need S_ISREG here, because we aren't sure if this
1267
S_ISREG(path_info[3].st_mode)
1268
and S_IXUSR & path_info[3].st_mode)
1270
target_exec = target_details[3]
1271
return (entry[0][2],
1272
(None, self.utf8_decode(path)[0]),
1276
(None, self.utf8_decode(entry[0][1])[0]),
1277
(None, path_info[2]),
1278
(None, target_exec))
1280
# Its a missing file, report it as such.
1281
return (entry[0][2],
1282
(None, self.utf8_decode(path)[0]),
1286
(None, self.utf8_decode(entry[0][1])[0]),
1289
elif _versioned_minikind(source_minikind) and target_minikind == c'a':
1290
# unversioned, possibly, or possibly not deleted: we dont care.
1291
# if its still on disk, *and* theres no other entry at this
1292
# path [we dont know this in this routine at the moment -
1293
# perhaps we should change this - then it would be an unknown.
1294
old_path = self.pathjoin(entry[0][0], entry[0][1])
1295
# parent id is the entry for the path in the target tree
1296
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
1297
if parent_id == entry[0][2]:
1299
return (entry[0][2],
1300
(self.utf8_decode(old_path)[0], None),
1304
(self.utf8_decode(entry[0][1])[0], None),
1305
(_minikind_to_kind(source_minikind), None),
1306
(source_details[3], None))
1307
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1308
# a rename; could be a true rename, or a rename inherited from
1309
# a renamed parent. TODO: handle this efficiently. Its not
1310
# common case to rename dirs though, so a correct but slow
1311
# implementation will do.
1312
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1313
self.search_specific_files.add(target_details[1])
1314
elif ((source_minikind == c'r' or source_minikind == c'a') and
1315
(target_minikind == c'r' or target_minikind == c'a')):
1316
# neither of the selected trees contain this path,
1317
# so skip over it. This is not currently directly tested, but
1318
# is indirectly via test_too_much.TestCommands.test_conflicts.
1321
raise AssertionError("don't know how to compare "
1322
"source_minikind=%r, target_minikind=%r"
1323
% (source_minikind, target_minikind))
1324
## import pdb;pdb.set_trace()
1330
def iter_changes(self):
1333
cdef void _update_current_block(self):
1334
if (self.block_index < len(self.state._dirblocks) and
1335
osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1336
self.current_block = self.state._dirblocks[self.block_index]
1337
self.current_block_list = self.current_block[1]
1338
self.current_block_pos = 0
1340
self.current_block = None
1341
self.current_block_list = None
1344
# Simple thunk to allow tail recursion without pyrex confusion
1345
return self._iter_next()
1347
cdef _iter_next(self):
1348
"""Iterate over the changes."""
1349
# This function single steps through an iterator. As such while loops
1350
# are often exited by 'return' - the code is structured so that the
1351
# next call into the function will return to the same while loop. Note
1352
# that all flow control needed to re-reach that step is reexecuted,
1353
# which can be a performance problem. It has not yet been tuned to
1354
# minimise this; a state machine is probably the simplest restructuring
1355
# to both minimise this overhead and make the code considerably more
1359
# compare source_index and target_index at or under each element of search_specific_files.
1360
# follow the following comparison table. Note that we only want to do diff operations when
1361
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1365
# Source | Target | disk | action
1366
# r | fdlt | | add source to search, add id path move and perform
1367
# | | | diff check on source-target
1368
# r | fdlt | a | dangling file that was present in the basis.
1370
# r | a | | add source to search
1372
# r | r | | this path is present in a non-examined tree, skip.
1373
# r | r | a | this path is present in a non-examined tree, skip.
1374
# a | fdlt | | add new id
1375
# a | fdlt | a | dangling locally added file, skip
1376
# a | a | | not present in either tree, skip
1377
# a | a | a | not present in any tree, skip
1378
# a | r | | not present in either tree at this path, skip as it
1379
# | | | may not be selected by the users list of paths.
1380
# a | r | a | not present in either tree at this path, skip as it
1381
# | | | may not be selected by the users list of paths.
1382
# fdlt | fdlt | | content in both: diff them
1383
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1384
# fdlt | a | | unversioned: output deleted id for now
1385
# fdlt | a | a | unversioned and deleted: output deleted id
1386
# fdlt | r | | relocated in this tree, so add target to search.
1387
# | | | Dont diff, we will see an r,fd; pair when we reach
1388
# | | | this id at the other path.
1389
# fdlt | r | a | relocated in this tree, so add target to search.
1390
# | | | Dont diff, we will see an r,fd; pair when we reach
1391
# | | | this id at the other path.
1393
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1394
# keeping a cache of directories that we have seen.
1395
cdef object current_dirname, current_blockname
1396
cdef char * current_dirname_c, * current_blockname_c
1397
cdef int advance_entry, advance_path
1398
cdef int path_handled
1399
uninteresting = self.uninteresting
1400
searched_specific_files = self.searched_specific_files
1401
# Are we walking a root?
1402
while self.root_entries_pos < self.root_entries_len:
1403
entry = self.root_entries[self.root_entries_pos]
1404
self.root_entries_pos = self.root_entries_pos + 1
1405
result = self._process_entry(entry, self.root_dir_info)
1406
if result is not None and result is not self.uninteresting:
1408
# Have we finished the prior root, or never started one ?
1409
if self.current_root is None:
1410
# TODO: the pending list should be lexically sorted? the
1411
# interface doesn't require it.
1413
self.current_root = self.search_specific_files.pop()
1415
raise StopIteration()
1416
self.current_root_unicode = self.current_root.decode('utf8')
1417
self.searched_specific_files.add(self.current_root)
1418
# process the entries for this containing directory: the rest will be
1419
# found by their parents recursively.
1420
self.root_entries = self.state._entries_for_path(self.current_root)
1421
self.root_entries_len = len(self.root_entries)
1422
self.root_abspath = self.tree.abspath(self.current_root_unicode)
1424
root_stat = os.lstat(self.root_abspath)
1426
if e.errno == errno.ENOENT:
1427
# the path does not exist: let _process_entry know that.
1428
self.root_dir_info = None
1430
# some other random error: hand it up.
1433
self.root_dir_info = ('', self.current_root,
1434
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
1436
if self.root_dir_info[2] == 'directory':
1437
if self.tree._directory_is_tree_reference(
1438
self.current_root_unicode):
1439
self.root_dir_info = self.root_dir_info[:2] + \
1440
('tree-reference',) + self.root_dir_info[3:]
1441
if not self.root_entries and not self.root_dir_info:
1442
# this specified path is not present at all, skip it.
1443
# (tail recursion, can do a loop once the full structure is
1445
return self._iter_next()
1447
self.root_entries_pos = 0
1448
# XXX Clarity: This loop is duplicated a out the self.current_root
1449
# is None guard above: if we return from it, it completes there
1450
# (and the following if block cannot trigger because
1451
# path_handled must be true, so the if block is not # duplicated.
1452
while self.root_entries_pos < self.root_entries_len:
1453
entry = self.root_entries[self.root_entries_pos]
1454
self.root_entries_pos = self.root_entries_pos + 1
1455
result = self._process_entry(entry, self.root_dir_info)
1456
if result is not None:
1458
if result is not self.uninteresting:
1460
# handle unversioned specified paths:
1461
if self.want_unversioned and not path_handled and self.root_dir_info:
1462
new_executable = bool(
1463
stat.S_ISREG(self.root_dir_info[3].st_mode)
1464
and stat.S_IEXEC & self.root_dir_info[3].st_mode)
1466
(None, self.current_root_unicode),
1470
(None, splitpath(self.current_root_unicode)[-1]),
1471
(None, self.root_dir_info[2]),
1472
(None, new_executable)
1474
# If we reach here, the outer flow continues, which enters into the
1475
# per-root setup logic.
1476
if self.current_dir_info is None and self.current_block is None:
1477
# setup iteration of this root:
1478
self.current_dir_list = None
1479
if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
1480
self.current_dir_info = None
1482
self.dir_iterator = osutils._walkdirs_utf8(self.root_abspath,
1483
prefix=self.current_root)
1486
self.current_dir_info = self.dir_iterator.next()
1487
self.current_dir_list = self.current_dir_info[1]
1489
# there may be directories in the inventory even though
1490
# this path is not a file on disk: so mark it as end of
1492
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
1493
self.current_dir_info = None
1494
elif sys.platform == 'win32':
1495
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
1496
# python 2.5 has e.errno == EINVAL,
1497
# and e.winerror == ERROR_DIRECTORY
1499
e_winerror = e.winerror
1500
except AttributeError:
1502
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
1503
if (e.errno in win_errors or e_winerror in win_errors):
1504
self.current_dir_info = None
1506
# Will this really raise the right exception ?
1511
if self.current_dir_info[0][0] == '':
1512
# remove .bzr from iteration
1513
bzr_index = self.bisect_left(self.current_dir_list, ('.bzr',))
1514
if self.current_dir_list[bzr_index][0] != '.bzr':
1515
raise AssertionError()
1516
del self.current_dir_list[bzr_index]
1517
initial_key = (self.current_root, '', '')
1518
self.block_index, _ = self.state._find_block_index_from_key(initial_key)
1519
if self.block_index == 0:
1520
# we have processed the total root already, but because the
1521
# initial key matched it we should skip it here.
1522
self.block_index = self.block_index + 1
1523
self._update_current_block()
1524
# walk until both the directory listing and the versioned metadata
1526
while (self.current_dir_info is not None
1527
or self.current_block is not None):
1528
# Uncommon case - a missing directory or an unversioned directory:
1529
if (self.current_dir_info and self.current_block
1530
and self.current_dir_info[0][0] != self.current_block[0]):
1531
# Work around pyrex broken heuristic - current_dirname has
1532
# the same scope as current_dirname_c
1533
current_dirname = self.current_dir_info[0][0]
1534
current_dirname_c = PyString_AS_STRING_void(
1535
<void *>current_dirname)
1536
current_blockname = self.current_block[0]
1537
current_blockname_c = PyString_AS_STRING_void(
1538
<void *>current_blockname)
1539
# In the python generator we evaluate this if block once per
1540
# dir+block; because we reenter in the pyrex version its being
1541
# evaluated once per path: we could cache the result before
1542
# doing the while loop and probably save time.
1543
if _cmp_by_dirs(current_dirname_c,
1544
PyString_Size(current_dirname),
1545
current_blockname_c,
1546
PyString_Size(current_blockname)) < 0:
1547
# filesystem data refers to paths not covered by the
1548
# dirblock. this has two possibilities:
1549
# A) it is versioned but empty, so there is no block for it
1550
# B) it is not versioned.
1552
# if (A) then we need to recurse into it to check for
1553
# new unknown files or directories.
1554
# if (B) then we should ignore it, because we don't
1555
# recurse into unknown directories.
1556
# We are doing a loop
1557
while self.path_index < len(self.current_dir_list):
1558
current_path_info = self.current_dir_list[self.path_index]
1559
# dont descend into this unversioned path if it is
1561
if current_path_info[2] in ('directory',
1563
del self.current_dir_list[self.path_index]
1564
self.path_index = self.path_index - 1
1565
self.path_index = self.path_index + 1
1566
if self.want_unversioned:
1567
if current_path_info[2] == 'directory':
1568
if self.tree._directory_is_tree_reference(
1569
self.utf8_decode(current_path_info[0])[0]):
1570
current_path_info = current_path_info[:2] + \
1571
('tree-reference',) + current_path_info[3:]
1572
new_executable = bool(
1573
stat.S_ISREG(current_path_info[3].st_mode)
1574
and stat.S_IEXEC & current_path_info[3].st_mode)
1576
(None, self.utf8_decode(current_path_info[0])[0]),
1580
(None, self.utf8_decode(current_path_info[1])[0]),
1581
(None, current_path_info[2]),
1582
(None, new_executable))
1583
# This dir info has been handled, go to the next
1585
self.current_dir_list = None
1587
self.current_dir_info = self.dir_iterator.next()
1588
self.current_dir_list = self.current_dir_info[1]
1589
except StopIteration:
1590
self.current_dir_info = None
1592
# We have a dirblock entry for this location, but there
1593
# is no filesystem path for this. This is most likely
1594
# because a directory was removed from the disk.
1595
# We don't have to report the missing directory,
1596
# because that should have already been handled, but we
1597
# need to handle all of the files that are contained
1599
while self.current_block_pos < len(self.current_block_list):
1600
current_entry = self.current_block_list[self.current_block_pos]
1601
self.current_block_pos = self.current_block_pos + 1
1602
# entry referring to file not present on disk.
1603
# advance the entry only, after processing.
1604
result = self._process_entry(current_entry, None)
1605
if result is not None:
1606
if result is not self.uninteresting:
1608
self.block_index = self.block_index + 1
1609
self._update_current_block()
1610
continue # next loop-on-block/dir
1611
result = self._loop_one_block()
1612
if result is not None:
1614
if len(self.search_specific_files):
1615
# More supplied paths to process
1616
self.current_root = None
1617
return self._iter_next()
1618
raise StopIteration()
1620
cdef object _maybe_tree_ref(self, current_path_info):
1621
if self.tree._directory_is_tree_reference(
1622
self.utf8_decode(current_path_info[0])[0]):
1623
return current_path_info[:2] + \
1624
('tree-reference',) + current_path_info[3:]
1626
return current_path_info
1628
cdef object _loop_one_block(self):
1629
# current_dir_info and current_block refer to the same directory -
1630
# this is the common case code.
1631
# Assign local variables for current path and entry:
1632
cdef object current_entry
1633
cdef object current_path_info
1634
cdef int path_handled
1637
# cdef char * temp_str
1638
# cdef Py_ssize_t temp_str_length
1639
# PyString_AsStringAndSize(disk_kind, &temp_str, &temp_str_length)
1640
# if not strncmp(temp_str, "directory", temp_str_length):
1641
if (self.current_block is not None and
1642
self.current_block_pos < PyList_GET_SIZE(self.current_block_list)):
1643
current_entry = PyList_GET_ITEM(self.current_block_list,
1644
self.current_block_pos)
1646
Py_INCREF(current_entry)
1648
current_entry = None
1649
if (self.current_dir_info is not None and
1650
self.path_index < PyList_GET_SIZE(self.current_dir_list)):
1651
current_path_info = PyList_GET_ITEM(self.current_dir_list,
1654
Py_INCREF(current_path_info)
1655
disk_kind = PyTuple_GET_ITEM(current_path_info, 2)
1657
Py_INCREF(disk_kind)
1658
if disk_kind == "directory":
1659
current_path_info = self._maybe_tree_ref(current_path_info)
1661
current_path_info = None
1662
while (current_entry is not None or current_path_info is not None):
1667
if current_entry is None:
1668
# unversioned - the check for path_handled when the path
1669
# is advanced will yield this path if needed.
1671
elif current_path_info is None:
1672
# no path is fine: the per entry code will handle it.
1673
result = self._process_entry(current_entry, current_path_info)
1674
if result is not None:
1675
if result is self.uninteresting:
1678
minikind = _minikind_from_string(
1679
current_entry[1][self.target_index][0])
1680
cmp_result = cmp(current_path_info[1], current_entry[0][1])
1681
if (cmp_result or minikind == c'a' or minikind == c'r'):
1682
# The current path on disk doesn't match the dirblock
1683
# record. Either the dirblock record is marked as
1684
# absent/renamed, or the file on disk is not present at all
1685
# in the dirblock. Either way, report about the dirblock
1686
# entry, and let other code handle the filesystem one.
1688
# Compare the basename for these files to determine
1691
# extra file on disk: pass for now, but only
1692
# increment the path, not the entry
1695
# entry referring to file not present on disk.
1696
# advance the entry only, after processing.
1697
result = self._process_entry(current_entry, None)
1698
if result is not None:
1699
if result is self.uninteresting:
1703
# paths are the same,and the dirstate entry is not
1704
# absent or renamed.
1705
result = self._process_entry(current_entry, current_path_info)
1706
if result is not None:
1708
if result is self.uninteresting:
1710
# >- loop control starts here:
1712
if advance_entry and current_entry is not None:
1713
self.current_block_pos = self.current_block_pos + 1
1714
if self.current_block_pos < PyList_GET_SIZE(self.current_block_list):
1715
current_entry = self.current_block_list[self.current_block_pos]
1717
current_entry = None
1719
if advance_path and current_path_info is not None:
1720
if not path_handled:
1721
# unversioned in all regards
1722
if self.want_unversioned:
1723
new_executable = bool(
1724
stat.S_ISREG(current_path_info[3].st_mode)
1725
and stat.S_IEXEC & current_path_info[3].st_mode)
1727
relpath_unicode = self.utf8_decode(current_path_info[0])[0]
1728
except UnicodeDecodeError:
1729
raise errors.BadFilenameEncoding(
1730
current_path_info[0], osutils._fs_enc)
1731
if result is not None:
1732
raise AssertionError(
1733
"result is not None: %r" % result)
1735
(None, relpath_unicode),
1739
(None, self.utf8_decode(current_path_info[1])[0]),
1740
(None, current_path_info[2]),
1741
(None, new_executable))
1742
# dont descend into this unversioned path if it is
1744
if current_path_info[2] in ('directory'):
1745
del self.current_dir_list[self.path_index]
1746
self.path_index = self.path_index - 1
1747
# dont descend the disk iterator into any tree
1749
if current_path_info[2] == 'tree-reference':
1750
del self.current_dir_list[self.path_index]
1751
self.path_index = self.path_index - 1
1752
self.path_index = self.path_index + 1
1753
if self.path_index < len(self.current_dir_list):
1754
current_path_info = self.current_dir_list[self.path_index]
1755
if current_path_info[2] == 'directory':
1756
current_path_info = self._maybe_tree_ref(
1759
current_path_info = None
1760
if result is not None:
1761
# Found a result on this pass, yield it
1763
if self.current_block is not None:
1764
self.block_index = self.block_index + 1
1765
self._update_current_block()
1766
if self.current_dir_info is not None:
1768
self.current_dir_list = None
1770
self.current_dir_info = self.dir_iterator.next()
1771
self.current_dir_list = self.current_dir_info[1]
1772
except StopIteration:
1773
self.current_dir_info = None