2774
3074
raise errors.ObjectNotLocked(self)
3077
def py_update_entry(state, entry, abspath, stat_value,
3078
_stat_to_minikind=DirState._stat_to_minikind,
3079
_pack_stat=pack_stat):
3080
"""Update the entry based on what is actually on disk.
3082
This function only calculates the sha if it needs to - if the entry is
3083
uncachable, or clearly different to the first parent's entry, no sha
3084
is calculated, and None is returned.
3086
:param state: The dirstate this entry is in.
3087
:param entry: This is the dirblock entry for the file in question.
3088
:param abspath: The path on disk for this file.
3089
:param stat_value: The stat value done on the path.
3090
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
3091
target of a symlink.
3094
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
3098
packed_stat = _pack_stat(stat_value)
3099
(saved_minikind, saved_link_or_sha1, saved_file_size,
3100
saved_executable, saved_packed_stat) = entry[1][0]
3102
if minikind == 'd' and saved_minikind == 't':
3104
if (minikind == saved_minikind
3105
and packed_stat == saved_packed_stat):
3106
# The stat hasn't changed since we saved, so we can re-use the
3111
# size should also be in packed_stat
3112
if saved_file_size == stat_value.st_size:
3113
return saved_link_or_sha1
3115
# If we have gotten this far, that means that we need to actually
3116
# process this entry.
3119
executable = state._is_executable(stat_value.st_mode,
3121
if state._cutoff_time is None:
3122
state._sha_cutoff_time()
3123
if (stat_value.st_mtime < state._cutoff_time
3124
and stat_value.st_ctime < state._cutoff_time
3125
and len(entry[1]) > 1
3126
and entry[1][1][0] != 'a'):
3127
# Could check for size changes for further optimised
3128
# avoidance of sha1's. However the most prominent case of
3129
# over-shaing is during initial add, which this catches.
3130
# Besides, if content filtering happens, size and sha
3131
# are calculated at the same time, so checking just the size
3132
# gains nothing w.r.t. performance.
3133
link_or_sha1 = state._sha1_file(abspath)
3134
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
3135
executable, packed_stat)
3137
entry[1][0] = ('f', '', stat_value.st_size,
3138
executable, DirState.NULLSTAT)
3139
elif minikind == 'd':
3141
entry[1][0] = ('d', '', 0, False, packed_stat)
3142
if saved_minikind != 'd':
3143
# This changed from something into a directory. Make sure we
3144
# have a directory block for it. This doesn't happen very
3145
# often, so this doesn't have to be super fast.
3146
block_index, entry_index, dir_present, file_present = \
3147
state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3148
state._ensure_block(block_index, entry_index,
3149
osutils.pathjoin(entry[0][0], entry[0][1]))
3150
elif minikind == 'l':
3151
link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3152
if state._cutoff_time is None:
3153
state._sha_cutoff_time()
3154
if (stat_value.st_mtime < state._cutoff_time
3155
and stat_value.st_ctime < state._cutoff_time):
3156
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
3159
entry[1][0] = ('l', '', stat_value.st_size,
3160
False, DirState.NULLSTAT)
3161
state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3165
class ProcessEntryPython(object):
3167
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3168
"last_source_parent", "last_target_parent", "include_unchanged",
3169
"partial", "use_filesystem_for_exec", "utf8_decode",
3170
"searched_specific_files", "search_specific_files",
3171
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3172
"state", "source_index", "target_index", "want_unversioned", "tree"]
3174
def __init__(self, include_unchanged, use_filesystem_for_exec,
3175
search_specific_files, state, source_index, target_index,
3176
want_unversioned, tree):
3177
self.old_dirname_to_file_id = {}
3178
self.new_dirname_to_file_id = {}
3179
# Are we doing a partial iter_changes?
3180
self.partial = search_specific_files != set([''])
3181
# Using a list so that we can access the values and change them in
3182
# nested scope. Each one is [path, file_id, entry]
3183
self.last_source_parent = [None, None]
3184
self.last_target_parent = [None, None]
3185
self.include_unchanged = include_unchanged
3186
self.use_filesystem_for_exec = use_filesystem_for_exec
3187
self.utf8_decode = cache_utf8._utf8_decode
3188
# for all search_indexs in each path at or under each element of
3189
# search_specific_files, if the detail is relocated: add the id, and
3190
# add the relocated path as one to search if its not searched already.
3191
# If the detail is not relocated, add the id.
3192
self.searched_specific_files = set()
3193
# When we search exact paths without expanding downwards, we record
3195
self.searched_exact_paths = set()
3196
self.search_specific_files = search_specific_files
3197
# The parents up to the root of the paths we are searching.
3198
# After all normal paths are returned, these specific items are returned.
3199
self.search_specific_file_parents = set()
3200
# The ids we've sent out in the delta.
3201
self.seen_ids = set()
3203
self.source_index = source_index
3204
self.target_index = target_index
3205
if target_index != 0:
3206
# A lot of code in here depends on target_index == 0
3207
raise errors.BzrError('unsupported target index')
3208
self.want_unversioned = want_unversioned
3211
def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
3212
"""Compare an entry and real disk to generate delta information.
3214
:param path_info: top_relpath, basename, kind, lstat, abspath for
3215
the path of entry. If None, then the path is considered absent in
3216
the target (Perhaps we should pass in a concrete entry for this ?)
3217
Basename is returned as a utf8 string because we expect this
3218
tuple will be ignored, and don't want to take the time to
3220
:return: (iter_changes_result, changed). If the entry has not been
3221
handled then changed is None. Otherwise it is False if no content
3222
or metadata changes have occurred, and True if any content or
3223
metadata change has occurred. If self.include_unchanged is True then
3224
if changed is not None, iter_changes_result will always be a result
3225
tuple. Otherwise, iter_changes_result is None unless changed is
3228
if self.source_index is None:
3229
source_details = DirState.NULL_PARENT_DETAILS
3231
source_details = entry[1][self.source_index]
3232
target_details = entry[1][self.target_index]
3233
target_minikind = target_details[0]
3234
if path_info is not None and target_minikind in 'fdlt':
3235
if not (self.target_index == 0):
3236
raise AssertionError()
3237
link_or_sha1 = update_entry(self.state, entry,
3238
abspath=path_info[4], stat_value=path_info[3])
3239
# The entry may have been modified by update_entry
3240
target_details = entry[1][self.target_index]
3241
target_minikind = target_details[0]
3244
file_id = entry[0][2]
3245
source_minikind = source_details[0]
3246
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
3247
# claimed content in both: diff
3248
# r | fdlt | | add source to search, add id path move and perform
3249
# | | | diff check on source-target
3250
# r | fdlt | a | dangling file that was present in the basis.
3252
if source_minikind in 'r':
3253
# add the source to the search path to find any children it
3254
# has. TODO ? : only add if it is a container ?
3255
if not osutils.is_inside_any(self.searched_specific_files,
3257
self.search_specific_files.add(source_details[1])
3258
# generate the old path; this is needed for stating later
3260
old_path = source_details[1]
3261
old_dirname, old_basename = os.path.split(old_path)
3262
path = pathjoin(entry[0][0], entry[0][1])
3263
old_entry = self.state._get_entry(self.source_index,
3265
# update the source details variable to be the real
3267
if old_entry == (None, None):
3268
raise errors.CorruptDirstate(self.state._filename,
3269
"entry '%s/%s' is considered renamed from %r"
3270
" but source does not exist\n"
3271
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3272
source_details = old_entry[1][self.source_index]
3273
source_minikind = source_details[0]
3275
old_dirname = entry[0][0]
3276
old_basename = entry[0][1]
3277
old_path = path = None
3278
if path_info is None:
3279
# the file is missing on disk, show as removed.
3280
content_change = True
3284
# source and target are both versioned and disk file is present.
3285
target_kind = path_info[2]
3286
if target_kind == 'directory':
3288
old_path = path = pathjoin(old_dirname, old_basename)
3289
self.new_dirname_to_file_id[path] = file_id
3290
if source_minikind != 'd':
3291
content_change = True
3293
# directories have no fingerprint
3294
content_change = False
3296
elif target_kind == 'file':
3297
if source_minikind != 'f':
3298
content_change = True
3300
# Check the sha. We can't just rely on the size as
3301
# content filtering may mean differ sizes actually
3302
# map to the same content
3303
if link_or_sha1 is None:
3305
statvalue, link_or_sha1 = \
3306
self.state._sha1_provider.stat_and_sha1(
3308
self.state._observed_sha1(entry, link_or_sha1,
3310
content_change = (link_or_sha1 != source_details[1])
3311
# Target details is updated at update_entry time
3312
if self.use_filesystem_for_exec:
3313
# We don't need S_ISREG here, because we are sure
3314
# we are dealing with a file.
3315
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
3317
target_exec = target_details[3]
3318
elif target_kind == 'symlink':
3319
if source_minikind != 'l':
3320
content_change = True
3322
content_change = (link_or_sha1 != source_details[1])
3324
elif target_kind == 'tree-reference':
3325
if source_minikind != 't':
3326
content_change = True
3328
content_change = False
3331
raise Exception, "unknown kind %s" % path_info[2]
3332
if source_minikind == 'd':
3334
old_path = path = pathjoin(old_dirname, old_basename)
3335
self.old_dirname_to_file_id[old_path] = file_id
3336
# parent id is the entry for the path in the target tree
3337
if old_basename and old_dirname == self.last_source_parent[0]:
3338
source_parent_id = self.last_source_parent[1]
3341
source_parent_id = self.old_dirname_to_file_id[old_dirname]
3343
source_parent_entry = self.state._get_entry(self.source_index,
3344
path_utf8=old_dirname)
3345
source_parent_id = source_parent_entry[0][2]
3346
if source_parent_id == entry[0][2]:
3347
# This is the root, so the parent is None
3348
source_parent_id = None
3350
self.last_source_parent[0] = old_dirname
3351
self.last_source_parent[1] = source_parent_id
3352
new_dirname = entry[0][0]
3353
if entry[0][1] and new_dirname == self.last_target_parent[0]:
3354
target_parent_id = self.last_target_parent[1]
3357
target_parent_id = self.new_dirname_to_file_id[new_dirname]
3359
# TODO: We don't always need to do the lookup, because the
3360
# parent entry will be the same as the source entry.
3361
target_parent_entry = self.state._get_entry(self.target_index,
3362
path_utf8=new_dirname)
3363
if target_parent_entry == (None, None):
3364
raise AssertionError(
3365
"Could not find target parent in wt: %s\nparent of: %s"
3366
% (new_dirname, entry))
3367
target_parent_id = target_parent_entry[0][2]
3368
if target_parent_id == entry[0][2]:
3369
# This is the root, so the parent is None
3370
target_parent_id = None
3372
self.last_target_parent[0] = new_dirname
3373
self.last_target_parent[1] = target_parent_id
3375
source_exec = source_details[3]
3376
changed = (content_change
3377
or source_parent_id != target_parent_id
3378
or old_basename != entry[0][1]
3379
or source_exec != target_exec
3381
if not changed and not self.include_unchanged:
3384
if old_path is None:
3385
old_path = path = pathjoin(old_dirname, old_basename)
3386
old_path_u = self.utf8_decode(old_path)[0]
3389
old_path_u = self.utf8_decode(old_path)[0]
3390
if old_path == path:
3393
path_u = self.utf8_decode(path)[0]
3394
source_kind = DirState._minikind_to_kind[source_minikind]
3395
return (entry[0][2],
3396
(old_path_u, path_u),
3399
(source_parent_id, target_parent_id),
3400
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3401
(source_kind, target_kind),
3402
(source_exec, target_exec)), changed
3403
elif source_minikind in 'a' and target_minikind in 'fdlt':
3404
# looks like a new file
3405
path = pathjoin(entry[0][0], entry[0][1])
3406
# parent id is the entry for the path in the target tree
3407
# TODO: these are the same for an entire directory: cache em.
3408
parent_id = self.state._get_entry(self.target_index,
3409
path_utf8=entry[0][0])[0][2]
3410
if parent_id == entry[0][2]:
3412
if path_info is not None:
3414
if self.use_filesystem_for_exec:
3415
# We need S_ISREG here, because we aren't sure if this
3418
stat.S_ISREG(path_info[3].st_mode)
3419
and stat.S_IEXEC & path_info[3].st_mode)
3421
target_exec = target_details[3]
3422
return (entry[0][2],
3423
(None, self.utf8_decode(path)[0]),
3427
(None, self.utf8_decode(entry[0][1])[0]),
3428
(None, path_info[2]),
3429
(None, target_exec)), True
3431
# Its a missing file, report it as such.
3432
return (entry[0][2],
3433
(None, self.utf8_decode(path)[0]),
3437
(None, self.utf8_decode(entry[0][1])[0]),
3439
(None, False)), True
3440
elif source_minikind in 'fdlt' and target_minikind in 'a':
3441
# unversioned, possibly, or possibly not deleted: we dont care.
3442
# if its still on disk, *and* theres no other entry at this
3443
# path [we dont know this in this routine at the moment -
3444
# perhaps we should change this - then it would be an unknown.
3445
old_path = pathjoin(entry[0][0], entry[0][1])
3446
# parent id is the entry for the path in the target tree
3447
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
3448
if parent_id == entry[0][2]:
3450
return (entry[0][2],
3451
(self.utf8_decode(old_path)[0], None),
3455
(self.utf8_decode(entry[0][1])[0], None),
3456
(DirState._minikind_to_kind[source_minikind], None),
3457
(source_details[3], None)), True
3458
elif source_minikind in 'fdlt' and target_minikind in 'r':
3459
# a rename; could be a true rename, or a rename inherited from
3460
# a renamed parent. TODO: handle this efficiently. Its not
3461
# common case to rename dirs though, so a correct but slow
3462
# implementation will do.
3463
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3464
self.search_specific_files.add(target_details[1])
3465
elif source_minikind in 'ra' and target_minikind in 'ra':
3466
# neither of the selected trees contain this file,
3467
# so skip over it. This is not currently directly tested, but
3468
# is indirectly via test_too_much.TestCommands.test_conflicts.
3471
raise AssertionError("don't know how to compare "
3472
"source_minikind=%r, target_minikind=%r"
3473
% (source_minikind, target_minikind))
3474
## import pdb;pdb.set_trace()
3480
def _gather_result_for_consistency(self, result):
3481
"""Check a result we will yield to make sure we are consistent later.
3483
This gathers result's parents into a set to output later.
3485
:param result: A result tuple.
3487
if not self.partial or not result[0]:
3489
self.seen_ids.add(result[0])
3490
new_path = result[1][1]
3492
# Not the root and not a delete: queue up the parents of the path.
3493
self.search_specific_file_parents.update(
3494
osutils.parent_directories(new_path.encode('utf8')))
3495
# Add the root directory which parent_directories does not
3497
self.search_specific_file_parents.add('')
3499
def iter_changes(self):
3500
"""Iterate over the changes."""
3501
utf8_decode = cache_utf8._utf8_decode
3502
_cmp_by_dirs = cmp_by_dirs
3503
_process_entry = self._process_entry
3504
search_specific_files = self.search_specific_files
3505
searched_specific_files = self.searched_specific_files
3506
splitpath = osutils.splitpath
3508
# compare source_index and target_index at or under each element of search_specific_files.
3509
# follow the following comparison table. Note that we only want to do diff operations when
3510
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
3514
# Source | Target | disk | action
3515
# r | fdlt | | add source to search, add id path move and perform
3516
# | | | diff check on source-target
3517
# r | fdlt | a | dangling file that was present in the basis.
3519
# r | a | | add source to search
3521
# r | r | | this path is present in a non-examined tree, skip.
3522
# r | r | a | this path is present in a non-examined tree, skip.
3523
# a | fdlt | | add new id
3524
# a | fdlt | a | dangling locally added file, skip
3525
# a | a | | not present in either tree, skip
3526
# a | a | a | not present in any tree, skip
3527
# a | r | | not present in either tree at this path, skip as it
3528
# | | | may not be selected by the users list of paths.
3529
# a | r | a | not present in either tree at this path, skip as it
3530
# | | | may not be selected by the users list of paths.
3531
# fdlt | fdlt | | content in both: diff them
3532
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
3533
# fdlt | a | | unversioned: output deleted id for now
3534
# fdlt | a | a | unversioned and deleted: output deleted id
3535
# fdlt | r | | relocated in this tree, so add target to search.
3536
# | | | Dont diff, we will see an r,fd; pair when we reach
3537
# | | | this id at the other path.
3538
# fdlt | r | a | relocated in this tree, so add target to search.
3539
# | | | Dont diff, we will see an r,fd; pair when we reach
3540
# | | | this id at the other path.
3542
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
3543
# keeping a cache of directories that we have seen.
3545
while search_specific_files:
3546
# TODO: the pending list should be lexically sorted? the
3547
# interface doesn't require it.
3548
current_root = search_specific_files.pop()
3549
current_root_unicode = current_root.decode('utf8')
3550
searched_specific_files.add(current_root)
3551
# process the entries for this containing directory: the rest will be
3552
# found by their parents recursively.
3553
root_entries = self.state._entries_for_path(current_root)
3554
root_abspath = self.tree.abspath(current_root_unicode)
3556
root_stat = os.lstat(root_abspath)
3558
if e.errno == errno.ENOENT:
3559
# the path does not exist: let _process_entry know that.
3560
root_dir_info = None
3562
# some other random error: hand it up.
3565
root_dir_info = ('', current_root,
3566
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3568
if root_dir_info[2] == 'directory':
3569
if self.tree._directory_is_tree_reference(
3570
current_root.decode('utf8')):
3571
root_dir_info = root_dir_info[:2] + \
3572
('tree-reference',) + root_dir_info[3:]
3574
if not root_entries and not root_dir_info:
3575
# this specified path is not present at all, skip it.
3577
path_handled = False
3578
for entry in root_entries:
3579
result, changed = _process_entry(entry, root_dir_info)
3580
if changed is not None:
3583
self._gather_result_for_consistency(result)
3584
if changed or self.include_unchanged:
3586
if self.want_unversioned and not path_handled and root_dir_info:
3587
new_executable = bool(
3588
stat.S_ISREG(root_dir_info[3].st_mode)
3589
and stat.S_IEXEC & root_dir_info[3].st_mode)
3591
(None, current_root_unicode),
3595
(None, splitpath(current_root_unicode)[-1]),
3596
(None, root_dir_info[2]),
3597
(None, new_executable)
3599
initial_key = (current_root, '', '')
3600
block_index, _ = self.state._find_block_index_from_key(initial_key)
3601
if block_index == 0:
3602
# we have processed the total root already, but because the
3603
# initial key matched it we should skip it here.
3605
if root_dir_info and root_dir_info[2] == 'tree-reference':
3606
current_dir_info = None
3608
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3610
current_dir_info = dir_iterator.next()
3612
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3613
# python 2.5 has e.errno == EINVAL,
3614
# and e.winerror == ERROR_DIRECTORY
3615
e_winerror = getattr(e, 'winerror', None)
3616
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
3617
# there may be directories in the inventory even though
3618
# this path is not a file on disk: so mark it as end of
3620
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
3621
current_dir_info = None
3622
elif (sys.platform == 'win32'
3623
and (e.errno in win_errors
3624
or e_winerror in win_errors)):
3625
current_dir_info = None
3629
if current_dir_info[0][0] == '':
3630
# remove .bzr from iteration
3631
bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3632
if current_dir_info[1][bzr_index][0] != '.bzr':
3633
raise AssertionError()
3634
del current_dir_info[1][bzr_index]
3635
# walk until both the directory listing and the versioned metadata
3637
if (block_index < len(self.state._dirblocks) and
3638
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3639
current_block = self.state._dirblocks[block_index]
3641
current_block = None
3642
while (current_dir_info is not None or
3643
current_block is not None):
3644
if (current_dir_info and current_block
3645
and current_dir_info[0][0] != current_block[0]):
3646
if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
3647
# filesystem data refers to paths not covered by the dirblock.
3648
# this has two possibilities:
3649
# A) it is versioned but empty, so there is no block for it
3650
# B) it is not versioned.
3652
# if (A) then we need to recurse into it to check for
3653
# new unknown files or directories.
3654
# if (B) then we should ignore it, because we don't
3655
# recurse into unknown directories.
3657
while path_index < len(current_dir_info[1]):
3658
current_path_info = current_dir_info[1][path_index]
3659
if self.want_unversioned:
3660
if current_path_info[2] == 'directory':
3661
if self.tree._directory_is_tree_reference(
3662
current_path_info[0].decode('utf8')):
3663
current_path_info = current_path_info[:2] + \
3664
('tree-reference',) + current_path_info[3:]
3665
new_executable = bool(
3666
stat.S_ISREG(current_path_info[3].st_mode)
3667
and stat.S_IEXEC & current_path_info[3].st_mode)
3669
(None, utf8_decode(current_path_info[0])[0]),
3673
(None, utf8_decode(current_path_info[1])[0]),
3674
(None, current_path_info[2]),
3675
(None, new_executable))
3676
# dont descend into this unversioned path if it is
3678
if current_path_info[2] in ('directory',
3680
del current_dir_info[1][path_index]
3684
# This dir info has been handled, go to the next
3686
current_dir_info = dir_iterator.next()
3687
except StopIteration:
3688
current_dir_info = None
3690
# We have a dirblock entry for this location, but there
3691
# is no filesystem path for this. This is most likely
3692
# because a directory was removed from the disk.
3693
# We don't have to report the missing directory,
3694
# because that should have already been handled, but we
3695
# need to handle all of the files that are contained
3697
for current_entry in current_block[1]:
3698
# entry referring to file not present on disk.
3699
# advance the entry only, after processing.
3700
result, changed = _process_entry(current_entry, None)
3701
if changed is not None:
3703
self._gather_result_for_consistency(result)
3704
if changed or self.include_unchanged:
3707
if (block_index < len(self.state._dirblocks) and
3708
osutils.is_inside(current_root,
3709
self.state._dirblocks[block_index][0])):
3710
current_block = self.state._dirblocks[block_index]
3712
current_block = None
3715
if current_block and entry_index < len(current_block[1]):
3716
current_entry = current_block[1][entry_index]
3718
current_entry = None
3719
advance_entry = True
3721
if current_dir_info and path_index < len(current_dir_info[1]):
3722
current_path_info = current_dir_info[1][path_index]
3723
if current_path_info[2] == 'directory':
3724
if self.tree._directory_is_tree_reference(
3725
current_path_info[0].decode('utf8')):
3726
current_path_info = current_path_info[:2] + \
3727
('tree-reference',) + current_path_info[3:]
3729
current_path_info = None
3731
path_handled = False
3732
while (current_entry is not None or
3733
current_path_info is not None):
3734
if current_entry is None:
3735
# the check for path_handled when the path is advanced
3736
# will yield this path if needed.
3738
elif current_path_info is None:
3739
# no path is fine: the per entry code will handle it.
3740
result, changed = _process_entry(current_entry, current_path_info)
3741
if changed is not None:
3743
self._gather_result_for_consistency(result)
3744
if changed or self.include_unchanged:
3746
elif (current_entry[0][1] != current_path_info[1]
3747
or current_entry[1][self.target_index][0] in 'ar'):
3748
# The current path on disk doesn't match the dirblock
3749
# record. Either the dirblock is marked as absent, or
3750
# the file on disk is not present at all in the
3751
# dirblock. Either way, report about the dirblock
3752
# entry, and let other code handle the filesystem one.
3754
# Compare the basename for these files to determine
3756
if current_path_info[1] < current_entry[0][1]:
3757
# extra file on disk: pass for now, but only
3758
# increment the path, not the entry
3759
advance_entry = False
3761
# entry referring to file not present on disk.
3762
# advance the entry only, after processing.
3763
result, changed = _process_entry(current_entry, None)
3764
if changed is not None:
3766
self._gather_result_for_consistency(result)
3767
if changed or self.include_unchanged:
3769
advance_path = False
3771
result, changed = _process_entry(current_entry, current_path_info)
3772
if changed is not None:
3775
self._gather_result_for_consistency(result)
3776
if changed or self.include_unchanged:
3778
if advance_entry and current_entry is not None:
3780
if entry_index < len(current_block[1]):
3781
current_entry = current_block[1][entry_index]
3783
current_entry = None
3785
advance_entry = True # reset the advance flaga
3786
if advance_path and current_path_info is not None:
3787
if not path_handled:
3788
# unversioned in all regards
3789
if self.want_unversioned:
3790
new_executable = bool(
3791
stat.S_ISREG(current_path_info[3].st_mode)
3792
and stat.S_IEXEC & current_path_info[3].st_mode)
3794
relpath_unicode = utf8_decode(current_path_info[0])[0]
3795
except UnicodeDecodeError:
3796
raise errors.BadFilenameEncoding(
3797
current_path_info[0], osutils._fs_enc)
3799
(None, relpath_unicode),
3803
(None, utf8_decode(current_path_info[1])[0]),
3804
(None, current_path_info[2]),
3805
(None, new_executable))
3806
# dont descend into this unversioned path if it is
3808
if current_path_info[2] in ('directory'):
3809
del current_dir_info[1][path_index]
3811
# dont descend the disk iterator into any tree
3813
if current_path_info[2] == 'tree-reference':
3814
del current_dir_info[1][path_index]
3817
if path_index < len(current_dir_info[1]):
3818
current_path_info = current_dir_info[1][path_index]
3819
if current_path_info[2] == 'directory':
3820
if self.tree._directory_is_tree_reference(
3821
current_path_info[0].decode('utf8')):
3822
current_path_info = current_path_info[:2] + \
3823
('tree-reference',) + current_path_info[3:]
3825
current_path_info = None
3826
path_handled = False
3828
advance_path = True # reset the advance flagg.
3829
if current_block is not None:
3831
if (block_index < len(self.state._dirblocks) and
3832
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3833
current_block = self.state._dirblocks[block_index]
3835
current_block = None
3836
if current_dir_info is not None:
3838
current_dir_info = dir_iterator.next()
3839
except StopIteration:
3840
current_dir_info = None
3841
for result in self._iter_specific_file_parents():
3844
def _iter_specific_file_parents(self):
3845
"""Iter over the specific file parents."""
3846
while self.search_specific_file_parents:
3847
# Process the parent directories for the paths we were iterating.
3848
# Even in extremely large trees this should be modest, so currently
3849
# no attempt is made to optimise.
3850
path_utf8 = self.search_specific_file_parents.pop()
3851
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3852
# We've examined this path.
3854
if path_utf8 in self.searched_exact_paths:
3855
# We've examined this path.
3857
path_entries = self.state._entries_for_path(path_utf8)
3858
# We need either one or two entries. If the path in
3859
# self.target_index has moved (so the entry in source_index is in
3860
# 'ar') then we need to also look for the entry for this path in
3861
# self.source_index, to output the appropriate delete-or-rename.
3862
selected_entries = []
3864
for candidate_entry in path_entries:
3865
# Find entries present in target at this path:
3866
if candidate_entry[1][self.target_index][0] not in 'ar':
3868
selected_entries.append(candidate_entry)
3869
# Find entries present in source at this path:
3870
elif (self.source_index is not None and
3871
candidate_entry[1][self.source_index][0] not in 'ar'):
3873
if candidate_entry[1][self.target_index][0] == 'a':
3874
# Deleted, emit it here.
3875
selected_entries.append(candidate_entry)
3877
# renamed, emit it when we process the directory it
3879
self.search_specific_file_parents.add(
3880
candidate_entry[1][self.target_index][1])
3882
raise AssertionError(
3883
"Missing entry for specific path parent %r, %r" % (
3884
path_utf8, path_entries))
3885
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3886
for entry in selected_entries:
3887
if entry[0][2] in self.seen_ids:
3889
result, changed = self._process_entry(entry, path_info)
3891
raise AssertionError(
3892
"Got entry<->path mismatch for specific path "
3893
"%r entry %r path_info %r " % (
3894
path_utf8, entry, path_info))
3895
# Only include changes - we're outside the users requested
3898
self._gather_result_for_consistency(result)
3899
if (result[6][0] == 'directory' and
3900
result[6][1] != 'directory'):
3901
# This stopped being a directory, the old children have
3903
if entry[1][self.source_index][0] == 'r':
3904
# renamed, take the source path
3905
entry_path_utf8 = entry[1][self.source_index][1]
3907
entry_path_utf8 = path_utf8
3908
initial_key = (entry_path_utf8, '', '')
3909
block_index, _ = self.state._find_block_index_from_key(
3911
if block_index == 0:
3912
# The children of the root are in block index 1.
3914
current_block = None
3915
if block_index < len(self.state._dirblocks):
3916
current_block = self.state._dirblocks[block_index]
3917
if not osutils.is_inside(
3918
entry_path_utf8, current_block[0]):
3919
# No entries for this directory at all.
3920
current_block = None
3921
if current_block is not None:
3922
for entry in current_block[1]:
3923
if entry[1][self.source_index][0] in 'ar':
3924
# Not in the source tree, so doesn't have to be
3927
# Path of the entry itself.
3929
self.search_specific_file_parents.add(
3930
osutils.pathjoin(*entry[0][:2]))
3931
if changed or self.include_unchanged:
3933
self.searched_exact_paths.add(path_utf8)
3935
def _path_info(self, utf8_path, unicode_path):
3936
"""Generate path_info for unicode_path.
3938
:return: None if unicode_path does not exist, or a path_info tuple.
3940
abspath = self.tree.abspath(unicode_path)
3942
stat = os.lstat(abspath)
3944
if e.errno == errno.ENOENT:
3945
# the path does not exist.
3949
utf8_basename = utf8_path.rsplit('/', 1)[-1]
3950
dir_info = (utf8_path, utf8_basename,
3951
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3953
if dir_info[2] == 'directory':
3954
if self.tree._directory_is_tree_reference(
3956
self.root_dir_info = self.root_dir_info[:2] + \
3957
('tree-reference',) + self.root_dir_info[3:]
2777
3961
# Try to load the compiled form if possible
2779
from bzrlib._dirstate_helpers_c import (
2780
_read_dirblocks_c as _read_dirblocks,
2781
bisect_dirblock_c as bisect_dirblock,
2782
_bisect_path_left_c as _bisect_path_left,
2783
_bisect_path_right_c as _bisect_path_right,
2784
cmp_by_dirs_c as cmp_by_dirs,
3963
from bzrlib._dirstate_helpers_pyx import (
3969
ProcessEntryC as _process_entry,
3970
update_entry as update_entry,
2786
3972
except ImportError:
2787
3973
from bzrlib._dirstate_helpers_py import (
2788
_read_dirblocks_py as _read_dirblocks,
2789
bisect_dirblock_py as bisect_dirblock,
2790
_bisect_path_left_py as _bisect_path_left,
2791
_bisect_path_right_py as _bisect_path_right,
2792
cmp_by_dirs_py as cmp_by_dirs,
3980
# FIXME: It would be nice to be able to track moved lines so that the
3981
# corresponding python code can be moved to the _dirstate_helpers_py
3982
# module. I don't want to break the history for this important piece of
3983
# code so I left the code here -- vila 20090622
3984
update_entry = py_update_entry
3985
_process_entry = ProcessEntryPython