~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: Martin Pool
  • Date: 2009-09-14 01:48:28 UTC
  • mfrom: (4685 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4688.
  • Revision ID: mbp@sourcefrog.net-20090914014828-ydr9rlkdfq2sv57z
Merge news

Show diffs side-by-side

added added

removed removed

Lines of Context:
3164
3164
 
3165
3165
class ProcessEntryPython(object):
3166
3166
 
3167
 
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
 
3167
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3168
3168
        "last_source_parent", "last_target_parent", "include_unchanged",
3169
 
        "use_filesystem_for_exec", "utf8_decode", "searched_specific_files",
3170
 
        "search_specific_files", "state", "source_index", "target_index",
3171
 
        "want_unversioned", "tree"]
 
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"]
3172
3173
 
3173
3174
    def __init__(self, include_unchanged, use_filesystem_for_exec,
3174
3175
        search_specific_files, state, source_index, target_index,
3175
3176
        want_unversioned, tree):
3176
3177
        self.old_dirname_to_file_id = {}
3177
3178
        self.new_dirname_to_file_id = {}
3178
 
        # Just a sentry, so that _process_entry can say that this
3179
 
        # record is handled, but isn't interesting to process (unchanged)
3180
 
        self.uninteresting = object()
 
3179
        # Are we doing a partial iter_changes?
 
3180
        self.partial = search_specific_files != set([''])
3181
3181
        # Using a list so that we can access the values and change them in
3182
3182
        # nested scope. Each one is [path, file_id, entry]
3183
3183
        self.last_source_parent = [None, None]
3186
3186
        self.use_filesystem_for_exec = use_filesystem_for_exec
3187
3187
        self.utf8_decode = cache_utf8._utf8_decode
3188
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 add the
3190
 
        # relocated path as one to search if its not searched already. If the
3191
 
        # detail is not relocated, add the id.
 
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
3192
        self.searched_specific_files = set()
 
3193
        # When we search exact paths without expanding downwards, we record
 
3194
        # that here.
 
3195
        self.searched_exact_paths = set()
3193
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()
3194
3202
        self.state = state
3195
3203
        self.source_index = source_index
3196
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')
3197
3208
        self.want_unversioned = want_unversioned
3198
3209
        self.tree = tree
3199
3210
 
3201
3212
        """Compare an entry and real disk to generate delta information.
3202
3213
 
3203
3214
        :param path_info: top_relpath, basename, kind, lstat, abspath for
3204
 
            the path of entry. If None, then the path is considered absent.
3205
 
            (Perhaps we should pass in a concrete entry for this ?)
 
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 ?)
3206
3217
            Basename is returned as a utf8 string because we expect this
3207
3218
            tuple will be ignored, and don't want to take the time to
3208
3219
            decode.
3209
 
        :return: None if these don't match
3210
 
                 A tuple of information about the change, or
3211
 
                 the object 'uninteresting' if these match, but are
3212
 
                 basically identical.
 
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
 
3226
            True.
3213
3227
        """
3214
3228
        if self.source_index is None:
3215
3229
            source_details = DirState.NULL_PARENT_DETAILS
3320
3334
                    old_path = path = pathjoin(old_dirname, old_basename)
3321
3335
                self.old_dirname_to_file_id[old_path] = file_id
3322
3336
            # parent id is the entry for the path in the target tree
3323
 
            if old_dirname == self.last_source_parent[0]:
 
3337
            if old_basename and old_dirname == self.last_source_parent[0]:
3324
3338
                source_parent_id = self.last_source_parent[1]
3325
3339
            else:
3326
3340
                try:
3336
3350
                    self.last_source_parent[0] = old_dirname
3337
3351
                    self.last_source_parent[1] = source_parent_id
3338
3352
            new_dirname = entry[0][0]
3339
 
            if new_dirname == self.last_target_parent[0]:
 
3353
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
3340
3354
                target_parent_id = self.last_target_parent[1]
3341
3355
            else:
3342
3356
                try:
3359
3373
                    self.last_target_parent[1] = target_parent_id
3360
3374
 
3361
3375
            source_exec = source_details[3]
3362
 
            if (self.include_unchanged
3363
 
                or content_change
 
3376
            changed = (content_change
3364
3377
                or source_parent_id != target_parent_id
3365
3378
                or old_basename != entry[0][1]
3366
3379
                or source_exec != target_exec
3367
 
                ):
 
3380
                )
 
3381
            if not changed and not self.include_unchanged:
 
3382
                return None, False
 
3383
            else:
3368
3384
                if old_path is None:
3369
3385
                    old_path = path = pathjoin(old_dirname, old_basename)
3370
3386
                    old_path_u = self.utf8_decode(old_path)[0]
3383
3399
                       (source_parent_id, target_parent_id),
3384
3400
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3385
3401
                       (source_kind, target_kind),
3386
 
                       (source_exec, target_exec))
3387
 
            else:
3388
 
                return self.uninteresting
 
3402
                       (source_exec, target_exec)), changed
3389
3403
        elif source_minikind in 'a' and target_minikind in 'fdlt':
3390
3404
            # looks like a new file
3391
3405
            path = pathjoin(entry[0][0], entry[0][1])
3412
3426
                       (None, parent_id),
3413
3427
                       (None, self.utf8_decode(entry[0][1])[0]),
3414
3428
                       (None, path_info[2]),
3415
 
                       (None, target_exec))
 
3429
                       (None, target_exec)), True
3416
3430
            else:
3417
3431
                # Its a missing file, report it as such.
3418
3432
                return (entry[0][2],
3422
3436
                       (None, parent_id),
3423
3437
                       (None, self.utf8_decode(entry[0][1])[0]),
3424
3438
                       (None, None),
3425
 
                       (None, False))
 
3439
                       (None, False)), True
3426
3440
        elif source_minikind in 'fdlt' and target_minikind in 'a':
3427
3441
            # unversioned, possibly, or possibly not deleted: we dont care.
3428
3442
            # if its still on disk, *and* theres no other entry at this
3440
3454
                   (parent_id, None),
3441
3455
                   (self.utf8_decode(entry[0][1])[0], None),
3442
3456
                   (DirState._minikind_to_kind[source_minikind], None),
3443
 
                   (source_details[3], None))
 
3457
                   (source_details[3], None)), True
3444
3458
        elif source_minikind in 'fdlt' and target_minikind in 'r':
3445
3459
            # a rename; could be a true rename, or a rename inherited from
3446
3460
            # a renamed parent. TODO: handle this efficiently. Its not
3458
3472
                "source_minikind=%r, target_minikind=%r"
3459
3473
                % (source_minikind, target_minikind))
3460
3474
            ## import pdb;pdb.set_trace()
3461
 
        return None
 
3475
        return None, None
3462
3476
 
3463
3477
    def __iter__(self):
3464
3478
        return self
3465
3479
 
 
3480
    def _gather_result_for_consistency(self, result):
 
3481
        """Check a result we will yield to make sure we are consistent later.
 
3482
        
 
3483
        This gathers result's parents into a set to output later.
 
3484
 
 
3485
        :param result: A result tuple.
 
3486
        """
 
3487
        if not self.partial or not result[0]:
 
3488
            return
 
3489
        self.seen_ids.add(result[0])
 
3490
        new_path = result[1][1]
 
3491
        if new_path:
 
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
 
3496
            # provide.
 
3497
            self.search_specific_file_parents.add('')
 
3498
 
3466
3499
    def iter_changes(self):
3467
3500
        """Iterate over the changes."""
3468
3501
        utf8_decode = cache_utf8._utf8_decode
3469
3502
        _cmp_by_dirs = cmp_by_dirs
3470
3503
        _process_entry = self._process_entry
3471
 
        uninteresting = self.uninteresting
3472
3504
        search_specific_files = self.search_specific_files
3473
3505
        searched_specific_files = self.searched_specific_files
3474
3506
        splitpath = osutils.splitpath
3544
3576
                continue
3545
3577
            path_handled = False
3546
3578
            for entry in root_entries:
3547
 
                result = _process_entry(entry, root_dir_info)
3548
 
                if result is not None:
 
3579
                result, changed = _process_entry(entry, root_dir_info)
 
3580
                if changed is not None:
3549
3581
                    path_handled = True
3550
 
                    if result is not uninteresting:
 
3582
                    if changed:
 
3583
                        self._gather_result_for_consistency(result)
 
3584
                    if changed or self.include_unchanged:
3551
3585
                        yield result
3552
3586
            if self.want_unversioned and not path_handled and root_dir_info:
3553
3587
                new_executable = bool(
3663
3697
                        for current_entry in current_block[1]:
3664
3698
                            # entry referring to file not present on disk.
3665
3699
                            # advance the entry only, after processing.
3666
 
                            result = _process_entry(current_entry, None)
3667
 
                            if result is not None:
3668
 
                                if result is not uninteresting:
 
3700
                            result, changed = _process_entry(current_entry, None)
 
3701
                            if changed is not None:
 
3702
                                if changed:
 
3703
                                    self._gather_result_for_consistency(result)
 
3704
                                if changed or self.include_unchanged:
3669
3705
                                    yield result
3670
3706
                        block_index +=1
3671
3707
                        if (block_index < len(self.state._dirblocks) and
3701
3737
                        pass
3702
3738
                    elif current_path_info is None:
3703
3739
                        # no path is fine: the per entry code will handle it.
3704
 
                        result = _process_entry(current_entry, current_path_info)
3705
 
                        if result is not None:
3706
 
                            if result is not uninteresting:
 
3740
                        result, changed = _process_entry(current_entry, current_path_info)
 
3741
                        if changed is not None:
 
3742
                            if changed:
 
3743
                                self._gather_result_for_consistency(result)
 
3744
                            if changed or self.include_unchanged:
3707
3745
                                yield result
3708
3746
                    elif (current_entry[0][1] != current_path_info[1]
3709
3747
                          or current_entry[1][self.target_index][0] in 'ar'):
3722
3760
                        else:
3723
3761
                            # entry referring to file not present on disk.
3724
3762
                            # advance the entry only, after processing.
3725
 
                            result = _process_entry(current_entry, None)
3726
 
                            if result is not None:
3727
 
                                if result is not uninteresting:
 
3763
                            result, changed = _process_entry(current_entry, None)
 
3764
                            if changed is not None:
 
3765
                                if changed:
 
3766
                                    self._gather_result_for_consistency(result)
 
3767
                                if changed or self.include_unchanged:
3728
3768
                                    yield result
3729
3769
                            advance_path = False
3730
3770
                    else:
3731
 
                        result = _process_entry(current_entry, current_path_info)
3732
 
                        if result is not None:
 
3771
                        result, changed = _process_entry(current_entry, current_path_info)
 
3772
                        if changed is not None:
3733
3773
                            path_handled = True
3734
 
                            if result is not uninteresting:
 
3774
                            if changed:
 
3775
                                self._gather_result_for_consistency(result)
 
3776
                            if changed or self.include_unchanged:
3735
3777
                                yield result
3736
3778
                    if advance_entry and current_entry is not None:
3737
3779
                        entry_index += 1
3796
3838
                        current_dir_info = dir_iterator.next()
3797
3839
                    except StopIteration:
3798
3840
                        current_dir_info = None
 
3841
        for result in self._iter_specific_file_parents():
 
3842
            yield result
 
3843
 
 
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.
 
3853
                continue
 
3854
            if path_utf8 in self.searched_exact_paths:
 
3855
                # We've examined this path.
 
3856
                continue
 
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 = []
 
3863
            found_item = False
 
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':
 
3867
                    found_item = True
 
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'):
 
3872
                    found_item = True
 
3873
                    if candidate_entry[1][self.target_index][0] == 'a':
 
3874
                        # Deleted, emit it here.
 
3875
                        selected_entries.append(candidate_entry)
 
3876
                    else:
 
3877
                        # renamed, emit it when we process the directory it
 
3878
                        # ended up at.
 
3879
                        self.search_specific_file_parents.add(
 
3880
                            candidate_entry[1][self.target_index][1])
 
3881
            if not found_item:
 
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:
 
3888
                    continue
 
3889
                result, changed = self._process_entry(entry, path_info)
 
3890
                if changed is None:
 
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
 
3896
                # expansion.
 
3897
                if changed:
 
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
 
3902
                        # to be included.
 
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]
 
3906
                        else:
 
3907
                            entry_path_utf8 = path_utf8
 
3908
                        initial_key = (entry_path_utf8, '', '')
 
3909
                        block_index, _ = self.state._find_block_index_from_key(
 
3910
                            initial_key)
 
3911
                        if block_index == 0:
 
3912
                            # The children of the root are in block index 1.
 
3913
                            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
 
3925
                                    # included.
 
3926
                                    continue
 
3927
                                # Path of the entry itself.
 
3928
 
 
3929
                                self.search_specific_file_parents.add(
 
3930
                                    osutils.pathjoin(*entry[0][:2]))
 
3931
                if changed or self.include_unchanged:
 
3932
                    yield result
 
3933
            self.searched_exact_paths.add(path_utf8)
 
3934
 
 
3935
    def _path_info(self, utf8_path, unicode_path):
 
3936
        """Generate path_info for unicode_path.
 
3937
 
 
3938
        :return: None if unicode_path does not exist, or a path_info tuple.
 
3939
        """
 
3940
        abspath = self.tree.abspath(unicode_path)
 
3941
        try:
 
3942
            stat = os.lstat(abspath)
 
3943
        except OSError, e:
 
3944
            if e.errno == errno.ENOENT:
 
3945
                # the path does not exist.
 
3946
                return None
 
3947
            else:
 
3948
                raise
 
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,
 
3952
            abspath)
 
3953
        if dir_info[2] == 'directory':
 
3954
            if self.tree._directory_is_tree_reference(
 
3955
                unicode_path):
 
3956
                self.root_dir_info = self.root_dir_info[:2] + \
 
3957
                    ('tree-reference',) + self.root_dir_info[3:]
 
3958
        return dir_info
3799
3959
 
3800
3960
 
3801
3961
# Try to load the compiled form if possible