3164
2588
raise errors.ObjectNotLocked(self)
3167
def py_update_entry(state, entry, abspath, stat_value,
3168
_stat_to_minikind=DirState._stat_to_minikind,
3169
_pack_stat=pack_stat):
3170
"""Update the entry based on what is actually on disk.
3172
This function only calculates the sha if it needs to - if the entry is
3173
uncachable, or clearly different to the first parent's entry, no sha
3174
is calculated, and None is returned.
3176
:param state: The dirstate this entry is in.
3177
:param entry: This is the dirblock entry for the file in question.
3178
:param abspath: The path on disk for this file.
3179
:param stat_value: The stat value done on the path.
3180
:return: None, or The sha1 hexdigest of the file (40 bytes) or link
3181
target of a symlink.
3184
minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
3188
packed_stat = _pack_stat(stat_value)
3189
(saved_minikind, saved_link_or_sha1, saved_file_size,
3190
saved_executable, saved_packed_stat) = entry[1][0]
3192
if minikind == 'd' and saved_minikind == 't':
3194
if (minikind == saved_minikind
3195
and packed_stat == saved_packed_stat):
3196
# The stat hasn't changed since we saved, so we can re-use the
3201
# size should also be in packed_stat
3202
if saved_file_size == stat_value.st_size:
3203
return saved_link_or_sha1
3205
# If we have gotten this far, that means that we need to actually
3206
# process this entry.
3210
executable = state._is_executable(stat_value.st_mode,
3212
if state._cutoff_time is None:
3213
state._sha_cutoff_time()
3214
if (stat_value.st_mtime < state._cutoff_time
3215
and stat_value.st_ctime < state._cutoff_time
3216
and len(entry[1]) > 1
3217
and entry[1][1][0] != 'a'):
3218
# Could check for size changes for further optimised
3219
# avoidance of sha1's. However the most prominent case of
3220
# over-shaing is during initial add, which this catches.
3221
# Besides, if content filtering happens, size and sha
3222
# are calculated at the same time, so checking just the size
3223
# gains nothing w.r.t. performance.
3224
link_or_sha1 = state._sha1_file(abspath)
3225
entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
3226
executable, packed_stat)
3228
entry[1][0] = ('f', '', stat_value.st_size,
3229
executable, DirState.NULLSTAT)
3230
worth_saving = False
3231
elif minikind == 'd':
3233
entry[1][0] = ('d', '', 0, False, packed_stat)
3234
if saved_minikind != 'd':
3235
# This changed from something into a directory. Make sure we
3236
# have a directory block for it. This doesn't happen very
3237
# often, so this doesn't have to be super fast.
3238
block_index, entry_index, dir_present, file_present = \
3239
state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3240
state._ensure_block(block_index, entry_index,
3241
osutils.pathjoin(entry[0][0], entry[0][1]))
3243
worth_saving = False
3244
elif minikind == 'l':
3245
link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3246
if state._cutoff_time is None:
3247
state._sha_cutoff_time()
3248
if (stat_value.st_mtime < state._cutoff_time
3249
and stat_value.st_ctime < state._cutoff_time):
3250
entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
3253
entry[1][0] = ('l', '', stat_value.st_size,
3254
False, DirState.NULLSTAT)
3256
state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3260
class ProcessEntryPython(object):
3262
__slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3263
"last_source_parent", "last_target_parent", "include_unchanged",
3264
"partial", "use_filesystem_for_exec", "utf8_decode",
3265
"searched_specific_files", "search_specific_files",
3266
"searched_exact_paths", "search_specific_file_parents", "seen_ids",
3267
"state", "source_index", "target_index", "want_unversioned", "tree"]
3269
def __init__(self, include_unchanged, use_filesystem_for_exec,
3270
search_specific_files, state, source_index, target_index,
3271
want_unversioned, tree):
3272
self.old_dirname_to_file_id = {}
3273
self.new_dirname_to_file_id = {}
3274
# Are we doing a partial iter_changes?
3275
self.partial = search_specific_files != set([''])
3276
# Using a list so that we can access the values and change them in
3277
# nested scope. Each one is [path, file_id, entry]
3278
self.last_source_parent = [None, None]
3279
self.last_target_parent = [None, None]
3280
self.include_unchanged = include_unchanged
3281
self.use_filesystem_for_exec = use_filesystem_for_exec
3282
self.utf8_decode = cache_utf8._utf8_decode
3283
# for all search_indexs in each path at or under each element of
3284
# search_specific_files, if the detail is relocated: add the id, and
3285
# add the relocated path as one to search if its not searched already.
3286
# If the detail is not relocated, add the id.
3287
self.searched_specific_files = set()
3288
# When we search exact paths without expanding downwards, we record
3290
self.searched_exact_paths = set()
3291
self.search_specific_files = search_specific_files
3292
# The parents up to the root of the paths we are searching.
3293
# After all normal paths are returned, these specific items are returned.
3294
self.search_specific_file_parents = set()
3295
# The ids we've sent out in the delta.
3296
self.seen_ids = set()
3298
self.source_index = source_index
3299
self.target_index = target_index
3300
if target_index != 0:
3301
# A lot of code in here depends on target_index == 0
3302
raise errors.BzrError('unsupported target index')
3303
self.want_unversioned = want_unversioned
3306
def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
3307
"""Compare an entry and real disk to generate delta information.
3309
:param path_info: top_relpath, basename, kind, lstat, abspath for
3310
the path of entry. If None, then the path is considered absent in
3311
the target (Perhaps we should pass in a concrete entry for this ?)
3312
Basename is returned as a utf8 string because we expect this
3313
tuple will be ignored, and don't want to take the time to
3315
:return: (iter_changes_result, changed). If the entry has not been
3316
handled then changed is None. Otherwise it is False if no content
3317
or metadata changes have occurred, and True if any content or
3318
metadata change has occurred. If self.include_unchanged is True then
3319
if changed is not None, iter_changes_result will always be a result
3320
tuple. Otherwise, iter_changes_result is None unless changed is
3323
if self.source_index is None:
3324
source_details = DirState.NULL_PARENT_DETAILS
3326
source_details = entry[1][self.source_index]
3327
target_details = entry[1][self.target_index]
3328
target_minikind = target_details[0]
3329
if path_info is not None and target_minikind in 'fdlt':
3330
if not (self.target_index == 0):
3331
raise AssertionError()
3332
link_or_sha1 = update_entry(self.state, entry,
3333
abspath=path_info[4], stat_value=path_info[3])
3334
# The entry may have been modified by update_entry
3335
target_details = entry[1][self.target_index]
3336
target_minikind = target_details[0]
3339
file_id = entry[0][2]
3340
source_minikind = source_details[0]
3341
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
3342
# claimed content in both: diff
3343
# r | fdlt | | add source to search, add id path move and perform
3344
# | | | diff check on source-target
3345
# r | fdlt | a | dangling file that was present in the basis.
3347
if source_minikind in 'r':
3348
# add the source to the search path to find any children it
3349
# has. TODO ? : only add if it is a container ?
3350
if not osutils.is_inside_any(self.searched_specific_files,
3352
self.search_specific_files.add(source_details[1])
3353
# generate the old path; this is needed for stating later
3355
old_path = source_details[1]
3356
old_dirname, old_basename = os.path.split(old_path)
3357
path = pathjoin(entry[0][0], entry[0][1])
3358
old_entry = self.state._get_entry(self.source_index,
3360
# update the source details variable to be the real
3362
if old_entry == (None, None):
3363
raise errors.CorruptDirstate(self.state._filename,
3364
"entry '%s/%s' is considered renamed from %r"
3365
" but source does not exist\n"
3366
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3367
source_details = old_entry[1][self.source_index]
3368
source_minikind = source_details[0]
3370
old_dirname = entry[0][0]
3371
old_basename = entry[0][1]
3372
old_path = path = None
3373
if path_info is None:
3374
# the file is missing on disk, show as removed.
3375
content_change = True
3379
# source and target are both versioned and disk file is present.
3380
target_kind = path_info[2]
3381
if target_kind == 'directory':
3383
old_path = path = pathjoin(old_dirname, old_basename)
3384
self.new_dirname_to_file_id[path] = file_id
3385
if source_minikind != 'd':
3386
content_change = True
3388
# directories have no fingerprint
3389
content_change = False
3391
elif target_kind == 'file':
3392
if source_minikind != 'f':
3393
content_change = True
3395
# Check the sha. We can't just rely on the size as
3396
# content filtering may mean differ sizes actually
3397
# map to the same content
3398
if link_or_sha1 is None:
3400
statvalue, link_or_sha1 = \
3401
self.state._sha1_provider.stat_and_sha1(
3403
self.state._observed_sha1(entry, link_or_sha1,
3405
content_change = (link_or_sha1 != source_details[1])
3406
# Target details is updated at update_entry time
3407
if self.use_filesystem_for_exec:
3408
# We don't need S_ISREG here, because we are sure
3409
# we are dealing with a file.
3410
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
3412
target_exec = target_details[3]
3413
elif target_kind == 'symlink':
3414
if source_minikind != 'l':
3415
content_change = True
3417
content_change = (link_or_sha1 != source_details[1])
3419
elif target_kind == 'tree-reference':
3420
if source_minikind != 't':
3421
content_change = True
3423
content_change = False
3427
path = pathjoin(old_dirname, old_basename)
3428
raise errors.BadFileKindError(path, path_info[2])
3429
if source_minikind == 'd':
3431
old_path = path = pathjoin(old_dirname, old_basename)
3432
self.old_dirname_to_file_id[old_path] = file_id
3433
# parent id is the entry for the path in the target tree
3434
if old_basename and old_dirname == self.last_source_parent[0]:
3435
source_parent_id = self.last_source_parent[1]
3438
source_parent_id = self.old_dirname_to_file_id[old_dirname]
3440
source_parent_entry = self.state._get_entry(self.source_index,
3441
path_utf8=old_dirname)
3442
source_parent_id = source_parent_entry[0][2]
3443
if source_parent_id == entry[0][2]:
3444
# This is the root, so the parent is None
3445
source_parent_id = None
3447
self.last_source_parent[0] = old_dirname
3448
self.last_source_parent[1] = source_parent_id
3449
new_dirname = entry[0][0]
3450
if entry[0][1] and new_dirname == self.last_target_parent[0]:
3451
target_parent_id = self.last_target_parent[1]
3454
target_parent_id = self.new_dirname_to_file_id[new_dirname]
3456
# TODO: We don't always need to do the lookup, because the
3457
# parent entry will be the same as the source entry.
3458
target_parent_entry = self.state._get_entry(self.target_index,
3459
path_utf8=new_dirname)
3460
if target_parent_entry == (None, None):
3461
raise AssertionError(
3462
"Could not find target parent in wt: %s\nparent of: %s"
3463
% (new_dirname, entry))
3464
target_parent_id = target_parent_entry[0][2]
3465
if target_parent_id == entry[0][2]:
3466
# This is the root, so the parent is None
3467
target_parent_id = None
3469
self.last_target_parent[0] = new_dirname
3470
self.last_target_parent[1] = target_parent_id
3472
source_exec = source_details[3]
3473
changed = (content_change
3474
or source_parent_id != target_parent_id
3475
or old_basename != entry[0][1]
3476
or source_exec != target_exec
3478
if not changed and not self.include_unchanged:
3481
if old_path is None:
3482
old_path = path = pathjoin(old_dirname, old_basename)
3483
old_path_u = self.utf8_decode(old_path)[0]
3486
old_path_u = self.utf8_decode(old_path)[0]
3487
if old_path == path:
3490
path_u = self.utf8_decode(path)[0]
3491
source_kind = DirState._minikind_to_kind[source_minikind]
3492
return (entry[0][2],
3493
(old_path_u, path_u),
3496
(source_parent_id, target_parent_id),
3497
(self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3498
(source_kind, target_kind),
3499
(source_exec, target_exec)), changed
3500
elif source_minikind in 'a' and target_minikind in 'fdlt':
3501
# looks like a new file
3502
path = pathjoin(entry[0][0], entry[0][1])
3503
# parent id is the entry for the path in the target tree
3504
# TODO: these are the same for an entire directory: cache em.
3505
parent_id = self.state._get_entry(self.target_index,
3506
path_utf8=entry[0][0])[0][2]
3507
if parent_id == entry[0][2]:
3509
if path_info is not None:
3511
if self.use_filesystem_for_exec:
3512
# We need S_ISREG here, because we aren't sure if this
3515
stat.S_ISREG(path_info[3].st_mode)
3516
and stat.S_IEXEC & path_info[3].st_mode)
3518
target_exec = target_details[3]
3519
return (entry[0][2],
3520
(None, self.utf8_decode(path)[0]),
3524
(None, self.utf8_decode(entry[0][1])[0]),
3525
(None, path_info[2]),
3526
(None, target_exec)), True
3528
# Its a missing file, report it as such.
3529
return (entry[0][2],
3530
(None, self.utf8_decode(path)[0]),
3534
(None, self.utf8_decode(entry[0][1])[0]),
3536
(None, False)), True
3537
elif source_minikind in 'fdlt' and target_minikind in 'a':
3538
# unversioned, possibly, or possibly not deleted: we dont care.
3539
# if its still on disk, *and* theres no other entry at this
3540
# path [we dont know this in this routine at the moment -
3541
# perhaps we should change this - then it would be an unknown.
3542
old_path = pathjoin(entry[0][0], entry[0][1])
3543
# parent id is the entry for the path in the target tree
3544
parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
3545
if parent_id == entry[0][2]:
3547
return (entry[0][2],
3548
(self.utf8_decode(old_path)[0], None),
3552
(self.utf8_decode(entry[0][1])[0], None),
3553
(DirState._minikind_to_kind[source_minikind], None),
3554
(source_details[3], None)), True
3555
elif source_minikind in 'fdlt' and target_minikind in 'r':
3556
# a rename; could be a true rename, or a rename inherited from
3557
# a renamed parent. TODO: handle this efficiently. Its not
3558
# common case to rename dirs though, so a correct but slow
3559
# implementation will do.
3560
if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3561
self.search_specific_files.add(target_details[1])
3562
elif source_minikind in 'ra' and target_minikind in 'ra':
3563
# neither of the selected trees contain this file,
3564
# so skip over it. This is not currently directly tested, but
3565
# is indirectly via test_too_much.TestCommands.test_conflicts.
3568
raise AssertionError("don't know how to compare "
3569
"source_minikind=%r, target_minikind=%r"
3570
% (source_minikind, target_minikind))
3571
## import pdb;pdb.set_trace()
3577
def _gather_result_for_consistency(self, result):
3578
"""Check a result we will yield to make sure we are consistent later.
3580
This gathers result's parents into a set to output later.
3582
:param result: A result tuple.
3584
if not self.partial or not result[0]:
3586
self.seen_ids.add(result[0])
3587
new_path = result[1][1]
3589
# Not the root and not a delete: queue up the parents of the path.
3590
self.search_specific_file_parents.update(
3591
osutils.parent_directories(new_path.encode('utf8')))
3592
# Add the root directory which parent_directories does not
3594
self.search_specific_file_parents.add('')
3596
def iter_changes(self):
3597
"""Iterate over the changes."""
3598
utf8_decode = cache_utf8._utf8_decode
3599
_cmp_by_dirs = cmp_by_dirs
3600
_process_entry = self._process_entry
3601
search_specific_files = self.search_specific_files
3602
searched_specific_files = self.searched_specific_files
3603
splitpath = osutils.splitpath
3605
# compare source_index and target_index at or under each element of search_specific_files.
3606
# follow the following comparison table. Note that we only want to do diff operations when
3607
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
3611
# Source | Target | disk | action
3612
# r | fdlt | | add source to search, add id path move and perform
3613
# | | | diff check on source-target
3614
# r | fdlt | a | dangling file that was present in the basis.
3616
# r | a | | add source to search
3618
# r | r | | this path is present in a non-examined tree, skip.
3619
# r | r | a | this path is present in a non-examined tree, skip.
3620
# a | fdlt | | add new id
3621
# a | fdlt | a | dangling locally added file, skip
3622
# a | a | | not present in either tree, skip
3623
# a | a | a | not present in any tree, skip
3624
# a | r | | not present in either tree at this path, skip as it
3625
# | | | may not be selected by the users list of paths.
3626
# a | r | a | not present in either tree at this path, skip as it
3627
# | | | may not be selected by the users list of paths.
3628
# fdlt | fdlt | | content in both: diff them
3629
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
3630
# fdlt | a | | unversioned: output deleted id for now
3631
# fdlt | a | a | unversioned and deleted: output deleted id
3632
# fdlt | r | | relocated in this tree, so add target to search.
3633
# | | | Dont diff, we will see an r,fd; pair when we reach
3634
# | | | this id at the other path.
3635
# fdlt | r | a | relocated in this tree, so add target to search.
3636
# | | | Dont diff, we will see an r,fd; pair when we reach
3637
# | | | this id at the other path.
3639
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
3640
# keeping a cache of directories that we have seen.
3642
while search_specific_files:
3643
# TODO: the pending list should be lexically sorted? the
3644
# interface doesn't require it.
3645
current_root = search_specific_files.pop()
3646
current_root_unicode = current_root.decode('utf8')
3647
searched_specific_files.add(current_root)
3648
# process the entries for this containing directory: the rest will be
3649
# found by their parents recursively.
3650
root_entries = self.state._entries_for_path(current_root)
3651
root_abspath = self.tree.abspath(current_root_unicode)
3653
root_stat = os.lstat(root_abspath)
3655
if e.errno == errno.ENOENT:
3656
# the path does not exist: let _process_entry know that.
3657
root_dir_info = None
3659
# some other random error: hand it up.
3662
root_dir_info = ('', current_root,
3663
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3665
if root_dir_info[2] == 'directory':
3666
if self.tree._directory_is_tree_reference(
3667
current_root.decode('utf8')):
3668
root_dir_info = root_dir_info[:2] + \
3669
('tree-reference',) + root_dir_info[3:]
3671
if not root_entries and not root_dir_info:
3672
# this specified path is not present at all, skip it.
3674
path_handled = False
3675
for entry in root_entries:
3676
result, changed = _process_entry(entry, root_dir_info)
3677
if changed is not None:
3680
self._gather_result_for_consistency(result)
3681
if changed or self.include_unchanged:
3683
if self.want_unversioned and not path_handled and root_dir_info:
3684
new_executable = bool(
3685
stat.S_ISREG(root_dir_info[3].st_mode)
3686
and stat.S_IEXEC & root_dir_info[3].st_mode)
3688
(None, current_root_unicode),
3692
(None, splitpath(current_root_unicode)[-1]),
3693
(None, root_dir_info[2]),
3694
(None, new_executable)
3696
initial_key = (current_root, '', '')
3697
block_index, _ = self.state._find_block_index_from_key(initial_key)
3698
if block_index == 0:
3699
# we have processed the total root already, but because the
3700
# initial key matched it we should skip it here.
3702
if root_dir_info and root_dir_info[2] == 'tree-reference':
3703
current_dir_info = None
3705
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3707
current_dir_info = dir_iterator.next()
3709
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3710
# python 2.5 has e.errno == EINVAL,
3711
# and e.winerror == ERROR_DIRECTORY
3712
e_winerror = getattr(e, 'winerror', None)
3713
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
3714
# there may be directories in the inventory even though
3715
# this path is not a file on disk: so mark it as end of
3717
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
3718
current_dir_info = None
3719
elif (sys.platform == 'win32'
3720
and (e.errno in win_errors
3721
or e_winerror in win_errors)):
3722
current_dir_info = None
3726
if current_dir_info[0][0] == '':
3727
# remove .bzr from iteration
3728
bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3729
if current_dir_info[1][bzr_index][0] != '.bzr':
3730
raise AssertionError()
3731
del current_dir_info[1][bzr_index]
3732
# walk until both the directory listing and the versioned metadata
3734
if (block_index < len(self.state._dirblocks) and
3735
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3736
current_block = self.state._dirblocks[block_index]
3738
current_block = None
3739
while (current_dir_info is not None or
3740
current_block is not None):
3741
if (current_dir_info and current_block
3742
and current_dir_info[0][0] != current_block[0]):
3743
if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
3744
# filesystem data refers to paths not covered by the dirblock.
3745
# this has two possibilities:
3746
# A) it is versioned but empty, so there is no block for it
3747
# B) it is not versioned.
3749
# if (A) then we need to recurse into it to check for
3750
# new unknown files or directories.
3751
# if (B) then we should ignore it, because we don't
3752
# recurse into unknown directories.
3754
while path_index < len(current_dir_info[1]):
3755
current_path_info = current_dir_info[1][path_index]
3756
if self.want_unversioned:
3757
if current_path_info[2] == 'directory':
3758
if self.tree._directory_is_tree_reference(
3759
current_path_info[0].decode('utf8')):
3760
current_path_info = current_path_info[:2] + \
3761
('tree-reference',) + current_path_info[3:]
3762
new_executable = bool(
3763
stat.S_ISREG(current_path_info[3].st_mode)
3764
and stat.S_IEXEC & current_path_info[3].st_mode)
3766
(None, utf8_decode(current_path_info[0])[0]),
3770
(None, utf8_decode(current_path_info[1])[0]),
3771
(None, current_path_info[2]),
3772
(None, new_executable))
3773
# dont descend into this unversioned path if it is
3775
if current_path_info[2] in ('directory',
3777
del current_dir_info[1][path_index]
3781
# This dir info has been handled, go to the next
3783
current_dir_info = dir_iterator.next()
3784
except StopIteration:
3785
current_dir_info = None
3787
# We have a dirblock entry for this location, but there
3788
# is no filesystem path for this. This is most likely
3789
# because a directory was removed from the disk.
3790
# We don't have to report the missing directory,
3791
# because that should have already been handled, but we
3792
# need to handle all of the files that are contained
3794
for current_entry in current_block[1]:
3795
# entry referring to file not present on disk.
3796
# advance the entry only, after processing.
3797
result, changed = _process_entry(current_entry, None)
3798
if changed is not None:
3800
self._gather_result_for_consistency(result)
3801
if changed or self.include_unchanged:
3804
if (block_index < len(self.state._dirblocks) and
3805
osutils.is_inside(current_root,
3806
self.state._dirblocks[block_index][0])):
3807
current_block = self.state._dirblocks[block_index]
3809
current_block = None
3812
if current_block and entry_index < len(current_block[1]):
3813
current_entry = current_block[1][entry_index]
3815
current_entry = None
3816
advance_entry = True
3818
if current_dir_info and path_index < len(current_dir_info[1]):
3819
current_path_info = current_dir_info[1][path_index]
3820
if current_path_info[2] == 'directory':
3821
if self.tree._directory_is_tree_reference(
3822
current_path_info[0].decode('utf8')):
3823
current_path_info = current_path_info[:2] + \
3824
('tree-reference',) + current_path_info[3:]
3826
current_path_info = None
3828
path_handled = False
3829
while (current_entry is not None or
3830
current_path_info is not None):
3831
if current_entry is None:
3832
# the check for path_handled when the path is advanced
3833
# will yield this path if needed.
3835
elif current_path_info is None:
3836
# no path is fine: the per entry code will handle it.
3837
result, changed = _process_entry(current_entry, current_path_info)
3838
if changed is not None:
3840
self._gather_result_for_consistency(result)
3841
if changed or self.include_unchanged:
3843
elif (current_entry[0][1] != current_path_info[1]
3844
or current_entry[1][self.target_index][0] in 'ar'):
3845
# The current path on disk doesn't match the dirblock
3846
# record. Either the dirblock is marked as absent, or
3847
# the file on disk is not present at all in the
3848
# dirblock. Either way, report about the dirblock
3849
# entry, and let other code handle the filesystem one.
3851
# Compare the basename for these files to determine
3853
if current_path_info[1] < current_entry[0][1]:
3854
# extra file on disk: pass for now, but only
3855
# increment the path, not the entry
3856
advance_entry = False
3858
# entry referring to file not present on disk.
3859
# advance the entry only, after processing.
3860
result, changed = _process_entry(current_entry, None)
3861
if changed is not None:
3863
self._gather_result_for_consistency(result)
3864
if changed or self.include_unchanged:
3866
advance_path = False
3868
result, changed = _process_entry(current_entry, current_path_info)
3869
if changed is not None:
3872
self._gather_result_for_consistency(result)
3873
if changed or self.include_unchanged:
3875
if advance_entry and current_entry is not None:
3877
if entry_index < len(current_block[1]):
3878
current_entry = current_block[1][entry_index]
3880
current_entry = None
3882
advance_entry = True # reset the advance flaga
3883
if advance_path and current_path_info is not None:
3884
if not path_handled:
3885
# unversioned in all regards
3886
if self.want_unversioned:
3887
new_executable = bool(
3888
stat.S_ISREG(current_path_info[3].st_mode)
3889
and stat.S_IEXEC & current_path_info[3].st_mode)
3891
relpath_unicode = utf8_decode(current_path_info[0])[0]
3892
except UnicodeDecodeError:
3893
raise errors.BadFilenameEncoding(
3894
current_path_info[0], osutils._fs_enc)
3896
(None, relpath_unicode),
3900
(None, utf8_decode(current_path_info[1])[0]),
3901
(None, current_path_info[2]),
3902
(None, new_executable))
3903
# dont descend into this unversioned path if it is
3905
if current_path_info[2] in ('directory'):
3906
del current_dir_info[1][path_index]
3908
# dont descend the disk iterator into any tree
3910
if current_path_info[2] == 'tree-reference':
3911
del current_dir_info[1][path_index]
3914
if path_index < len(current_dir_info[1]):
3915
current_path_info = current_dir_info[1][path_index]
3916
if current_path_info[2] == 'directory':
3917
if self.tree._directory_is_tree_reference(
3918
current_path_info[0].decode('utf8')):
3919
current_path_info = current_path_info[:2] + \
3920
('tree-reference',) + current_path_info[3:]
3922
current_path_info = None
3923
path_handled = False
3925
advance_path = True # reset the advance flagg.
3926
if current_block is not None:
3928
if (block_index < len(self.state._dirblocks) and
3929
osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3930
current_block = self.state._dirblocks[block_index]
3932
current_block = None
3933
if current_dir_info is not None:
3935
current_dir_info = dir_iterator.next()
3936
except StopIteration:
3937
current_dir_info = None
3938
for result in self._iter_specific_file_parents():
3941
def _iter_specific_file_parents(self):
3942
"""Iter over the specific file parents."""
3943
while self.search_specific_file_parents:
3944
# Process the parent directories for the paths we were iterating.
3945
# Even in extremely large trees this should be modest, so currently
3946
# no attempt is made to optimise.
3947
path_utf8 = self.search_specific_file_parents.pop()
3948
if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3949
# We've examined this path.
3951
if path_utf8 in self.searched_exact_paths:
3952
# We've examined this path.
3954
path_entries = self.state._entries_for_path(path_utf8)
3955
# We need either one or two entries. If the path in
3956
# self.target_index has moved (so the entry in source_index is in
3957
# 'ar') then we need to also look for the entry for this path in
3958
# self.source_index, to output the appropriate delete-or-rename.
3959
selected_entries = []
3961
for candidate_entry in path_entries:
3962
# Find entries present in target at this path:
3963
if candidate_entry[1][self.target_index][0] not in 'ar':
3965
selected_entries.append(candidate_entry)
3966
# Find entries present in source at this path:
3967
elif (self.source_index is not None and
3968
candidate_entry[1][self.source_index][0] not in 'ar'):
3970
if candidate_entry[1][self.target_index][0] == 'a':
3971
# Deleted, emit it here.
3972
selected_entries.append(candidate_entry)
3974
# renamed, emit it when we process the directory it
3976
self.search_specific_file_parents.add(
3977
candidate_entry[1][self.target_index][1])
3979
raise AssertionError(
3980
"Missing entry for specific path parent %r, %r" % (
3981
path_utf8, path_entries))
3982
path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3983
for entry in selected_entries:
3984
if entry[0][2] in self.seen_ids:
3986
result, changed = self._process_entry(entry, path_info)
3988
raise AssertionError(
3989
"Got entry<->path mismatch for specific path "
3990
"%r entry %r path_info %r " % (
3991
path_utf8, entry, path_info))
3992
# Only include changes - we're outside the users requested
3995
self._gather_result_for_consistency(result)
3996
if (result[6][0] == 'directory' and
3997
result[6][1] != 'directory'):
3998
# This stopped being a directory, the old children have
4000
if entry[1][self.source_index][0] == 'r':
4001
# renamed, take the source path
4002
entry_path_utf8 = entry[1][self.source_index][1]
4004
entry_path_utf8 = path_utf8
4005
initial_key = (entry_path_utf8, '', '')
4006
block_index, _ = self.state._find_block_index_from_key(
4008
if block_index == 0:
4009
# The children of the root are in block index 1.
4011
current_block = None
4012
if block_index < len(self.state._dirblocks):
4013
current_block = self.state._dirblocks[block_index]
4014
if not osutils.is_inside(
4015
entry_path_utf8, current_block[0]):
4016
# No entries for this directory at all.
4017
current_block = None
4018
if current_block is not None:
4019
for entry in current_block[1]:
4020
if entry[1][self.source_index][0] in 'ar':
4021
# Not in the source tree, so doesn't have to be
4024
# Path of the entry itself.
4026
self.search_specific_file_parents.add(
4027
osutils.pathjoin(*entry[0][:2]))
4028
if changed or self.include_unchanged:
4030
self.searched_exact_paths.add(path_utf8)
4032
def _path_info(self, utf8_path, unicode_path):
4033
"""Generate path_info for unicode_path.
4035
:return: None if unicode_path does not exist, or a path_info tuple.
4037
abspath = self.tree.abspath(unicode_path)
4039
stat = os.lstat(abspath)
4041
if e.errno == errno.ENOENT:
4042
# the path does not exist.
4046
utf8_basename = utf8_path.rsplit('/', 1)[-1]
4047
dir_info = (utf8_path, utf8_basename,
4048
osutils.file_kind_from_stat_mode(stat.st_mode), stat,
4050
if dir_info[2] == 'directory':
4051
if self.tree._directory_is_tree_reference(
4053
self.root_dir_info = self.root_dir_info[:2] + \
4054
('tree-reference',) + self.root_dir_info[3:]
4058
2591
# Try to load the compiled form if possible
4060
from bzrlib._dirstate_helpers_pyx import (
4066
ProcessEntryC as _process_entry,
4067
update_entry as update_entry,
2593
from bzrlib._dirstate_helpers_c import (
2594
_read_dirblocks_c as _read_dirblocks,
2595
bisect_dirblock_c as bisect_dirblock,
2596
_bisect_path_left_c as _bisect_path_left,
2597
_bisect_path_right_c as _bisect_path_right,
2598
cmp_by_dirs_c as cmp_by_dirs,
4069
except ImportError, e:
4070
osutils.failed_to_load_extension(e)
4071
2601
from bzrlib._dirstate_helpers_py import (
2602
_read_dirblocks_py as _read_dirblocks,
2603
bisect_dirblock_py as bisect_dirblock,
2604
_bisect_path_left_py as _bisect_path_left,
2605
_bisect_path_right_py as _bisect_path_right,
2606
cmp_by_dirs_py as cmp_by_dirs,
4078
# FIXME: It would be nice to be able to track moved lines so that the
4079
# corresponding python code can be moved to the _dirstate_helpers_py
4080
# module. I don't want to break the history for this important piece of
4081
# code so I left the code here -- vila 20090622
4082
update_entry = py_update_entry
4083
_process_entry = ProcessEntryPython