3081
2743
raise errors.ObjectNotLocked(self)
3084
def py_update_entry(state, entry, abspath, stat_value,
3085
_stat_to_minikind=DirState._stat_to_minikind,
3086
_pack_stat=pack_stat):
3087
"""Update the entry based on what is actually on disk.
3089
This function only calculates the sha if it needs to - if the entry is
3090
uncachable, or clearly different to the first parent's entry, no sha
3091
is calculated, and None is returned.
3093
:param state: The dirstate this entry is in.
3094
:param entry: This is the dirblock entry for the file in question.
3095
:param abspath: The path on disk for this file.
3096
:param stat_value: The stat value done on the path.
3097
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
3098
target of a symlink.
3101
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
3105
packed_stat = _pack_stat(stat_value)
3106
(saved_minikind, saved_link_or_sha1, saved_file_size,
3107
saved_executable, saved_packed_stat) = entry[1][0]
3109
if minikind == 'd' and saved_minikind == 't':
3111
if (minikind == saved_minikind
3112
and packed_stat == saved_packed_stat):
3113
# The stat hasn't changed since we saved, so we can re-use the
3118
# size should also be in packed_stat
3119
if saved_file_size == stat_value.st_size:
3120
return saved_link_or_sha1
3122
# If we have gotten this far, that means that we need to actually
3123
# process this entry.
3126
executable = state._is_executable(stat_value.st_mode,
3128
if state._cutoff_time is None:
3129
state._sha_cutoff_time()
3130
if (stat_value.st_mtime < state._cutoff_time
3131
and stat_value.st_ctime < state._cutoff_time
3132
and len(entry[1]) > 1
3133
and entry[1][1][0] != 'a'):
3134
# Could check for size changes for further optimised
3135
# avoidance of sha1's. However the most prominent case of
3136
# over-shaing is during initial add, which this catches.
3137
# Besides, if content filtering happens, size and sha
3138
# are calculated at the same time, so checking just the size
3139
# gains nothing w.r.t. performance.
3140
link_or_sha1 = state._sha1_file(abspath)
3141
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
3142
executable, packed_stat)
3144
entry[1][0] = ('f', '', stat_value.st_size,
3145
executable, DirState.NULLSTAT)
3146
elif minikind == 'd':
3148
entry[1][0] = ('d', '', 0, False, packed_stat)
3149
if saved_minikind != 'd':
3150
# This changed from something into a directory. Make sure we
3151
# have a directory block for it. This doesn't happen very
3152
# often, so this doesn't have to be super fast.
3153
block_index, entry_index, dir_present, file_present = \
3154
state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3155
state._ensure_block(block_index, entry_index,
3156
osutils.pathjoin(entry[0][0], entry[0][1]))
3157
elif minikind == 'l':
3158
link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3159
if state._cutoff_time is None:
3160
state._sha_cutoff_time()
3161
if (stat_value.st_mtime < state._cutoff_time
3162
and stat_value.st_ctime < state._cutoff_time):
3163
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
3166
entry[1][0] = ('l', '', stat_value.st_size,
3167
False, DirState.NULLSTAT)
3168
state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3172
class ProcessEntryPython(object):
3174
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3175
"last_source_parent", "last_target_parent", "include_unchanged",
3176
"partial", "use_filesystem_for_exec", "utf8_decode",
3177
"searched_specific_files", "search_specific_files",
3178
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3179
"state", "source_index", "target_index", "want_unversioned", "tree"]
3181
def __init__(self, include_unchanged, use_filesystem_for_exec,
3182
search_specific_files, state, source_index, target_index,
3183
want_unversioned, tree):
3184
self.old_dirname_to_file_id = {}
3185
self.new_dirname_to_file_id = {}
3186
# Are we doing a partial iter_changes?
3187
self.partial = search_specific_files != set([''])
3188
# Using a list so that we can access the values and change them in
3189
# nested scope. Each one is [path, file_id, entry]
3190
self.last_source_parent = [None, None]
3191
self.last_target_parent = [None, None]
3192
self.include_unchanged = include_unchanged
3193
self.use_filesystem_for_exec = use_filesystem_for_exec
3194
self.utf8_decode = cache_utf8._utf8_decode
3195
# for all search_indexs in each path at or under each element of
3196
# search_specific_files, if the detail is relocated: add the id, and
3197
# add the relocated path as one to search if its not searched already.
3198
# If the detail is not relocated, add the id.
3199
self.searched_specific_files = set()
3200
# When we search exact paths without expanding downwards, we record
3202
self.searched_exact_paths = set()
3203
self.search_specific_files = search_specific_files
3204
# The parents up to the root of the paths we are searching.
3205
# After all normal paths are returned, these specific items are returned.
3206
self.search_specific_file_parents = set()
3207
# The ids we've sent out in the delta.
3208
self.seen_ids = set()
3210
self.source_index = source_index
3211
self.target_index = target_index
3212
if target_index != 0:
3213
# A lot of code in here depends on target_index == 0
3214
raise errors.BzrError('unsupported target index')
3215
self.want_unversioned = want_unversioned
3218
def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
3219
"""Compare an entry and real disk to generate delta information.
3221
:param path_info: top_relpath, basename, kind, lstat, abspath for
3222
the path of entry. If None, then the path is considered absent in
3223
the target (Perhaps we should pass in a concrete entry for this ?)
3224
Basename is returned as a utf8 string because we expect this
3225
tuple will be ignored, and don't want to take the time to
3227
:return: (iter_changes_result, changed). If the entry has not been
3228
handled then changed is None. Otherwise it is False if no content
3229
or metadata changes have occurred, and True if any content or
3230
metadata change has occurred. If self.include_unchanged is True then
3231
if changed is not None, iter_changes_result will always be a result
3232
tuple. Otherwise, iter_changes_result is None unless changed is
3235
if self.source_index is None:
3236
source_details = DirState.NULL_PARENT_DETAILS
3238
source_details = entry[1][self.source_index]
3239
target_details = entry[1][self.target_index]
3240
target_minikind = target_details[0]
3241
if path_info is not None and target_minikind in 'fdlt':
3242
if not (self.target_index == 0):
3243
raise AssertionError()
3244
link_or_sha1 = update_entry(self.state, entry,
3245
abspath=path_info[4], stat_value=path_info[3])
3246
# The entry may have been modified by update_entry
3247
target_details = entry[1][self.target_index]
3248
target_minikind = target_details[0]
3251
file_id = entry[0][2]
3252
source_minikind = source_details[0]
3253
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
3254
# claimed content in both: diff
3255
# r | fdlt | | add source to search, add id path move and perform
3256
# | | | diff check on source-target
3257
# r | fdlt | a | dangling file that was present in the basis.
3259
if source_minikind in 'r':
3260
# add the source to the search path to find any children it
3261
# has. TODO ? : only add if it is a container ?
3262
if not osutils.is_inside_any(self.searched_specific_files,
3264
self.search_specific_files.add(source_details[1])
3265
# generate the old path; this is needed for stating later
3267
old_path = source_details[1]
3268
old_dirname, old_basename = os.path.split(old_path)
3269
path = pathjoin(entry[0][0], entry[0][1])
3270
old_entry = self.state._get_entry(self.source_index,
3272
# update the source details variable to be the real
3274
if old_entry == (None, None):
3275
raise errors.CorruptDirstate(self.state._filename,
3276
"entry '%s/%s' is considered renamed from %r"
3277
" but source does not exist\n"
3278
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3279
source_details = old_entry[1][self.source_index]
3280
source_minikind = source_details[0]
3282
old_dirname = entry[0][0]
3283
old_basename = entry[0][1]
3284
old_path = path = None
3285
if path_info is None:
3286
# the file is missing on disk, show as removed.
3287
content_change = True
3291
# source and target are both versioned and disk file is present.
3292
target_kind = path_info[2]
3293
if target_kind == 'directory':
3295
old_path = path = pathjoin(old_dirname, old_basename)
3296
self.new_dirname_to_file_id[path] = file_id
3297
if source_minikind != 'd':
3298
content_change = True
3300
# directories have no fingerprint
3301
content_change = False
3303
elif target_kind == 'file':
3304
if source_minikind != 'f':
3305
content_change = True
3307
# Check the sha. We can't just rely on the size as
3308
# content filtering may mean differ sizes actually
3309
# map to the same content
3310
if link_or_sha1 is None:
3312
statvalue, link_or_sha1 = \
3313
self.state._sha1_provider.stat_and_sha1(
3315
self.state._observed_sha1(entry, link_or_sha1,
3317
content_change = (link_or_sha1 != source_details[1])
3318
# Target details is updated at update_entry time
3319
if self.use_filesystem_for_exec:
3320
# We don't need S_ISREG here, because we are sure
3321
# we are dealing with a file.
3322
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
3324
target_exec = target_details[3]
3325
elif target_kind == 'symlink':
3326
if source_minikind != 'l':
3327
content_change = True
3329
content_change = (link_or_sha1 != source_details[1])
3331
elif target_kind == 'tree-reference':
3332
if source_minikind != 't':
3333
content_change = True
3335
content_change = False
3339
path = pathjoin(old_dirname, old_basename)
3340
raise errors.BadFileKindError(path, path_info[2])
3341
if source_minikind == 'd':
3343
old_path = path = pathjoin(old_dirname, old_basename)
3344
self.old_dirname_to_file_id[old_path] = file_id
3345
# parent id is the entry for the path in the target tree
3346
if old_basename and old_dirname == self.last_source_parent[0]:
3347
source_parent_id = self.last_source_parent[1]
3350
source_parent_id = self.old_dirname_to_file_id[old_dirname]
3352
source_parent_entry = self.state._get_entry(self.source_index,
3353
path_utf8=old_dirname)
3354
source_parent_id = source_parent_entry[0][2]
3355
if source_parent_id == entry[0][2]:
3356
# This is the root, so the parent is None
3357
source_parent_id = None
3359
self.last_source_parent[0] = old_dirname
3360
self.last_source_parent[1] = source_parent_id
3361
new_dirname = entry[0][0]
3362
if entry[0][1] and new_dirname == self.last_target_parent[0]:
3363
target_parent_id = self.last_target_parent[1]
3366
target_parent_id = self.new_dirname_to_file_id[new_dirname]
3368
# TODO: We don't always need to do the lookup, because the
3369
# parent entry will be the same as the source entry.
3370
target_parent_entry = self.state._get_entry(self.target_index,
3371
path_utf8=new_dirname)
3372
if target_parent_entry == (None, None):
3373
raise AssertionError(
3374
"Could not find target parent in wt: %s\nparent of: %s"
3375
% (new_dirname, entry))
3376
target_parent_id = target_parent_entry[0][2]
3377
if target_parent_id == entry[0][2]:
3378
# This is the root, so the parent is None
3379
target_parent_id = None
3381
self.last_target_parent[0] = new_dirname
3382
self.last_target_parent[1] = target_parent_id
3384
source_exec = source_details[3]
3385
changed = (content_change
3386
or source_parent_id != target_parent_id
3387
or old_basename != entry[0][1]
3388
or source_exec != target_exec
3390
if not changed and not self.include_unchanged:
3393
if old_path is None:
3394
old_path = path = pathjoin(old_dirname, old_basename)
3395
old_path_u = self.utf8_decode(old_path)[0]
3398
old_path_u = self.utf8_decode(old_path)[0]
3399
if old_path == path:
3402
path_u = self.utf8_decode(path)[0]
3403
source_kind = DirState._minikind_to_kind[source_minikind]
3404
return (entry[0][2],
3405
(old_path_u, path_u),
3408
(source_parent_id, target_parent_id),
3409
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3410
(source_kind, target_kind),
3411
(source_exec, target_exec)), changed
3412
elif source_minikind in 'a' and target_minikind in 'fdlt':
3413
# looks like a new file
3414
path = pathjoin(entry[0][0], entry[0][1])
3415
# parent id is the entry for the path in the target tree
3416
# TODO: these are the same for an entire directory: cache em.
3417
parent_id = self.state._get_entry(self.target_index,
3418
path_utf8=entry[0][0])[0][2]
3419
if parent_id == entry[0][2]:
3421
if path_info is not None:
3423
if self.use_filesystem_for_exec:
3424
# We need S_ISREG here, because we aren't sure if this
3427
stat.S_ISREG(path_info[3].st_mode)
3428
and stat.S_IEXEC & path_info[3].st_mode)
3430
target_exec = target_details[3]
3431
return (entry[0][2],
3432
(None, self.utf8_decode(path)[0]),
3436
(None, self.utf8_decode(entry[0][1])[0]),
3437
(None, path_info[2]),
3438
(None, target_exec)), True
3440
# Its a missing file, report it as such.
3441
return (entry[0][2],
3442
(None, self.utf8_decode(path)[0]),
3446
(None, self.utf8_decode(entry[0][1])[0]),
3448
(None, False)), True
3449
elif source_minikind in 'fdlt' and target_minikind in 'a':
3450
# unversioned, possibly, or possibly not deleted: we dont care.
3451
# if its still on disk, *and* theres no other entry at this
3452
# path [we dont know this in this routine at the moment -
3453
# perhaps we should change this - then it would be an unknown.
3454
old_path = pathjoin(entry[0][0], entry[0][1])
3455
# parent id is the entry for the path in the target tree
3456
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
3457
if parent_id == entry[0][2]:
3459
return (entry[0][2],
3460
(self.utf8_decode(old_path)[0], None),
3464
(self.utf8_decode(entry[0][1])[0], None),
3465
(DirState._minikind_to_kind[source_minikind], None),
3466
(source_details[3], None)), True
3467
elif source_minikind in 'fdlt' and target_minikind in 'r':
3468
# a rename; could be a true rename, or a rename inherited from
3469
# a renamed parent. TODO: handle this efficiently. Its not
3470
# common case to rename dirs though, so a correct but slow
3471
# implementation will do.
3472
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3473
self.search_specific_files.add(target_details[1])
3474
elif source_minikind in 'ra' and target_minikind in 'ra':
3475
# neither of the selected trees contain this file,
3476
# so skip over it. This is not currently directly tested, but
3477
# is indirectly via test_too_much.TestCommands.test_conflicts.
3480
raise AssertionError("don't know how to compare "
3481
"source_minikind=%r, target_minikind=%r"
3482
% (source_minikind, target_minikind))
3483
## import pdb;pdb.set_trace()
3489
def _gather_result_for_consistency(self, result):
3490
"""Check a result we will yield to make sure we are consistent later.
3492
This gathers result's parents into a set to output later.
3494
:param result: A result tuple.
3496
if not self.partial or not result[0]:
3498
self.seen_ids.add(result[0])
3499
new_path = result[1][1]
3501
# Not the root and not a delete: queue up the parents of the path.
3502
self.search_specific_file_parents.update(
3503
osutils.parent_directories(new_path.encode('utf8')))
3504
# Add the root directory which parent_directories does not
3506
self.search_specific_file_parents.add('')
3508
def iter_changes(self):
3509
"""Iterate over the changes."""
3510
utf8_decode = cache_utf8._utf8_decode
3511
_cmp_by_dirs = cmp_by_dirs
3512
_process_entry = self._process_entry
3513
search_specific_files = self.search_specific_files
3514
searched_specific_files = self.searched_specific_files
3515
splitpath = osutils.splitpath
3517
# compare source_index and target_index at or under each element of search_specific_files.
3518
# follow the following comparison table. Note that we only want to do diff operations when
3519
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
3523
# Source | Target | disk | action
3524
# r | fdlt | | add source to search, add id path move and perform
3525
# | | | diff check on source-target
3526
# r | fdlt | a | dangling file that was present in the basis.
3528
# r | a | | add source to search
3530
# r | r | | this path is present in a non-examined tree, skip.
3531
# r | r | a | this path is present in a non-examined tree, skip.
3532
# a | fdlt | | add new id
3533
# a | fdlt | a | dangling locally added file, skip
3534
# a | a | | not present in either tree, skip
3535
# a | a | a | not present in any tree, skip
3536
# a | r | | not present in either tree at this path, skip as it
3537
# | | | may not be selected by the users list of paths.
3538
# a | r | a | not present in either tree at this path, skip as it
3539
# | | | may not be selected by the users list of paths.
3540
# fdlt | fdlt | | content in both: diff them
3541
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
3542
# fdlt | a | | unversioned: output deleted id for now
3543
# fdlt | a | a | unversioned and deleted: output deleted id
3544
# fdlt | r | | relocated in this tree, so add target to search.
3545
# | | | Dont diff, we will see an r,fd; pair when we reach
3546
# | | | this id at the other path.
3547
# fdlt | r | a | relocated in this tree, so add target to search.
3548
# | | | Dont diff, we will see an r,fd; pair when we reach
3549
# | | | this id at the other path.
3551
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
3552
# keeping a cache of directories that we have seen.
3554
while search_specific_files:
3555
# TODO: the pending list should be lexically sorted? the
3556
# interface doesn't require it.
3557
current_root = search_specific_files.pop()
3558
current_root_unicode = current_root.decode('utf8')
3559
searched_specific_files.add(current_root)
3560
# process the entries for this containing directory: the rest will be
3561
# found by their parents recursively.
3562
root_entries = self.state._entries_for_path(current_root)
3563
root_abspath = self.tree.abspath(current_root_unicode)
3565
root_stat = os.lstat(root_abspath)
3567
if e.errno == errno.ENOENT:
3568
# the path does not exist: let _process_entry know that.
3569
root_dir_info = None
3571
# some other random error: hand it up.
3574
root_dir_info = ('', current_root,
3575
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3577
if root_dir_info[2] == 'directory':
3578
if self.tree._directory_is_tree_reference(
3579
current_root.decode('utf8')):
3580
root_dir_info = root_dir_info[:2] + \
3581
('tree-reference',) + root_dir_info[3:]
3583
if not root_entries and not root_dir_info:
3584
# this specified path is not present at all, skip it.
3586
path_handled = False
3587
for entry in root_entries:
3588
result, changed = _process_entry(entry, root_dir_info)
3589
if changed is not None:
3592
self._gather_result_for_consistency(result)
3593
if changed or self.include_unchanged:
3595
if self.want_unversioned and not path_handled and root_dir_info:
3596
new_executable = bool(
3597
stat.S_ISREG(root_dir_info[3].st_mode)
3598
and stat.S_IEXEC & root_dir_info[3].st_mode)
3600
(None, current_root_unicode),
3604
(None, splitpath(current_root_unicode)[-1]),
3605
(None, root_dir_info[2]),
3606
(None, new_executable)
3608
initial_key = (current_root, '', '')
3609
block_index, _ = self.state._find_block_index_from_key(initial_key)
3610
if block_index == 0:
3611
# we have processed the total root already, but because the
3612
# initial key matched it we should skip it here.
3614
if root_dir_info and root_dir_info[2] == 'tree-reference':
3615
current_dir_info = None
3617
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3619
current_dir_info = dir_iterator.next()
3621
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3622
# python 2.5 has e.errno == EINVAL,
3623
# and e.winerror == ERROR_DIRECTORY
3624
e_winerror = getattr(e, 'winerror', None)
3625
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
3626
# there may be directories in the inventory even though
3627
# this path is not a file on disk: so mark it as end of
3629
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
3630
current_dir_info = None
3631
elif (sys.platform == 'win32'
3632
and (e.errno in win_errors
3633
or e_winerror in win_errors)):
3634
current_dir_info = None
3638
if current_dir_info[0][0] == '':
3639
# remove .bzr from iteration
3640
bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3641
if current_dir_info[1][bzr_index][0] != '.bzr':
3642
raise AssertionError()
3643
del current_dir_info[1][bzr_index]
3644
# walk until both the directory listing and the versioned metadata
3646
if (block_index < len(self.state._dirblocks) and
3647
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3648
current_block = self.state._dirblocks[block_index]
3650
current_block = None
3651
while (current_dir_info is not None or
3652
current_block is not None):
3653
if (current_dir_info and current_block
3654
and current_dir_info[0][0] != current_block[0]):
3655
if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
3656
# filesystem data refers to paths not covered by the dirblock.
3657
# this has two possibilities:
3658
# A) it is versioned but empty, so there is no block for it
3659
# B) it is not versioned.
3661
# if (A) then we need to recurse into it to check for
3662
# new unknown files or directories.
3663
# if (B) then we should ignore it, because we don't
3664
# recurse into unknown directories.
3666
while path_index < len(current_dir_info[1]):
3667
current_path_info = current_dir_info[1][path_index]
3668
if self.want_unversioned:
3669
if current_path_info[2] == 'directory':
3670
if self.tree._directory_is_tree_reference(
3671
current_path_info[0].decode('utf8')):
3672
current_path_info = current_path_info[:2] + \
3673
('tree-reference',) + current_path_info[3:]
3674
new_executable = bool(
3675
stat.S_ISREG(current_path_info[3].st_mode)
3676
and stat.S_IEXEC & current_path_info[3].st_mode)
3678
(None, utf8_decode(current_path_info[0])[0]),
3682
(None, utf8_decode(current_path_info[1])[0]),
3683
(None, current_path_info[2]),
3684
(None, new_executable))
3685
# dont descend into this unversioned path if it is
3687
if current_path_info[2] in ('directory',
3689
del current_dir_info[1][path_index]
3693
# This dir info has been handled, go to the next
3695
current_dir_info = dir_iterator.next()
3696
except StopIteration:
3697
current_dir_info = None
3699
# We have a dirblock entry for this location, but there
3700
# is no filesystem path for this. This is most likely
3701
# because a directory was removed from the disk.
3702
# We don't have to report the missing directory,
3703
# because that should have already been handled, but we
3704
# need to handle all of the files that are contained
3706
for current_entry in current_block[1]:
3707
# entry referring to file not present on disk.
3708
# advance the entry only, after processing.
3709
result, changed = _process_entry(current_entry, None)
3710
if changed is not None:
3712
self._gather_result_for_consistency(result)
3713
if changed or self.include_unchanged:
3716
if (block_index < len(self.state._dirblocks) and
3717
osutils.is_inside(current_root,
3718
self.state._dirblocks[block_index][0])):
3719
current_block = self.state._dirblocks[block_index]
3721
current_block = None
3724
if current_block and entry_index < len(current_block[1]):
3725
current_entry = current_block[1][entry_index]
3727
current_entry = None
3728
advance_entry = True
3730
if current_dir_info and path_index < len(current_dir_info[1]):
3731
current_path_info = current_dir_info[1][path_index]
3732
if current_path_info[2] == 'directory':
3733
if self.tree._directory_is_tree_reference(
3734
current_path_info[0].decode('utf8')):
3735
current_path_info = current_path_info[:2] + \
3736
('tree-reference',) + current_path_info[3:]
3738
current_path_info = None
3740
path_handled = False
3741
while (current_entry is not None or
3742
current_path_info is not None):
3743
if current_entry is None:
3744
# the check for path_handled when the path is advanced
3745
# will yield this path if needed.
3747
elif current_path_info is None:
3748
# no path is fine: the per entry code will handle it.
3749
result, changed = _process_entry(current_entry, current_path_info)
3750
if changed is not None:
3752
self._gather_result_for_consistency(result)
3753
if changed or self.include_unchanged:
3755
elif (current_entry[0][1] != current_path_info[1]
3756
or current_entry[1][self.target_index][0] in 'ar'):
3757
# The current path on disk doesn't match the dirblock
3758
# record. Either the dirblock is marked as absent, or
3759
# the file on disk is not present at all in the
3760
# dirblock. Either way, report about the dirblock
3761
# entry, and let other code handle the filesystem one.
3763
# Compare the basename for these files to determine
3765
if current_path_info[1] < current_entry[0][1]:
3766
# extra file on disk: pass for now, but only
3767
# increment the path, not the entry
3768
advance_entry = False
3770
# entry referring to file not present on disk.
3771
# advance the entry only, after processing.
3772
result, changed = _process_entry(current_entry, None)
3773
if changed is not None:
3775
self._gather_result_for_consistency(result)
3776
if changed or self.include_unchanged:
3778
advance_path = False
3780
result, changed = _process_entry(current_entry, current_path_info)
3781
if changed is not None:
3784
self._gather_result_for_consistency(result)
3785
if changed or self.include_unchanged:
3787
if advance_entry and current_entry is not None:
3789
if entry_index < len(current_block[1]):
3790
current_entry = current_block[1][entry_index]
3792
current_entry = None
3794
advance_entry = True # reset the advance flaga
3795
if advance_path and current_path_info is not None:
3796
if not path_handled:
3797
# unversioned in all regards
3798
if self.want_unversioned:
3799
new_executable = bool(
3800
stat.S_ISREG(current_path_info[3].st_mode)
3801
and stat.S_IEXEC & current_path_info[3].st_mode)
3803
relpath_unicode = utf8_decode(current_path_info[0])[0]
3804
except UnicodeDecodeError:
3805
raise errors.BadFilenameEncoding(
3806
current_path_info[0], osutils._fs_enc)
3808
(None, relpath_unicode),
3812
(None, utf8_decode(current_path_info[1])[0]),
3813
(None, current_path_info[2]),
3814
(None, new_executable))
3815
# dont descend into this unversioned path if it is
3817
if current_path_info[2] in ('directory'):
3818
del current_dir_info[1][path_index]
3820
# dont descend the disk iterator into any tree
3822
if current_path_info[2] == 'tree-reference':
3823
del current_dir_info[1][path_index]
3826
if path_index < len(current_dir_info[1]):
3827
current_path_info = current_dir_info[1][path_index]
3828
if current_path_info[2] == 'directory':
3829
if self.tree._directory_is_tree_reference(
3830
current_path_info[0].decode('utf8')):
3831
current_path_info = current_path_info[:2] + \
3832
('tree-reference',) + current_path_info[3:]
3834
current_path_info = None
3835
path_handled = False
3837
advance_path = True # reset the advance flagg.
3838
if current_block is not None:
3840
if (block_index < len(self.state._dirblocks) and
3841
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3842
current_block = self.state._dirblocks[block_index]
3844
current_block = None
3845
if current_dir_info is not None:
3847
current_dir_info = dir_iterator.next()
3848
except StopIteration:
3849
current_dir_info = None
3850
for result in self._iter_specific_file_parents():
3853
def _iter_specific_file_parents(self):
3854
"""Iter over the specific file parents."""
3855
while self.search_specific_file_parents:
3856
# Process the parent directories for the paths we were iterating.
3857
# Even in extremely large trees this should be modest, so currently
3858
# no attempt is made to optimise.
3859
path_utf8 = self.search_specific_file_parents.pop()
3860
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3861
# We've examined this path.
3863
if path_utf8 in self.searched_exact_paths:
3864
# We've examined this path.
3866
path_entries = self.state._entries_for_path(path_utf8)
3867
# We need either one or two entries. If the path in
3868
# self.target_index has moved (so the entry in source_index is in
3869
# 'ar') then we need to also look for the entry for this path in
3870
# self.source_index, to output the appropriate delete-or-rename.
3871
selected_entries = []
3873
for candidate_entry in path_entries:
3874
# Find entries present in target at this path:
3875
if candidate_entry[1][self.target_index][0] not in 'ar':
3877
selected_entries.append(candidate_entry)
3878
# Find entries present in source at this path:
3879
elif (self.source_index is not None and
3880
candidate_entry[1][self.source_index][0] not in 'ar'):
3882
if candidate_entry[1][self.target_index][0] == 'a':
3883
# Deleted, emit it here.
3884
selected_entries.append(candidate_entry)
3886
# renamed, emit it when we process the directory it
3888
self.search_specific_file_parents.add(
3889
candidate_entry[1][self.target_index][1])
3891
raise AssertionError(
3892
"Missing entry for specific path parent %r, %r" % (
3893
path_utf8, path_entries))
3894
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3895
for entry in selected_entries:
3896
if entry[0][2] in self.seen_ids:
3898
result, changed = self._process_entry(entry, path_info)
3900
raise AssertionError(
3901
"Got entry<->path mismatch for specific path "
3902
"%r entry %r path_info %r " % (
3903
path_utf8, entry, path_info))
3904
# Only include changes - we're outside the users requested
3907
self._gather_result_for_consistency(result)
3908
if (result[6][0] == 'directory' and
3909
result[6][1] != 'directory'):
3910
# This stopped being a directory, the old children have
3912
if entry[1][self.source_index][0] == 'r':
3913
# renamed, take the source path
3914
entry_path_utf8 = entry[1][self.source_index][1]
3916
entry_path_utf8 = path_utf8
3917
initial_key = (entry_path_utf8, '', '')
3918
block_index, _ = self.state._find_block_index_from_key(
3920
if block_index == 0:
3921
# The children of the root are in block index 1.
3923
current_block = None
3924
if block_index < len(self.state._dirblocks):
3925
current_block = self.state._dirblocks[block_index]
3926
if not osutils.is_inside(
3927
entry_path_utf8, current_block[0]):
3928
# No entries for this directory at all.
3929
current_block = None
3930
if current_block is not None:
3931
for entry in current_block[1]:
3932
if entry[1][self.source_index][0] in 'ar':
3933
# Not in the source tree, so doesn't have to be
3936
# Path of the entry itself.
3938
self.search_specific_file_parents.add(
3939
osutils.pathjoin(*entry[0][:2]))
3940
if changed or self.include_unchanged:
3942
self.searched_exact_paths.add(path_utf8)
3944
def _path_info(self, utf8_path, unicode_path):
3945
"""Generate path_info for unicode_path.
3947
:return: None if unicode_path does not exist, or a path_info tuple.
3949
abspath = self.tree.abspath(unicode_path)
3951
stat = os.lstat(abspath)
3953
if e.errno == errno.ENOENT:
3954
# the path does not exist.
3958
utf8_basename = utf8_path.rsplit('/', 1)[-1]
3959
dir_info = (utf8_path, utf8_basename,
3960
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3962
if dir_info[2] == 'directory':
3963
if self.tree._directory_is_tree_reference(
3965
self.root_dir_info = self.root_dir_info[:2] + \
3966
('tree-reference',) + self.root_dir_info[3:]
3970
2746
# Try to load the compiled form if possible
3972
from bzrlib._dirstate_helpers_pyx import (
3978
ProcessEntryC as _process_entry,
3979
update_entry as update_entry,
2748
from bzrlib._dirstate_helpers_c import (
2749
_read_dirblocks_c as _read_dirblocks,
2750
bisect_dirblock_c as bisect_dirblock,
2751
_bisect_path_left_c as _bisect_path_left,
2752
_bisect_path_right_c as _bisect_path_right,
2753
cmp_by_dirs_c as cmp_by_dirs,
3981
except ImportError, e:
3982
osutils.failed_to_load_extension(e)
3983
2756
from bzrlib._dirstate_helpers_py import (
2757
_read_dirblocks_py as _read_dirblocks,
2758
bisect_dirblock_py as bisect_dirblock,
2759
_bisect_path_left_py as _bisect_path_left,
2760
_bisect_path_right_py as _bisect_path_right,
2761
cmp_by_dirs_py as cmp_by_dirs,
3990
# FIXME: It would be nice to be able to track moved lines so that the
3991
# corresponding python code can be moved to the _dirstate_helpers_py
3992
# module. I don't want to break the history for this important piece of
3993
# code so I left the code here -- vila 20090622
3994
update_entry = py_update_entry
3995
_process_entry = ProcessEntryPython