~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: John Arbash Meinel
  • Date: 2010-07-13 07:44:02 UTC
  • mto: This revision was merged to the branch mainline in revision 5342.
  • Revision ID: john@arbash-meinel.com-20100713074402-wp3oh7cyx76fkvmm
Bump trunk to 2.3-dev1

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
1324
1324
                key = (dirname_utf8, basename, file_id)
1325
1325
                minikind = DirState._kind_to_minikind[inv_entry.kind]
1326
1326
                if minikind == 't':
1327
 
                    fingerprint = inv_entry.reference_revision
 
1327
                    fingerprint = inv_entry.reference_revision or ''
1328
1328
                else:
1329
1329
                    fingerprint = ''
1330
1330
                insertions[file_id] = (key, minikind, inv_entry.executable,
1339
1339
                    minikind = child[1][0][0]
1340
1340
                    fingerprint = child[1][0][4]
1341
1341
                    executable = child[1][0][3]
1342
 
                    old_child_path = osutils.pathjoin(child[0][0],
1343
 
                                                      child[0][1])
 
1342
                    old_child_path = osutils.pathjoin(child_dirname,
 
1343
                                                      child_basename)
1344
1344
                    removals[child[0][2]] = old_child_path
1345
1345
                    child_suffix = child_dirname[len(old_path):]
1346
1346
                    new_child_dirname = (new_path + child_suffix)
1347
1347
                    key = (new_child_dirname, child_basename, child[0][2])
1348
 
                    new_child_path = os.path.join(new_child_dirname,
1349
 
                                                  child_basename)
 
1348
                    new_child_path = osutils.pathjoin(new_child_dirname,
 
1349
                                                      child_basename)
1350
1350
                    insertions[child[0][2]] = (key, minikind, executable,
1351
1351
                                               fingerprint, new_child_path)
1352
1352
        self._check_delta_ids_absent(new_ids, delta, 0)
1997
1997
                entry_index, present = self._find_entry_index(key, block)
1998
1998
                if present:
1999
1999
                    entry = self._dirblocks[block_index][1][entry_index]
 
2000
                    # TODO: We might want to assert that entry[0][2] ==
 
2001
                    #       fileid_utf8.
2000
2002
                    if entry[1][tree_index][0] in 'fdlt':
2001
2003
                        # this is the result we are looking for: the
2002
2004
                        # real home of this file_id in this tree.
2354
2356
        self.update_minimal(('', '', new_id), 'd',
2355
2357
            path_utf8='', packed_stat=entry[1][0][4])
2356
2358
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2357
 
        if self._id_index is not None:
2358
 
            self._id_index.setdefault(new_id, set()).add(entry[0])
2359
2359
 
2360
2360
    def set_parent_trees(self, trees, ghosts):
2361
2361
        """Set the parent trees for the dirstate.
3013
3013
            if absent_positions == tree_count:
3014
3014
                raise AssertionError(
3015
3015
                    "entry %r has no data for any tree." % (entry,))
 
3016
        if self._id_index is not None:
 
3017
            for file_id, entry_keys in self._id_index.iteritems():
 
3018
                for entry_key in entry_keys:
 
3019
                    if entry_key[2] != file_id:
 
3020
                        raise AssertionError(
 
3021
                            'file_id %r did not match entry key %s'
 
3022
                            % (file_id, entry_key))
3016
3023
 
3017
3024
    def _wipe_state(self):
3018
3025
        """Forget all state information about the dirstate."""
3164
3171
 
3165
3172
class ProcessEntryPython(object):
3166
3173
 
3167
 
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
 
3174
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3168
3175
        "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"]
 
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"]
3172
3180
 
3173
3181
    def __init__(self, include_unchanged, use_filesystem_for_exec,
3174
3182
        search_specific_files, state, source_index, target_index,
3175
3183
        want_unversioned, tree):
3176
3184
        self.old_dirname_to_file_id = {}
3177
3185
        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()
 
3186
        # Are we doing a partial iter_changes?
 
3187
        self.partial = search_specific_files != set([''])
3181
3188
        # Using a list so that we can access the values and change them in
3182
3189
        # nested scope. Each one is [path, file_id, entry]
3183
3190
        self.last_source_parent = [None, None]
3186
3193
        self.use_filesystem_for_exec = use_filesystem_for_exec
3187
3194
        self.utf8_decode = cache_utf8._utf8_decode
3188
3195
        # 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.
 
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.
3192
3199
        self.searched_specific_files = set()
 
3200
        # When we search exact paths without expanding downwards, we record
 
3201
        # that here.
 
3202
        self.searched_exact_paths = set()
3193
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()
3194
3209
        self.state = state
3195
3210
        self.source_index = source_index
3196
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')
3197
3215
        self.want_unversioned = want_unversioned
3198
3216
        self.tree = tree
3199
3217
 
3201
3219
        """Compare an entry and real disk to generate delta information.
3202
3220
 
3203
3221
        :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 ?)
 
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 ?)
3206
3224
            Basename is returned as a utf8 string because we expect this
3207
3225
            tuple will be ignored, and don't want to take the time to
3208
3226
            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.
 
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
 
3233
            True.
3213
3234
        """
3214
3235
        if self.source_index is None:
3215
3236
            source_details = DirState.NULL_PARENT_DETAILS
3314
3335
                        content_change = False
3315
3336
                    target_exec = False
3316
3337
                else:
3317
 
                    raise Exception, "unknown kind %s" % path_info[2]
 
3338
                    if path is None:
 
3339
                        path = pathjoin(old_dirname, old_basename)
 
3340
                    raise errors.BadFileKindError(path, path_info[2])
3318
3341
            if source_minikind == 'd':
3319
3342
                if path is None:
3320
3343
                    old_path = path = pathjoin(old_dirname, old_basename)
3321
3344
                self.old_dirname_to_file_id[old_path] = file_id
3322
3345
            # parent id is the entry for the path in the target tree
3323
 
            if old_dirname == self.last_source_parent[0]:
 
3346
            if old_basename and old_dirname == self.last_source_parent[0]:
3324
3347
                source_parent_id = self.last_source_parent[1]
3325
3348
            else:
3326
3349
                try:
3336
3359
                    self.last_source_parent[0] = old_dirname
3337
3360
                    self.last_source_parent[1] = source_parent_id
3338
3361
            new_dirname = entry[0][0]
3339
 
            if new_dirname == self.last_target_parent[0]:
 
3362
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
3340
3363
                target_parent_id = self.last_target_parent[1]
3341
3364
            else:
3342
3365
                try:
3359
3382
                    self.last_target_parent[1] = target_parent_id
3360
3383
 
3361
3384
            source_exec = source_details[3]
3362
 
            if (self.include_unchanged
3363
 
                or content_change
 
3385
            changed = (content_change
3364
3386
                or source_parent_id != target_parent_id
3365
3387
                or old_basename != entry[0][1]
3366
3388
                or source_exec != target_exec
3367
 
                ):
 
3389
                )
 
3390
            if not changed and not self.include_unchanged:
 
3391
                return None, False
 
3392
            else:
3368
3393
                if old_path is None:
3369
3394
                    old_path = path = pathjoin(old_dirname, old_basename)
3370
3395
                    old_path_u = self.utf8_decode(old_path)[0]
3383
3408
                       (source_parent_id, target_parent_id),
3384
3409
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3385
3410
                       (source_kind, target_kind),
3386
 
                       (source_exec, target_exec))
3387
 
            else:
3388
 
                return self.uninteresting
 
3411
                       (source_exec, target_exec)), changed
3389
3412
        elif source_minikind in 'a' and target_minikind in 'fdlt':
3390
3413
            # looks like a new file
3391
3414
            path = pathjoin(entry[0][0], entry[0][1])
3412
3435
                       (None, parent_id),
3413
3436
                       (None, self.utf8_decode(entry[0][1])[0]),
3414
3437
                       (None, path_info[2]),
3415
 
                       (None, target_exec))
 
3438
                       (None, target_exec)), True
3416
3439
            else:
3417
3440
                # Its a missing file, report it as such.
3418
3441
                return (entry[0][2],
3422
3445
                       (None, parent_id),
3423
3446
                       (None, self.utf8_decode(entry[0][1])[0]),
3424
3447
                       (None, None),
3425
 
                       (None, False))
 
3448
                       (None, False)), True
3426
3449
        elif source_minikind in 'fdlt' and target_minikind in 'a':
3427
3450
            # unversioned, possibly, or possibly not deleted: we dont care.
3428
3451
            # if its still on disk, *and* theres no other entry at this
3440
3463
                   (parent_id, None),
3441
3464
                   (self.utf8_decode(entry[0][1])[0], None),
3442
3465
                   (DirState._minikind_to_kind[source_minikind], None),
3443
 
                   (source_details[3], None))
 
3466
                   (source_details[3], None)), True
3444
3467
        elif source_minikind in 'fdlt' and target_minikind in 'r':
3445
3468
            # a rename; could be a true rename, or a rename inherited from
3446
3469
            # a renamed parent. TODO: handle this efficiently. Its not
3458
3481
                "source_minikind=%r, target_minikind=%r"
3459
3482
                % (source_minikind, target_minikind))
3460
3483
            ## import pdb;pdb.set_trace()
3461
 
        return None
 
3484
        return None, None
3462
3485
 
3463
3486
    def __iter__(self):
3464
3487
        return self
3465
3488
 
 
3489
    def _gather_result_for_consistency(self, result):
 
3490
        """Check a result we will yield to make sure we are consistent later.
 
3491
        
 
3492
        This gathers result's parents into a set to output later.
 
3493
 
 
3494
        :param result: A result tuple.
 
3495
        """
 
3496
        if not self.partial or not result[0]:
 
3497
            return
 
3498
        self.seen_ids.add(result[0])
 
3499
        new_path = result[1][1]
 
3500
        if new_path:
 
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
 
3505
            # provide.
 
3506
            self.search_specific_file_parents.add('')
 
3507
 
3466
3508
    def iter_changes(self):
3467
3509
        """Iterate over the changes."""
3468
3510
        utf8_decode = cache_utf8._utf8_decode
3469
3511
        _cmp_by_dirs = cmp_by_dirs
3470
3512
        _process_entry = self._process_entry
3471
 
        uninteresting = self.uninteresting
3472
3513
        search_specific_files = self.search_specific_files
3473
3514
        searched_specific_files = self.searched_specific_files
3474
3515
        splitpath = osutils.splitpath
3544
3585
                continue
3545
3586
            path_handled = False
3546
3587
            for entry in root_entries:
3547
 
                result = _process_entry(entry, root_dir_info)
3548
 
                if result is not None:
 
3588
                result, changed = _process_entry(entry, root_dir_info)
 
3589
                if changed is not None:
3549
3590
                    path_handled = True
3550
 
                    if result is not uninteresting:
 
3591
                    if changed:
 
3592
                        self._gather_result_for_consistency(result)
 
3593
                    if changed or self.include_unchanged:
3551
3594
                        yield result
3552
3595
            if self.want_unversioned and not path_handled and root_dir_info:
3553
3596
                new_executable = bool(
3663
3706
                        for current_entry in current_block[1]:
3664
3707
                            # entry referring to file not present on disk.
3665
3708
                            # 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:
 
3709
                            result, changed = _process_entry(current_entry, None)
 
3710
                            if changed is not None:
 
3711
                                if changed:
 
3712
                                    self._gather_result_for_consistency(result)
 
3713
                                if changed or self.include_unchanged:
3669
3714
                                    yield result
3670
3715
                        block_index +=1
3671
3716
                        if (block_index < len(self.state._dirblocks) and
3701
3746
                        pass
3702
3747
                    elif current_path_info is None:
3703
3748
                        # 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:
 
3749
                        result, changed = _process_entry(current_entry, current_path_info)
 
3750
                        if changed is not None:
 
3751
                            if changed:
 
3752
                                self._gather_result_for_consistency(result)
 
3753
                            if changed or self.include_unchanged:
3707
3754
                                yield result
3708
3755
                    elif (current_entry[0][1] != current_path_info[1]
3709
3756
                          or current_entry[1][self.target_index][0] in 'ar'):
3722
3769
                        else:
3723
3770
                            # entry referring to file not present on disk.
3724
3771
                            # 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:
 
3772
                            result, changed = _process_entry(current_entry, None)
 
3773
                            if changed is not None:
 
3774
                                if changed:
 
3775
                                    self._gather_result_for_consistency(result)
 
3776
                                if changed or self.include_unchanged:
3728
3777
                                    yield result
3729
3778
                            advance_path = False
3730
3779
                    else:
3731
 
                        result = _process_entry(current_entry, current_path_info)
3732
 
                        if result is not None:
 
3780
                        result, changed = _process_entry(current_entry, current_path_info)
 
3781
                        if changed is not None:
3733
3782
                            path_handled = True
3734
 
                            if result is not uninteresting:
 
3783
                            if changed:
 
3784
                                self._gather_result_for_consistency(result)
 
3785
                            if changed or self.include_unchanged:
3735
3786
                                yield result
3736
3787
                    if advance_entry and current_entry is not None:
3737
3788
                        entry_index += 1
3796
3847
                        current_dir_info = dir_iterator.next()
3797
3848
                    except StopIteration:
3798
3849
                        current_dir_info = None
 
3850
        for result in self._iter_specific_file_parents():
 
3851
            yield result
 
3852
 
 
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.
 
3862
                continue
 
3863
            if path_utf8 in self.searched_exact_paths:
 
3864
                # We've examined this path.
 
3865
                continue
 
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 = []
 
3872
            found_item = False
 
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':
 
3876
                    found_item = True
 
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'):
 
3881
                    found_item = True
 
3882
                    if candidate_entry[1][self.target_index][0] == 'a':
 
3883
                        # Deleted, emit it here.
 
3884
                        selected_entries.append(candidate_entry)
 
3885
                    else:
 
3886
                        # renamed, emit it when we process the directory it
 
3887
                        # ended up at.
 
3888
                        self.search_specific_file_parents.add(
 
3889
                            candidate_entry[1][self.target_index][1])
 
3890
            if not found_item:
 
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:
 
3897
                    continue
 
3898
                result, changed = self._process_entry(entry, path_info)
 
3899
                if changed is None:
 
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
 
3905
                # expansion.
 
3906
                if changed:
 
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
 
3911
                        # to be included.
 
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]
 
3915
                        else:
 
3916
                            entry_path_utf8 = path_utf8
 
3917
                        initial_key = (entry_path_utf8, '', '')
 
3918
                        block_index, _ = self.state._find_block_index_from_key(
 
3919
                            initial_key)
 
3920
                        if block_index == 0:
 
3921
                            # The children of the root are in block index 1.
 
3922
                            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
 
3934
                                    # included.
 
3935
                                    continue
 
3936
                                # Path of the entry itself.
 
3937
 
 
3938
                                self.search_specific_file_parents.add(
 
3939
                                    osutils.pathjoin(*entry[0][:2]))
 
3940
                if changed or self.include_unchanged:
 
3941
                    yield result
 
3942
            self.searched_exact_paths.add(path_utf8)
 
3943
 
 
3944
    def _path_info(self, utf8_path, unicode_path):
 
3945
        """Generate path_info for unicode_path.
 
3946
 
 
3947
        :return: None if unicode_path does not exist, or a path_info tuple.
 
3948
        """
 
3949
        abspath = self.tree.abspath(unicode_path)
 
3950
        try:
 
3951
            stat = os.lstat(abspath)
 
3952
        except OSError, e:
 
3953
            if e.errno == errno.ENOENT:
 
3954
                # the path does not exist.
 
3955
                return None
 
3956
            else:
 
3957
                raise
 
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,
 
3961
            abspath)
 
3962
        if dir_info[2] == 'directory':
 
3963
            if self.tree._directory_is_tree_reference(
 
3964
                unicode_path):
 
3965
                self.root_dir_info = self.root_dir_info[:2] + \
 
3966
                    ('tree-reference',) + self.root_dir_info[3:]
 
3967
        return dir_info
3799
3968
 
3800
3969
 
3801
3970
# Try to load the compiled form if possible
3809
3978
        ProcessEntryC as _process_entry,
3810
3979
        update_entry as update_entry,
3811
3980
        )
3812
 
except ImportError:
 
3981
except ImportError, e:
 
3982
    osutils.failed_to_load_extension(e)
3813
3983
    from bzrlib._dirstate_helpers_py import (
3814
3984
        _read_dirblocks,
3815
3985
        bisect_dirblock,