~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-07-22 18:18:34 UTC
  • mfrom: (4537.2.1 1.18-absent-content)
  • Revision ID: pqm@pqm.ubuntu.com-20090722181834-2geyfaa06s9himqg
(jam) Add AbsentContentFactory.get_bytes_as,
        which just raises a better error.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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 or ''
 
1327
                    fingerprint = inv_entry.reference_revision
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_dirname,
1343
 
                                                      child_basename)
 
1342
                    old_child_path = osutils.pathjoin(child[0][0],
 
1343
                                                      child[0][1])
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 = osutils.pathjoin(new_child_dirname,
1349
 
                                                      child_basename)
 
1348
                    new_child_path = os.path.join(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.
2002
2000
                    if entry[1][tree_index][0] in 'fdlt':
2003
2001
                        # this is the result we are looking for: the
2004
2002
                        # real home of this file_id in this tree.
2356
2354
        self.update_minimal(('', '', new_id), 'd',
2357
2355
            path_utf8='', packed_stat=entry[1][0][4])
2358
2356
        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))
3023
3016
 
3024
3017
    def _wipe_state(self):
3025
3018
        """Forget all state information about the dirstate."""
3171
3164
 
3172
3165
class ProcessEntryPython(object):
3173
3166
 
3174
 
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
 
3167
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
3175
3168
        "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"]
 
3169
        "use_filesystem_for_exec", "utf8_decode", "searched_specific_files",
 
3170
        "search_specific_files", "state", "source_index", "target_index",
 
3171
        "want_unversioned", "tree"]
3180
3172
 
3181
3173
    def __init__(self, include_unchanged, use_filesystem_for_exec,
3182
3174
        search_specific_files, state, source_index, target_index,
3183
3175
        want_unversioned, tree):
3184
3176
        self.old_dirname_to_file_id = {}
3185
3177
        self.new_dirname_to_file_id = {}
3186
 
        # Are we doing a partial iter_changes?
3187
 
        self.partial = search_specific_files != set([''])
 
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()
3188
3181
        # Using a list so that we can access the values and change them in
3189
3182
        # nested scope. Each one is [path, file_id, entry]
3190
3183
        self.last_source_parent = [None, None]
3193
3186
        self.use_filesystem_for_exec = use_filesystem_for_exec
3194
3187
        self.utf8_decode = cache_utf8._utf8_decode
3195
3188
        # 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.
 
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.
3199
3192
        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()
3203
3193
        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()
3209
3194
        self.state = state
3210
3195
        self.source_index = source_index
3211
3196
        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
3197
        self.want_unversioned = want_unversioned
3216
3198
        self.tree = tree
3217
3199
 
3219
3201
        """Compare an entry and real disk to generate delta information.
3220
3202
 
3221
3203
        :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 ?)
 
3204
            the path of entry. If None, then the path is considered absent.
 
3205
            (Perhaps we should pass in a concrete entry for this ?)
3224
3206
            Basename is returned as a utf8 string because we expect this
3225
3207
            tuple will be ignored, and don't want to take the time to
3226
3208
            decode.
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.
 
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.
3234
3213
        """
3235
3214
        if self.source_index is None:
3236
3215
            source_details = DirState.NULL_PARENT_DETAILS
3335
3314
                        content_change = False
3336
3315
                    target_exec = False
3337
3316
                else:
3338
 
                    if path is None:
3339
 
                        path = pathjoin(old_dirname, old_basename)
3340
 
                    raise errors.BadFileKindError(path, path_info[2])
 
3317
                    raise Exception, "unknown kind %s" % path_info[2]
3341
3318
            if source_minikind == 'd':
3342
3319
                if path is None:
3343
3320
                    old_path = path = pathjoin(old_dirname, old_basename)
3344
3321
                self.old_dirname_to_file_id[old_path] = file_id
3345
3322
            # parent id is the entry for the path in the target tree
3346
 
            if old_basename and old_dirname == self.last_source_parent[0]:
 
3323
            if old_dirname == self.last_source_parent[0]:
3347
3324
                source_parent_id = self.last_source_parent[1]
3348
3325
            else:
3349
3326
                try:
3359
3336
                    self.last_source_parent[0] = old_dirname
3360
3337
                    self.last_source_parent[1] = source_parent_id
3361
3338
            new_dirname = entry[0][0]
3362
 
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
 
3339
            if new_dirname == self.last_target_parent[0]:
3363
3340
                target_parent_id = self.last_target_parent[1]
3364
3341
            else:
3365
3342
                try:
3382
3359
                    self.last_target_parent[1] = target_parent_id
3383
3360
 
3384
3361
            source_exec = source_details[3]
3385
 
            changed = (content_change
 
3362
            if (self.include_unchanged
 
3363
                or content_change
3386
3364
                or source_parent_id != target_parent_id
3387
3365
                or old_basename != entry[0][1]
3388
3366
                or source_exec != target_exec
3389
 
                )
3390
 
            if not changed and not self.include_unchanged:
3391
 
                return None, False
3392
 
            else:
 
3367
                ):
3393
3368
                if old_path is None:
3394
3369
                    old_path = path = pathjoin(old_dirname, old_basename)
3395
3370
                    old_path_u = self.utf8_decode(old_path)[0]
3408
3383
                       (source_parent_id, target_parent_id),
3409
3384
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3410
3385
                       (source_kind, target_kind),
3411
 
                       (source_exec, target_exec)), changed
 
3386
                       (source_exec, target_exec))
 
3387
            else:
 
3388
                return self.uninteresting
3412
3389
        elif source_minikind in 'a' and target_minikind in 'fdlt':
3413
3390
            # looks like a new file
3414
3391
            path = pathjoin(entry[0][0], entry[0][1])
3435
3412
                       (None, parent_id),
3436
3413
                       (None, self.utf8_decode(entry[0][1])[0]),
3437
3414
                       (None, path_info[2]),
3438
 
                       (None, target_exec)), True
 
3415
                       (None, target_exec))
3439
3416
            else:
3440
3417
                # Its a missing file, report it as such.
3441
3418
                return (entry[0][2],
3445
3422
                       (None, parent_id),
3446
3423
                       (None, self.utf8_decode(entry[0][1])[0]),
3447
3424
                       (None, None),
3448
 
                       (None, False)), True
 
3425
                       (None, False))
3449
3426
        elif source_minikind in 'fdlt' and target_minikind in 'a':
3450
3427
            # unversioned, possibly, or possibly not deleted: we dont care.
3451
3428
            # if its still on disk, *and* theres no other entry at this
3463
3440
                   (parent_id, None),
3464
3441
                   (self.utf8_decode(entry[0][1])[0], None),
3465
3442
                   (DirState._minikind_to_kind[source_minikind], None),
3466
 
                   (source_details[3], None)), True
 
3443
                   (source_details[3], None))
3467
3444
        elif source_minikind in 'fdlt' and target_minikind in 'r':
3468
3445
            # a rename; could be a true rename, or a rename inherited from
3469
3446
            # a renamed parent. TODO: handle this efficiently. Its not
3481
3458
                "source_minikind=%r, target_minikind=%r"
3482
3459
                % (source_minikind, target_minikind))
3483
3460
            ## import pdb;pdb.set_trace()
3484
 
        return None, None
 
3461
        return None
3485
3462
 
3486
3463
    def __iter__(self):
3487
3464
        return self
3488
3465
 
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
 
 
3508
3466
    def iter_changes(self):
3509
3467
        """Iterate over the changes."""
3510
3468
        utf8_decode = cache_utf8._utf8_decode
3511
3469
        _cmp_by_dirs = cmp_by_dirs
3512
3470
        _process_entry = self._process_entry
 
3471
        uninteresting = self.uninteresting
3513
3472
        search_specific_files = self.search_specific_files
3514
3473
        searched_specific_files = self.searched_specific_files
3515
3474
        splitpath = osutils.splitpath
3585
3544
                continue
3586
3545
            path_handled = False
3587
3546
            for entry in root_entries:
3588
 
                result, changed = _process_entry(entry, root_dir_info)
3589
 
                if changed is not None:
 
3547
                result = _process_entry(entry, root_dir_info)
 
3548
                if result is not None:
3590
3549
                    path_handled = True
3591
 
                    if changed:
3592
 
                        self._gather_result_for_consistency(result)
3593
 
                    if changed or self.include_unchanged:
 
3550
                    if result is not uninteresting:
3594
3551
                        yield result
3595
3552
            if self.want_unversioned and not path_handled and root_dir_info:
3596
3553
                new_executable = bool(
3706
3663
                        for current_entry in current_block[1]:
3707
3664
                            # entry referring to file not present on disk.
3708
3665
                            # advance the entry only, after processing.
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:
 
3666
                            result = _process_entry(current_entry, None)
 
3667
                            if result is not None:
 
3668
                                if result is not uninteresting:
3714
3669
                                    yield result
3715
3670
                        block_index +=1
3716
3671
                        if (block_index < len(self.state._dirblocks) and
3746
3701
                        pass
3747
3702
                    elif current_path_info is None:
3748
3703
                        # 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:
3751
 
                            if changed:
3752
 
                                self._gather_result_for_consistency(result)
3753
 
                            if changed or self.include_unchanged:
 
3704
                        result = _process_entry(current_entry, current_path_info)
 
3705
                        if result is not None:
 
3706
                            if result is not uninteresting:
3754
3707
                                yield result
3755
3708
                    elif (current_entry[0][1] != current_path_info[1]
3756
3709
                          or current_entry[1][self.target_index][0] in 'ar'):
3769
3722
                        else:
3770
3723
                            # entry referring to file not present on disk.
3771
3724
                            # advance the entry only, after processing.
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:
 
3725
                            result = _process_entry(current_entry, None)
 
3726
                            if result is not None:
 
3727
                                if result is not uninteresting:
3777
3728
                                    yield result
3778
3729
                            advance_path = False
3779
3730
                    else:
3780
 
                        result, changed = _process_entry(current_entry, current_path_info)
3781
 
                        if changed is not None:
 
3731
                        result = _process_entry(current_entry, current_path_info)
 
3732
                        if result is not None:
3782
3733
                            path_handled = True
3783
 
                            if changed:
3784
 
                                self._gather_result_for_consistency(result)
3785
 
                            if changed or self.include_unchanged:
 
3734
                            if result is not uninteresting:
3786
3735
                                yield result
3787
3736
                    if advance_entry and current_entry is not None:
3788
3737
                        entry_index += 1
3847
3796
                        current_dir_info = dir_iterator.next()
3848
3797
                    except StopIteration:
3849
3798
                        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
3968
3799
 
3969
3800
 
3970
3801
# Try to load the compiled form if possible
3978
3809
        ProcessEntryC as _process_entry,
3979
3810
        update_entry as update_entry,
3980
3811
        )
3981
 
except ImportError, e:
3982
 
    osutils.failed_to_load_extension(e)
 
3812
except ImportError:
3983
3813
    from bzrlib._dirstate_helpers_py import (
3984
3814
        _read_dirblocks,
3985
3815
        bisect_dirblock,