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
saved_link_or_sha1 = PyTuple_GetItem_void_object(details, 1)
845
saved_file_size = PyTuple_GetItem_void_object(details, 2)
846
saved_executable = PyTuple_GetItem_void_object(details, 3)
847
saved_packed_stat = PyTuple_GetItem_void_object(details, 4)
848
# Deal with pyrex decrefing the objects
849
Py_INCREF(saved_link_or_sha1)
850
Py_INCREF(saved_file_size)
851
Py_INCREF(saved_executable)
852
Py_INCREF(saved_packed_stat)
853
#(saved_minikind, saved_link_or_sha1, saved_file_size,
854
# saved_executable, saved_packed_stat) = entry[1][0]
856
if (minikind == saved_minikind
857
and packed_stat == saved_packed_stat):
858
# The stat hasn't changed since we saved, so we can re-use the
863
# size should also be in packed_stat
864
if saved_file_size == stat_value.st_size:
865
return saved_link_or_sha1
867
# If we have gotten this far, that means that we need to actually
868
# process this entry.
871
executable = self._is_executable(stat_value.st_mode,
873
if self._cutoff_time is None:
874
self._sha_cutoff_time()
875
if (stat_value.st_mtime < self._cutoff_time
876
and stat_value.st_ctime < self._cutoff_time
877
and len(entry[1]) > 1
878
and entry[1][1][0] != 'a'):
879
# Could check for size changes for further optimised
880
# avoidance of sha1's. However the most prominent case of
881
# over-shaing is during initial add, which this catches.
882
link_or_sha1 = self._sha1_file(abspath)
883
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
884
executable, packed_stat)
886
entry[1][0] = ('f', '', stat_value.st_size,
887
executable, DirState.NULLSTAT)
888
elif minikind == c'd':
890
entry[1][0] = ('d', '', 0, False, packed_stat)
891
if saved_minikind != c'd':
892
# This changed from something into a directory. Make sure we
893
# have a directory block for it. This doesn't happen very
894
# often, so this doesn't have to be super fast.
895
block_index, entry_index, dir_present, file_present = \
896
self._get_block_entry_index(entry[0][0], entry[0][1], 0)
897
self._ensure_block(block_index, entry_index,
898
pathjoin(entry[0][0], entry[0][1]))
899
elif minikind == c'l':
900
link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
901
if self._cutoff_time is None:
902
self._sha_cutoff_time()
903
if (stat_value.st_mtime < self._cutoff_time
904
and stat_value.st_ctime < self._cutoff_time):
905
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
908
entry[1][0] = ('l', '', stat_value.st_size,
909
False, DirState.NULLSTAT)
910
self._dirblock_state = DirState.IN_MEMORY_MODIFIED
914
cdef char _minikind_from_string(object string):
915
"""Convert a python string to a char."""
916
return PyString_AsString(string)[0]
919
cdef object _kind_absent
920
cdef object _kind_file
921
cdef object _kind_directory
922
cdef object _kind_symlink
923
cdef object _kind_relocated
924
cdef object _kind_tree_reference
925
_kind_absent = "absent"
927
_kind_directory = "directory"
928
_kind_symlink = "symlink"
929
_kind_relocated = "relocated"
930
_kind_tree_reference = "tree-reference"
933
cdef object _minikind_to_kind(char minikind):
934
"""Create a string kind for minikind."""
935
cdef char _minikind[1]
938
elif minikind == c'd':
939
return _kind_directory
940
elif minikind == c'a':
942
elif minikind == c'r':
943
return _kind_relocated
944
elif minikind == c'l':
946
elif minikind == c't':
947
return _kind_tree_reference
948
_minikind[0] = minikind
949
raise KeyError(PyString_FromStringAndSize(_minikind, 1))
952
cdef int _versioned_minikind(char minikind):
953
"""Return non-zero if minikind is in fltd"""
954
return (minikind == c'f' or
960
cdef class ProcessEntryC:
962
cdef object old_dirname_to_file_id # dict
963
cdef object new_dirname_to_file_id # dict
964
cdef readonly object uninteresting
965
cdef object last_source_parent
966
cdef object last_target_parent
967
cdef object include_unchanged
968
cdef object use_filesystem_for_exec
969
cdef object utf8_decode
970
cdef readonly object searched_specific_files
971
cdef object search_specific_files
973
# Current iteration variables:
974
cdef object current_root
975
cdef object current_root_unicode
976
cdef object root_entries
977
cdef int root_entries_pos, root_entries_len
978
cdef object root_abspath
979
cdef int source_index, target_index
980
cdef int want_unversioned
982
cdef object dir_iterator
984
cdef object current_block
985
cdef int current_block_pos
986
cdef object current_block_list
987
cdef object current_dir_info
988
cdef object current_dir_list
990
cdef object root_dir_info
991
cdef object bisect_left
996
def __init__(self, include_unchanged, use_filesystem_for_exec,
997
search_specific_files, state, source_index, target_index,
998
want_unversioned, tree):
999
self.old_dirname_to_file_id = {}
1000
self.new_dirname_to_file_id = {}
1001
# Just a sentry, so that _process_entry can say that this
1002
# record is handled, but isn't interesting to process (unchanged)
1003
self.uninteresting = object()
1004
# Using a list so that we can access the values and change them in
1005
# nested scope. Each one is [path, file_id, entry]
1006
self.last_source_parent = [None, None]
1007
self.last_target_parent = [None, None]
1008
self.include_unchanged = include_unchanged
1009
self.use_filesystem_for_exec = use_filesystem_for_exec
1010
self.utf8_decode = cache_utf8._utf8_decode
1011
# for all search_indexs in each path at or under each element of
1012
# search_specific_files, if the detail is relocated: add the id, and add the
1013
# relocated path as one to search if its not searched already. If the
1014
# detail is not relocated, add the id.
1015
self.searched_specific_files = set()
1016
self.search_specific_files = search_specific_files
1018
self.current_root = None
1019
self.current_root_unicode = None
1020
self.root_entries = None
1021
self.root_entries_pos = 0
1022
self.root_entries_len = 0
1023
self.root_abspath = None
1024
if source_index is None:
1025
self.source_index = -1
1027
self.source_index = source_index
1028
self.target_index = target_index
1029
self.want_unversioned = want_unversioned
1031
self.dir_iterator = None
1032
self.block_index = -1
1033
self.current_block = None
1034
self.current_block_list = None
1035
self.current_block_pos = -1
1036
self.current_dir_info = None
1037
self.current_dir_list = None
1039
self.root_dir_info = None
1040
self.bisect_left = bisect.bisect_left
1041
self.pathjoin = osutils.pathjoin
1042
self.fstat = os.fstat
1043
self.sha_file = osutils.sha_file
1045
cdef _process_entry(self, entry, path_info):
1046
"""Compare an entry and real disk to generate delta information.
1048
:param path_info: top_relpath, basename, kind, lstat, abspath for
1049
the path of entry. If None, then the path is considered absent.
1050
(Perhaps we should pass in a concrete entry for this ?)
1051
Basename is returned as a utf8 string because we expect this
1052
tuple will be ignored, and don't want to take the time to
1054
:return: None if the these don't match
1055
A tuple of information about the change, or
1056
the object 'uninteresting' if these match, but are
1057
basically identical.
1059
cdef char target_minikind
1060
cdef char source_minikind
1062
cdef int content_change
1063
cdef object details_list
1065
details_list = entry[1]
1066
if -1 == self.source_index:
1067
source_details = DirState.NULL_PARENT_DETAILS
1069
source_details = details_list[self.source_index]
1070
target_details = details_list[self.target_index]
1071
target_minikind = _minikind_from_string(target_details[0])
1072
if path_info is not None and _versioned_minikind(target_minikind):
1073
if self.target_index != 0:
1074
raise AssertionError("Unsupported target index %d" %
1076
link_or_sha1 = _update_entry(self.state, entry, path_info[4], path_info[3])
1077
# The entry may have been modified by update_entry
1078
target_details = details_list[self.target_index]
1079
target_minikind = _minikind_from_string(target_details[0])
1082
# the rest of this function is 0.3 seconds on 50K paths, or
1083
# 0.000006 seconds per call.
1084
source_minikind = _minikind_from_string(source_details[0])
1085
if ((_versioned_minikind(source_minikind) or source_minikind == c'r')
1086
and _versioned_minikind(target_minikind)):
1087
# claimed content in both: diff
1088
# r | fdlt | | add source to search, add id path move and perform
1089
# | | | diff check on source-target
1090
# r | fdlt | a | dangling file that was present in the basis.
1092
if source_minikind != c'r':
1093
old_dirname = entry[0][0]
1094
old_basename = entry[0][1]
1095
old_path = path = None
1097
# add the source to the search path to find any children it
1098
# has. TODO ? : only add if it is a container ?
1099
if not osutils.is_inside_any(self.searched_specific_files,
1101
self.search_specific_files.add(source_details[1])
1102
# generate the old path; this is needed for stating later
1104
old_path = source_details[1]
1105
old_dirname, old_basename = os.path.split(old_path)
1106
path = self.pathjoin(entry[0][0], entry[0][1])
1107
old_entry = self.state._get_entry(self.source_index,
1109
# update the source details variable to be the real
1111
if old_entry == (None, None):
1112
raise errors.CorruptDirstate(self.state._filename,
1113
"entry '%s/%s' is considered renamed from %r"
1114
" but source does not exist\n"
1115
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
1116
source_details = old_entry[1][self.source_index]
1117
source_minikind = _minikind_from_string(source_details[0])
1118
if path_info is None:
1119
# the file is missing on disk, show as removed.
1124
# source and target are both versioned and disk file is present.
1125
target_kind = path_info[2]
1126
if target_kind == 'directory':
1128
old_path = path = self.pathjoin(old_dirname, old_basename)
1129
file_id = entry[0][2]
1130
self.new_dirname_to_file_id[path] = file_id
1131
if source_minikind != c'd':
1134
# directories have no fingerprint
1137
elif target_kind == 'file':
1138
if source_minikind != c'f':
1141
# If the size is the same, check the sha:
1142
if target_details[2] == source_details[2]:
1143
if link_or_sha1 is None:
1145
file_obj = file(path_info[4], 'rb')
1147
# XXX: TODO: Use lower level file IO rather
1148
# than python objects for sha-misses.
1149
statvalue = self.fstat(file_obj.fileno())
1150
link_or_sha1 = self.sha_file(file_obj)
1153
self.state._observed_sha1(entry, link_or_sha1,
1155
content_change = (link_or_sha1 != source_details[1])
1157
# Size changed, so must be different
1159
# Target details is updated at update_entry time
1160
if self.use_filesystem_for_exec:
1161
# We don't need S_ISREG here, because we are sure
1162
# we are dealing with a file.
1163
target_exec = bool(S_IXUSR & path_info[3].st_mode)
1165
target_exec = target_details[3]
1166
elif target_kind == 'symlink':
1167
if source_minikind != c'l':
1170
content_change = (link_or_sha1 != source_details[1])
1172
elif target_kind == 'tree-reference':
1173
if source_minikind != c't':
1179
raise Exception, "unknown kind %s" % path_info[2]
1180
if source_minikind == c'd':
1182
old_path = path = self.pathjoin(old_dirname, old_basename)
1184
file_id = entry[0][2]
1185
self.old_dirname_to_file_id[old_path] = file_id
1186
# parent id is the entry for the path in the target tree
1187
if old_dirname == self.last_source_parent[0]:
1188
source_parent_id = self.last_source_parent[1]
1191
source_parent_id = self.old_dirname_to_file_id[old_dirname]
1193
source_parent_entry = self.state._get_entry(self.source_index,
1194
path_utf8=old_dirname)
1195
source_parent_id = source_parent_entry[0][2]
1196
if source_parent_id == entry[0][2]:
1197
# This is the root, so the parent is None
1198
source_parent_id = None
1200
self.last_source_parent[0] = old_dirname
1201
self.last_source_parent[1] = source_parent_id
1202
new_dirname = entry[0][0]
1203
if new_dirname == self.last_target_parent[0]:
1204
target_parent_id = self.last_target_parent[1]
1207
target_parent_id = self.new_dirname_to_file_id[new_dirname]
1209
# TODO: We don't always need to do the lookup, because the
1210
# parent entry will be the same as the source entry.
1211
target_parent_entry = self.state._get_entry(self.target_index,
1212
path_utf8=new_dirname)
1213
if target_parent_entry == (None, None):
1214
raise AssertionError(
1215
"Could not find target parent in wt: %s\nparent of: %s"
1216
% (new_dirname, entry))
1217
target_parent_id = target_parent_entry[0][2]
1218
if target_parent_id == entry[0][2]:
1219
# This is the root, so the parent is None
1220
target_parent_id = None
1222
self.last_target_parent[0] = new_dirname
1223
self.last_target_parent[1] = target_parent_id
1225
source_exec = source_details[3]
1226
if (self.include_unchanged
1228
or source_parent_id != target_parent_id
1229
or old_basename != entry[0][1]
1230
or source_exec != target_exec
1232
if old_path is None:
1233
path = self.pathjoin(old_dirname, old_basename)
1235
old_path_u = self.utf8_decode(old_path)[0]
1238
old_path_u = self.utf8_decode(old_path)[0]
1239
if old_path == path:
1242
path_u = self.utf8_decode(path)[0]
1243
source_kind = _minikind_to_kind(source_minikind)
1244
return (entry[0][2],
1245
(old_path_u, path_u),
1248
(source_parent_id, target_parent_id),
1249
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
1250
(source_kind, target_kind),
1251
(source_exec, target_exec))
1253
return self.uninteresting
1254
elif source_minikind == c'a' and _versioned_minikind(target_minikind):
1255
# looks like a new file
1256
path = self.pathjoin(entry[0][0], entry[0][1])
1257
# parent id is the entry for the path in the target tree
1258
# TODO: these are the same for an entire directory: cache em.
1259
parent_id = self.state._get_entry(self.target_index,
1260
path_utf8=entry[0][0])[0][2]
1261
if parent_id == entry[0][2]:
1263
if path_info is not None:
1265
if self.use_filesystem_for_exec:
1266
# We need S_ISREG here, because we aren't sure if this
1269
S_ISREG(path_info[3].st_mode)
1270
and S_IXUSR & path_info[3].st_mode)
1272
target_exec = target_details[3]
1273
return (entry[0][2],
1274
(None, self.utf8_decode(path)[0]),
1278
(None, self.utf8_decode(entry[0][1])[0]),
1279
(None, path_info[2]),
1280
(None, target_exec))
1282
# Its a missing file, report it as such.
1283
return (entry[0][2],
1284
(None, self.utf8_decode(path)[0]),
1288
(None, self.utf8_decode(entry[0][1])[0]),
1291
elif _versioned_minikind(source_minikind) and target_minikind == c'a':
1292
# unversioned, possibly, or possibly not deleted: we dont care.
1293
# if its still on disk, *and* theres no other entry at this
1294
# path [we dont know this in this routine at the moment -
1295
# perhaps we should change this - then it would be an unknown.
1296
old_path = self.pathjoin(entry[0][0], entry[0][1])
1297
# parent id is the entry for the path in the target tree
1298
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
1299
if parent_id == entry[0][2]:
1301
return (entry[0][2],
1302
(self.utf8_decode(old_path)[0], None),
1306
(self.utf8_decode(entry[0][1])[0], None),
1307
(_minikind_to_kind(source_minikind), None),
1308
(source_details[3], None))
1309
elif _versioned_minikind(source_minikind) and target_minikind == c'r':
1310
# a rename; could be a true rename, or a rename inherited from
1311
# a renamed parent. TODO: handle this efficiently. Its not
1312
# common case to rename dirs though, so a correct but slow
1313
# implementation will do.
1314
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
1315
self.search_specific_files.add(target_details[1])
1316
elif ((source_minikind == c'r' or source_minikind == c'a') and
1317
(target_minikind == c'r' or target_minikind == c'a')):
1318
# neither of the selected trees contain this path,
1319
# so skip over it. This is not currently directly tested, but
1320
# is indirectly via test_too_much.TestCommands.test_conflicts.
1323
raise AssertionError("don't know how to compare "
1324
"source_minikind=%r, target_minikind=%r"
1325
% (source_minikind, target_minikind))
1326
## import pdb;pdb.set_trace()
1332
def iter_changes(self):
1335
cdef void _update_current_block(self):
1336
if (self.block_index < len(self.state._dirblocks) and
1337
osutils.is_inside(self.current_root, self.state._dirblocks[self.block_index][0])):
1338
self.current_block = self.state._dirblocks[self.block_index]
1339
self.current_block_list = self.current_block[1]
1340
self.current_block_pos = 0
1342
self.current_block = None
1343
self.current_block_list = None
1346
# Simple thunk to allow tail recursion without pyrex confusion
1347
return self._iter_next()
1349
cdef _iter_next(self):
1350
"""Iterate over the changes."""
1351
# This function single steps through an iterator. As such while loops
1352
# are often exited by 'return' - the code is structured so that the
1353
# next call into the function will return to the same while loop. Note
1354
# that all flow control needed to re-reach that step is reexecuted,
1355
# which can be a performance problem. It has not yet been tuned to
1356
# minimise this; a state machine is probably the simplest restructuring
1357
# to both minimise this overhead and make the code considerably more
1361
# compare source_index and target_index at or under each element of search_specific_files.
1362
# follow the following comparison table. Note that we only want to do diff operations when
1363
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1367
# Source | Target | disk | action
1368
# r | fdlt | | add source to search, add id path move and perform
1369
# | | | diff check on source-target
1370
# r | fdlt | a | dangling file that was present in the basis.
1372
# r | a | | add source to search
1374
# r | r | | this path is present in a non-examined tree, skip.
1375
# r | r | a | this path is present in a non-examined tree, skip.
1376
# a | fdlt | | add new id
1377
# a | fdlt | a | dangling locally added file, skip
1378
# a | a | | not present in either tree, skip
1379
# a | a | a | not present in any tree, skip
1380
# a | r | | not present in either tree at this path, skip as it
1381
# | | | may not be selected by the users list of paths.
1382
# a | r | a | not present in either tree at this path, skip as it
1383
# | | | may not be selected by the users list of paths.
1384
# fdlt | fdlt | | content in both: diff them
1385
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1386
# fdlt | a | | unversioned: output deleted id for now
1387
# fdlt | a | a | unversioned and deleted: output deleted id
1388
# fdlt | r | | relocated in this tree, so add target to search.
1389
# | | | Dont diff, we will see an r,fd; pair when we reach
1390
# | | | this id at the other path.
1391
# fdlt | r | a | 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.
1395
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1396
# keeping a cache of directories that we have seen.
1397
cdef object current_dirname, current_blockname
1398
cdef char * current_dirname_c, * current_blockname_c
1399
cdef int advance_entry, advance_path
1400
cdef int path_handled
1401
uninteresting = self.uninteresting
1402
searched_specific_files = self.searched_specific_files
1403
# Are we walking a root?
1404
while self.root_entries_pos < self.root_entries_len:
1405
entry = self.root_entries[self.root_entries_pos]
1406
self.root_entries_pos = self.root_entries_pos + 1
1407
result = self._process_entry(entry, self.root_dir_info)
1408
if result is not None and result is not self.uninteresting:
1410
# Have we finished the prior root, or never started one ?
1411
if self.current_root is None:
1412
# TODO: the pending list should be lexically sorted? the
1413
# interface doesn't require it.
1415
self.current_root = self.search_specific_files.pop()
1417
raise StopIteration()
1418
self.current_root_unicode = self.current_root.decode('utf8')
1419
self.searched_specific_files.add(self.current_root)
1420
# process the entries for this containing directory: the rest will be
1421
# found by their parents recursively.
1422
self.root_entries = self.state._entries_for_path(self.current_root)
1423
self.root_entries_len = len(self.root_entries)
1424
self.root_abspath = self.tree.abspath(self.current_root_unicode)
1426
root_stat = os.lstat(self.root_abspath)
1428
if e.errno == errno.ENOENT:
1429
# the path does not exist: let _process_entry know that.
1430
self.root_dir_info = None
1432
# some other random error: hand it up.
1435
self.root_dir_info = ('', self.current_root,
1436
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
1438
if self.root_dir_info[2] == 'directory':
1439
if self.tree._directory_is_tree_reference(
1440
self.current_root_unicode):
1441
self.root_dir_info = self.root_dir_info[:2] + \
1442
('tree-reference',) + self.root_dir_info[3:]
1443
if not self.root_entries and not self.root_dir_info:
1444
# this specified path is not present at all, skip it.
1445
# (tail recursion, can do a loop once the full structure is
1447
return self._iter_next()
1449
self.root_entries_pos = 0
1450
# XXX Clarity: This loop is duplicated a out the self.current_root
1451
# is None guard above: if we return from it, it completes there
1452
# (and the following if block cannot trigger because
1453
# path_handled must be true, so the if block is not # duplicated.
1454
while self.root_entries_pos < self.root_entries_len:
1455
entry = self.root_entries[self.root_entries_pos]
1456
self.root_entries_pos = self.root_entries_pos + 1
1457
result = self._process_entry(entry, self.root_dir_info)
1458
if result is not None:
1460
if result is not self.uninteresting:
1462
# handle unversioned specified paths:
1463
if self.want_unversioned and not path_handled and self.root_dir_info:
1464
new_executable = bool(
1465
stat.S_ISREG(self.root_dir_info[3].st_mode)
1466
and stat.S_IEXEC & self.root_dir_info[3].st_mode)
1468
(None, self.current_root_unicode),
1472
(None, splitpath(self.current_root_unicode)[-1]),
1473
(None, self.root_dir_info[2]),
1474
(None, new_executable)
1476
# If we reach here, the outer flow continues, which enters into the
1477
# per-root setup logic.
1478
if self.current_dir_info is None and self.current_block is None:
1479
# setup iteration of this root:
1480
self.current_dir_list = None
1481
if self.root_dir_info and self.root_dir_info[2] == 'tree-reference':
1482
self.current_dir_info = None
1484
self.dir_iterator = osutils._walkdirs_utf8(self.root_abspath,
1485
prefix=self.current_root)
1488
self.current_dir_info = self.dir_iterator.next()
1489
self.current_dir_list = self.current_dir_info[1]
1491
# there may be directories in the inventory even though
1492
# this path is not a file on disk: so mark it as end of
1494
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
1495
self.current_dir_info = None
1496
elif sys.platform == 'win32':
1497
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
1498
# python 2.5 has e.errno == EINVAL,
1499
# and e.winerror == ERROR_DIRECTORY
1501
e_winerror = e.winerror
1502
except AttributeError:
1504
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
1505
if (e.errno in win_errors or e_winerror in win_errors):
1506
self.current_dir_info = None
1508
# Will this really raise the right exception ?
1513
if self.current_dir_info[0][0] == '':
1514
# remove .bzr from iteration
1515
bzr_index = self.bisect_left(self.current_dir_list, ('.bzr',))
1516
if self.current_dir_list[bzr_index][0] != '.bzr':
1517
raise AssertionError()
1518
del self.current_dir_list[bzr_index]
1519
initial_key = (self.current_root, '', '')
1520
self.block_index, _ = self.state._find_block_index_from_key(initial_key)
1521
if self.block_index == 0:
1522
# we have processed the total root already, but because the
1523
# initial key matched it we should skip it here.
1524
self.block_index = self.block_index + 1
1525
self._update_current_block()
1526
# walk until both the directory listing and the versioned metadata
1528
while (self.current_dir_info is not None
1529
or self.current_block is not None):
1530
# Uncommon case - a missing directory or an unversioned directory:
1531
if (self.current_dir_info and self.current_block
1532
and self.current_dir_info[0][0] != self.current_block[0]):
1533
# Work around pyrex broken heuristic - current_dirname has
1534
# the same scope as current_dirname_c
1535
current_dirname = self.current_dir_info[0][0]
1536
current_dirname_c = PyString_AS_STRING_void(
1537
<void *>current_dirname)
1538
current_blockname = self.current_block[0]
1539
current_blockname_c = PyString_AS_STRING_void(
1540
<void *>current_blockname)
1541
# In the python generator we evaluate this if block once per
1542
# dir+block; because we reenter in the pyrex version its being
1543
# evaluated once per path: we could cache the result before
1544
# doing the while loop and probably save time.
1545
if _cmp_by_dirs(current_dirname_c,
1546
PyString_Size(current_dirname),
1547
current_blockname_c,
1548
PyString_Size(current_blockname)) < 0:
1549
# filesystem data refers to paths not covered by the
1550
# dirblock. this has two possibilities:
1551
# A) it is versioned but empty, so there is no block for it
1552
# B) it is not versioned.
1554
# if (A) then we need to recurse into it to check for
1555
# new unknown files or directories.
1556
# if (B) then we should ignore it, because we don't
1557
# recurse into unknown directories.
1558
# We are doing a loop
1559
while self.path_index < len(self.current_dir_list):
1560
current_path_info = self.current_dir_list[self.path_index]
1561
# dont descend into this unversioned path if it is
1563
if current_path_info[2] in ('directory',
1565
del self.current_dir_list[self.path_index]
1566
self.path_index = self.path_index - 1
1567
self.path_index = self.path_index + 1
1568
if self.want_unversioned:
1569
if current_path_info[2] == 'directory':
1570
if self.tree._directory_is_tree_reference(
1571
self.utf8_decode(current_path_info[0])[0]):
1572
current_path_info = current_path_info[:2] + \
1573
('tree-reference',) + current_path_info[3:]
1574
new_executable = bool(
1575
stat.S_ISREG(current_path_info[3].st_mode)
1576
and stat.S_IEXEC & current_path_info[3].st_mode)
1578
(None, self.utf8_decode(current_path_info[0])[0]),
1582
(None, self.utf8_decode(current_path_info[1])[0]),
1583
(None, current_path_info[2]),
1584
(None, new_executable))
1585
# This dir info has been handled, go to the next
1587
self.current_dir_list = None
1589
self.current_dir_info = self.dir_iterator.next()
1590
self.current_dir_list = self.current_dir_info[1]
1591
except StopIteration:
1592
self.current_dir_info = None
1594
# We have a dirblock entry for this location, but there
1595
# is no filesystem path for this. This is most likely
1596
# because a directory was removed from the disk.
1597
# We don't have to report the missing directory,
1598
# because that should have already been handled, but we
1599
# need to handle all of the files that are contained
1601
while self.current_block_pos < len(self.current_block_list):
1602
current_entry = self.current_block_list[self.current_block_pos]
1603
self.current_block_pos = self.current_block_pos + 1
1604
# entry referring to file not present on disk.
1605
# advance the entry only, after processing.
1606
result = self._process_entry(current_entry, None)
1607
if result is not None:
1608
if result is not self.uninteresting:
1610
self.block_index = self.block_index + 1
1611
self._update_current_block()
1612
continue # next loop-on-block/dir
1613
result = self._loop_one_block()
1614
if result is not None:
1616
if len(self.search_specific_files):
1617
# More supplied paths to process
1618
self.current_root = None
1619
return self._iter_next()
1620
raise StopIteration()
1622
cdef object _maybe_tree_ref(self, current_path_info):
1623
if self.tree._directory_is_tree_reference(
1624
self.utf8_decode(current_path_info[0])[0]):
1625
return current_path_info[:2] + \
1626
('tree-reference',) + current_path_info[3:]
1628
return current_path_info
1630
cdef object _loop_one_block(self):
1631
# current_dir_info and current_block refer to the same directory -
1632
# this is the common case code.
1633
# Assign local variables for current path and entry:
1634
cdef object current_entry
1635
cdef object current_path_info
1636
cdef int path_handled
1639
# cdef char * temp_str
1640
# cdef Py_ssize_t temp_str_length
1641
# PyString_AsStringAndSize(disk_kind, &temp_str, &temp_str_length)
1642
# if not strncmp(temp_str, "directory", temp_str_length):
1643
if (self.current_block is not None and
1644
self.current_block_pos < PyList_GET_SIZE(self.current_block_list)):
1645
current_entry = PyList_GET_ITEM(self.current_block_list,
1646
self.current_block_pos)
1648
Py_INCREF(current_entry)
1650
current_entry = None
1651
if (self.current_dir_info is not None and
1652
self.path_index < PyList_GET_SIZE(self.current_dir_list)):
1653
current_path_info = PyList_GET_ITEM(self.current_dir_list,
1656
Py_INCREF(current_path_info)
1657
disk_kind = PyTuple_GET_ITEM(current_path_info, 2)
1659
Py_INCREF(disk_kind)
1660
if disk_kind == "directory":
1661
current_path_info = self._maybe_tree_ref(current_path_info)
1663
current_path_info = None
1664
while (current_entry is not None or current_path_info is not None):
1669
if current_entry is None:
1670
# unversioned - the check for path_handled when the path
1671
# is advanced will yield this path if needed.
1673
elif current_path_info is None:
1674
# no path is fine: the per entry code will handle it.
1675
result = self._process_entry(current_entry, current_path_info)
1676
if result is not None:
1677
if result is self.uninteresting:
1680
minikind = _minikind_from_string(
1681
current_entry[1][self.target_index][0])
1682
cmp_result = cmp(current_path_info[1], current_entry[0][1])
1683
if (cmp_result or minikind == c'a' or minikind == c'r'):
1684
# The current path on disk doesn't match the dirblock
1685
# record. Either the dirblock record is marked as
1686
# absent/renamed, or the file on disk is not present at all
1687
# in the dirblock. Either way, report about the dirblock
1688
# entry, and let other code handle the filesystem one.
1690
# Compare the basename for these files to determine
1693
# extra file on disk: pass for now, but only
1694
# increment the path, not the entry
1697
# entry referring to file not present on disk.
1698
# advance the entry only, after processing.
1699
result = self._process_entry(current_entry, None)
1700
if result is not None:
1701
if result is self.uninteresting:
1705
# paths are the same,and the dirstate entry is not
1706
# absent or renamed.
1707
result = self._process_entry(current_entry, current_path_info)
1708
if result is not None:
1710
if result is self.uninteresting:
1712
# >- loop control starts here:
1714
if advance_entry and current_entry is not None:
1715
self.current_block_pos = self.current_block_pos + 1
1716
if self.current_block_pos < PyList_GET_SIZE(self.current_block_list):
1717
current_entry = self.current_block_list[self.current_block_pos]
1719
current_entry = None
1721
if advance_path and current_path_info is not None:
1722
if not path_handled:
1723
# unversioned in all regards
1724
if self.want_unversioned:
1725
new_executable = bool(
1726
stat.S_ISREG(current_path_info[3].st_mode)
1727
and stat.S_IEXEC & current_path_info[3].st_mode)
1729
relpath_unicode = self.utf8_decode(current_path_info[0])[0]
1730
except UnicodeDecodeError:
1731
raise errors.BadFilenameEncoding(
1732
current_path_info[0], osutils._fs_enc)
1733
if result is not None:
1734
raise AssertionError(
1735
"result is not None: %r" % result)
1737
(None, relpath_unicode),
1741
(None, self.utf8_decode(current_path_info[1])[0]),
1742
(None, current_path_info[2]),
1743
(None, new_executable))
1744
# dont descend into this unversioned path if it is
1746
if current_path_info[2] in ('directory'):
1747
del self.current_dir_list[self.path_index]
1748
self.path_index = self.path_index - 1
1749
# dont descend the disk iterator into any tree
1751
if current_path_info[2] == 'tree-reference':
1752
del self.current_dir_list[self.path_index]
1753
self.path_index = self.path_index - 1
1754
self.path_index = self.path_index + 1
1755
if self.path_index < len(self.current_dir_list):
1756
current_path_info = self.current_dir_list[self.path_index]
1757
if current_path_info[2] == 'directory':
1758
current_path_info = self._maybe_tree_ref(
1761
current_path_info = None
1762
if result is not None:
1763
# Found a result on this pass, yield it
1765
if self.current_block is not None:
1766
self.block_index = self.block_index + 1
1767
self._update_current_block()
1768
if self.current_dir_info is not None:
1770
self.current_dir_list = None
1772
self.current_dir_info = self.dir_iterator.next()
1773
self.current_dir_list = self.current_dir_info[1]
1774
except StopIteration:
1775
self.current_dir_info = None