~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
220
220
    inventory,
221
221
    lock,
222
222
    osutils,
223
 
    static_tuple,
224
223
    trace,
225
224
    )
226
225
 
549
548
           self._ensure_block(block_index, entry_index, utf8path)
550
549
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
551
550
        if self._id_index:
552
 
            self._add_to_id_index(self._id_index, entry_key)
 
551
            self._id_index.setdefault(entry_key[2], set()).add(entry_key)
553
552
 
554
553
    def _bisect(self, paths):
555
554
        """Bisect through the disk structure for specific rows.
1325
1324
                key = (dirname_utf8, basename, file_id)
1326
1325
                minikind = DirState._kind_to_minikind[inv_entry.kind]
1327
1326
                if minikind == 't':
1328
 
                    fingerprint = inv_entry.reference_revision or ''
 
1327
                    fingerprint = inv_entry.reference_revision
1329
1328
                else:
1330
1329
                    fingerprint = ''
1331
1330
                insertions[file_id] = (key, minikind, inv_entry.executable,
1340
1339
                    minikind = child[1][0][0]
1341
1340
                    fingerprint = child[1][0][4]
1342
1341
                    executable = child[1][0][3]
1343
 
                    old_child_path = osutils.pathjoin(child_dirname,
1344
 
                                                      child_basename)
 
1342
                    old_child_path = osutils.pathjoin(child[0][0],
 
1343
                                                      child[0][1])
1345
1344
                    removals[child[0][2]] = old_child_path
1346
1345
                    child_suffix = child_dirname[len(old_path):]
1347
1346
                    new_child_dirname = (new_path + child_suffix)
1348
1347
                    key = (new_child_dirname, child_basename, child[0][2])
1349
 
                    new_child_path = osutils.pathjoin(new_child_dirname,
1350
 
                                                      child_basename)
 
1348
                    new_child_path = os.path.join(new_child_dirname,
 
1349
                                                  child_basename)
1351
1350
                    insertions[child[0][2]] = (key, minikind, executable,
1352
1351
                                               fingerprint, new_child_path)
1353
1352
        self._check_delta_ids_absent(new_ids, delta, 0)
1567
1566
            return
1568
1567
        id_index = self._get_id_index()
1569
1568
        for file_id in new_ids:
1570
 
            for key in id_index.get(file_id, ()):
 
1569
            for key in id_index.get(file_id, []):
1571
1570
                block_i, entry_i, d_present, f_present = \
1572
1571
                    self._get_block_entry_index(key[0], key[1], tree_index)
1573
1572
                if not f_present:
1981
1980
                                          ' tree_index, file_id and path')
1982
1981
            return entry
1983
1982
        else:
1984
 
            possible_keys = self._get_id_index().get(fileid_utf8, ())
 
1983
            possible_keys = self._get_id_index().get(fileid_utf8, None)
1985
1984
            if not possible_keys:
1986
1985
                return None, None
1987
1986
            for key in possible_keys:
1998
1997
                entry_index, present = self._find_entry_index(key, block)
1999
1998
                if present:
2000
1999
                    entry = self._dirblocks[block_index][1][entry_index]
2001
 
                    # TODO: We might want to assert that entry[0][2] ==
2002
 
                    #       fileid_utf8.
2003
2000
                    if entry[1][tree_index][0] in 'fdlt':
2004
2001
                        # this is the result we are looking for: the
2005
2002
                        # real home of this file_id in this tree.
2144
2141
                yield entry
2145
2142
 
2146
2143
    def _get_id_index(self):
2147
 
        """Get an id index of self._dirblocks.
2148
 
        
2149
 
        This maps from file_id => [(directory, name, file_id)] entries where
2150
 
        that file_id appears in one of the trees.
2151
 
        """
 
2144
        """Get an id index of self._dirblocks."""
2152
2145
        if self._id_index is None:
2153
2146
            id_index = {}
2154
2147
            for key, tree_details in self._iter_entries():
2155
 
                self._add_to_id_index(id_index, key)
 
2148
                id_index.setdefault(key[2], set()).add(key)
2156
2149
            self._id_index = id_index
2157
2150
        return self._id_index
2158
2151
 
2159
 
    def _add_to_id_index(self, id_index, entry_key):
2160
 
        """Add this entry to the _id_index mapping."""
2161
 
        # This code used to use a set for every entry in the id_index. However,
2162
 
        # it is *rare* to have more than one entry. So a set is a large
2163
 
        # overkill. And even when we do, we won't ever have more than the
2164
 
        # number of parent trees. Which is still a small number (rarely >2). As
2165
 
        # such, we use a simple tuple, and do our own uniqueness checks. While
2166
 
        # the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2167
 
        # cause quadratic failure.
2168
 
        # TODO: This should use StaticTuple
2169
 
        file_id = entry_key[2]
2170
 
        entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2171
 
        if file_id not in id_index:
2172
 
            id_index[file_id] = static_tuple.StaticTuple(entry_key,)
2173
 
        else:
2174
 
            entry_keys = id_index[file_id]
2175
 
            if entry_key not in entry_keys:
2176
 
                id_index[file_id] = entry_keys + (entry_key,)
2177
 
 
2178
 
    def _remove_from_id_index(self, id_index, entry_key):
2179
 
        """Remove this entry from the _id_index mapping.
2180
 
 
2181
 
        It is an programming error to call this when the entry_key is not
2182
 
        already present.
2183
 
        """
2184
 
        file_id = entry_key[2]
2185
 
        entry_keys = list(id_index[file_id])
2186
 
        entry_keys.remove(entry_key)
2187
 
        id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
2188
 
 
2189
2152
    def _get_output_lines(self, lines):
2190
2153
        """Format lines for final output.
2191
2154
 
2391
2354
        self.update_minimal(('', '', new_id), 'd',
2392
2355
            path_utf8='', packed_stat=entry[1][0][4])
2393
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])
2394
2359
 
2395
2360
    def set_parent_trees(self, trees, ghosts):
2396
2361
        """Set the parent trees for the dirstate.
2449
2414
                continue
2450
2415
            by_path[entry[0]] = [entry[1][0]] + \
2451
2416
                [DirState.NULL_PARENT_DETAILS] * parent_count
2452
 
            # TODO: Possibly inline this, since we know it isn't present yet
2453
 
            #       id_index[entry[0][2]] = (entry[0],)
2454
 
            self._add_to_id_index(id_index, entry[0])
 
2417
            id_index[entry[0][2]] = set([entry[0]])
2455
2418
 
2456
2419
        # now the parent trees:
2457
2420
        for tree_index, tree in enumerate(parent_trees):
2479
2442
                new_entry_key = (dirname, basename, file_id)
2480
2443
                # tree index consistency: All other paths for this id in this tree
2481
2444
                # index must point to the correct path.
2482
 
                for entry_key in id_index.get(file_id, ()):
 
2445
                for entry_key in id_index.setdefault(file_id, set()):
2483
2446
                    # TODO:PROFILING: It might be faster to just update
2484
2447
                    # rather than checking if we need to, and then overwrite
2485
2448
                    # the one we are located at.
2491
2454
                        by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2492
2455
                # by path consistency: Insert into an existing path record (trivial), or
2493
2456
                # add a new one with relocation pointers for the other tree indexes.
2494
 
                entry_keys = id_index.get(file_id, ())
2495
 
                if new_entry_key in entry_keys:
 
2457
                if new_entry_key in id_index[file_id]:
2496
2458
                    # there is already an entry where this data belongs, just insert it.
2497
2459
                    by_path[new_entry_key][tree_index] = \
2498
2460
                        self._inv_entry_to_details(entry)
2503
2465
                    new_details = []
2504
2466
                    for lookup_index in xrange(tree_index):
2505
2467
                        # boundary case: this is the first occurence of file_id
2506
 
                        # so there are no id_indexes, possibly take this out of
 
2468
                        # so there are no id_indexs, possibly take this out of
2507
2469
                        # the loop?
2508
 
                        if not len(entry_keys):
 
2470
                        if not len(id_index[file_id]):
2509
2471
                            new_details.append(DirState.NULL_PARENT_DETAILS)
2510
2472
                        else:
2511
2473
                            # grab any one entry, use it to find the right path.
2512
2474
                            # TODO: optimise this to reduce memory use in highly
2513
2475
                            # fragmented situations by reusing the relocation
2514
2476
                            # records.
2515
 
                            a_key = iter(entry_keys).next()
 
2477
                            a_key = iter(id_index[file_id]).next()
2516
2478
                            if by_path[a_key][lookup_index][0] in ('r', 'a'):
2517
2479
                                # its a pointer or missing statement, use it as is.
2518
2480
                                new_details.append(by_path[a_key][lookup_index])
2523
2485
                    new_details.append(self._inv_entry_to_details(entry))
2524
2486
                    new_details.extend(new_location_suffix)
2525
2487
                    by_path[new_entry_key] = new_details
2526
 
                    self._add_to_id_index(id_index, new_entry_key)
 
2488
                    id_index[file_id].add(new_entry_key)
2527
2489
        # --- end generation of full tree mappings
2528
2490
 
2529
2491
        # sort and output all the entries
2711
2673
            block[1].pop(entry_index)
2712
2674
            # if we have an id_index in use, remove this key from it for this id.
2713
2675
            if self._id_index is not None:
2714
 
                self._remove_from_id_index(self._id_index, current_old[0])
 
2676
                self._id_index[current_old[0][2]].remove(current_old[0])
2715
2677
        # update all remaining keys for this id to record it as absent. The
2716
2678
        # existing details may either be the record we are marking as deleted
2717
2679
        # (if there were other trees with the id present at this path), or may
2786
2748
                    else:
2787
2749
                        break
2788
2750
            # new entry, synthesis cross reference here,
2789
 
            existing_keys = id_index.get(key[2], ())
 
2751
            existing_keys = id_index.setdefault(key[2], set())
2790
2752
            if not existing_keys:
2791
2753
                # not currently in the state, simplest case
2792
2754
                new_entry = key, [new_details] + self._empty_parent_info()
2823
2785
                    # loop.
2824
2786
                    other_entry = other_block[other_entry_index]
2825
2787
                    other_entry[1][0] = ('r', path_utf8, 0, False, '')
2826
 
                    if self._maybe_remove_row(other_block, other_entry_index,
2827
 
                                              id_index):
2828
 
                        # If the row holding this was removed, we need to
2829
 
                        # recompute where this entry goes
2830
 
                        entry_index, _ = self._find_entry_index(key, block)
 
2788
                    self._maybe_remove_row(other_block, other_entry_index,
 
2789
                        id_index)
2831
2790
 
2832
2791
                # This loop:
2833
2792
                # adds a tuple to the new details for each column
2835
2794
                #  - or by creating a new pointer to the right row inside that column
2836
2795
                num_present_parents = self._num_present_parents()
2837
2796
                if num_present_parents:
2838
 
                    # TODO: This re-evaluates the existing_keys set, do we need
2839
 
                    #       to do that ourselves?
2840
2797
                    other_key = list(existing_keys)[0]
2841
2798
                for lookup_index in xrange(1, num_present_parents + 1):
2842
2799
                    # grab any one entry, use it to find the right path.
2861
2818
                        pointer_path = osutils.pathjoin(*other_key[0:2])
2862
2819
                        new_entry[1].append(('r', pointer_path, 0, False, ''))
2863
2820
            block.insert(entry_index, new_entry)
2864
 
            self._add_to_id_index(id_index, key)
 
2821
            existing_keys.add(key)
2865
2822
        else:
2866
2823
            # Does the new state matter?
2867
2824
            block[entry_index][1][0] = new_details
2876
2833
            # converted to relocated.
2877
2834
            if path_utf8 is None:
2878
2835
                raise AssertionError('no path')
2879
 
            existing_keys = id_index[key[2]]
2880
 
            if key not in existing_keys:
2881
 
                raise AssertionError('We found the entry in the blocks, but'
2882
 
                    ' the key is not in the id_index.'
2883
 
                    ' key: %s, existing_keys: %s' % (key, existing_keys))
2884
 
            for entry_key in id_index[key[2]]:
 
2836
            for entry_key in id_index.setdefault(key[2], set()):
2885
2837
                # TODO:PROFILING: It might be faster to just update
2886
2838
                # rather than checking if we need to, and then overwrite
2887
2839
                # the one we are located at.
2911
2863
        """Remove index if it is absent or relocated across the row.
2912
2864
        
2913
2865
        id_index is updated accordingly.
2914
 
        :return: True if we removed the row, False otherwise
2915
2866
        """
2916
2867
        present_in_row = False
2917
2868
        entry = block[index]
2921
2872
                break
2922
2873
        if not present_in_row:
2923
2874
            block.pop(index)
2924
 
            self._remove_from_id_index(id_index, entry[0])
2925
 
            return True
2926
 
        return False
 
2875
            id_index[entry[0][2]].remove(entry[0])
2927
2876
 
2928
2877
    def _validate(self):
2929
2878
        """Check that invariants on the dirblock are correct.
3064
3013
            if absent_positions == tree_count:
3065
3014
                raise AssertionError(
3066
3015
                    "entry %r has no data for any tree." % (entry,))
3067
 
        if self._id_index is not None:
3068
 
            for file_id, entry_keys in self._id_index.iteritems():
3069
 
                for entry_key in entry_keys:
3070
 
                    if entry_key[2] != file_id:
3071
 
                        raise AssertionError(
3072
 
                            'file_id %r did not match entry key %s'
3073
 
                            % (file_id, entry_key))
3074
 
                if len(entry_keys) != len(set(entry_keys)):
3075
 
                    raise AssertionError(
3076
 
                        'id_index contained non-unique data for %s'
3077
 
                        % (entry_keys,))
3078
3016
 
3079
3017
    def _wipe_state(self):
3080
3018
        """Forget all state information about the dirstate."""
3226
3164
 
3227
3165
class ProcessEntryPython(object):
3228
3166
 
3229
 
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
 
3167
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id", "uninteresting",
3230
3168
        "last_source_parent", "last_target_parent", "include_unchanged",
3231
 
        "partial", "use_filesystem_for_exec", "utf8_decode",
3232
 
        "searched_specific_files", "search_specific_files",
3233
 
        "searched_exact_paths", "search_specific_file_parents", "seen_ids",
3234
 
        "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"]
3235
3172
 
3236
3173
    def __init__(self, include_unchanged, use_filesystem_for_exec,
3237
3174
        search_specific_files, state, source_index, target_index,
3238
3175
        want_unversioned, tree):
3239
3176
        self.old_dirname_to_file_id = {}
3240
3177
        self.new_dirname_to_file_id = {}
3241
 
        # Are we doing a partial iter_changes?
3242
 
        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()
3243
3181
        # Using a list so that we can access the values and change them in
3244
3182
        # nested scope. Each one is [path, file_id, entry]
3245
3183
        self.last_source_parent = [None, None]
3248
3186
        self.use_filesystem_for_exec = use_filesystem_for_exec
3249
3187
        self.utf8_decode = cache_utf8._utf8_decode
3250
3188
        # for all search_indexs in each path at or under each element of
3251
 
        # search_specific_files, if the detail is relocated: add the id, and
3252
 
        # add the relocated path as one to search if its not searched already.
3253
 
        # 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.
3254
3192
        self.searched_specific_files = set()
3255
 
        # When we search exact paths without expanding downwards, we record
3256
 
        # that here.
3257
 
        self.searched_exact_paths = set()
3258
3193
        self.search_specific_files = search_specific_files
3259
 
        # The parents up to the root of the paths we are searching.
3260
 
        # After all normal paths are returned, these specific items are returned.
3261
 
        self.search_specific_file_parents = set()
3262
 
        # The ids we've sent out in the delta.
3263
 
        self.seen_ids = set()
3264
3194
        self.state = state
3265
3195
        self.source_index = source_index
3266
3196
        self.target_index = target_index
3267
 
        if target_index != 0:
3268
 
            # A lot of code in here depends on target_index == 0
3269
 
            raise errors.BzrError('unsupported target index')
3270
3197
        self.want_unversioned = want_unversioned
3271
3198
        self.tree = tree
3272
3199
 
3274
3201
        """Compare an entry and real disk to generate delta information.
3275
3202
 
3276
3203
        :param path_info: top_relpath, basename, kind, lstat, abspath for
3277
 
            the path of entry. If None, then the path is considered absent in 
3278
 
            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 ?)
3279
3206
            Basename is returned as a utf8 string because we expect this
3280
3207
            tuple will be ignored, and don't want to take the time to
3281
3208
            decode.
3282
 
        :return: (iter_changes_result, changed). If the entry has not been
3283
 
            handled then changed is None. Otherwise it is False if no content
3284
 
            or metadata changes have occurred, and True if any content or
3285
 
            metadata change has occurred. If self.include_unchanged is True then
3286
 
            if changed is not None, iter_changes_result will always be a result
3287
 
            tuple. Otherwise, iter_changes_result is None unless changed is
3288
 
            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.
3289
3213
        """
3290
3214
        if self.source_index is None:
3291
3215
            source_details = DirState.NULL_PARENT_DETAILS
3390
3314
                        content_change = False
3391
3315
                    target_exec = False
3392
3316
                else:
3393
 
                    if path is None:
3394
 
                        path = pathjoin(old_dirname, old_basename)
3395
 
                    raise errors.BadFileKindError(path, path_info[2])
 
3317
                    raise Exception, "unknown kind %s" % path_info[2]
3396
3318
            if source_minikind == 'd':
3397
3319
                if path is None:
3398
3320
                    old_path = path = pathjoin(old_dirname, old_basename)
3399
3321
                self.old_dirname_to_file_id[old_path] = file_id
3400
3322
            # parent id is the entry for the path in the target tree
3401
 
            if old_basename and old_dirname == self.last_source_parent[0]:
 
3323
            if old_dirname == self.last_source_parent[0]:
3402
3324
                source_parent_id = self.last_source_parent[1]
3403
3325
            else:
3404
3326
                try:
3414
3336
                    self.last_source_parent[0] = old_dirname
3415
3337
                    self.last_source_parent[1] = source_parent_id
3416
3338
            new_dirname = entry[0][0]
3417
 
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
 
3339
            if new_dirname == self.last_target_parent[0]:
3418
3340
                target_parent_id = self.last_target_parent[1]
3419
3341
            else:
3420
3342
                try:
3437
3359
                    self.last_target_parent[1] = target_parent_id
3438
3360
 
3439
3361
            source_exec = source_details[3]
3440
 
            changed = (content_change
 
3362
            if (self.include_unchanged
 
3363
                or content_change
3441
3364
                or source_parent_id != target_parent_id
3442
3365
                or old_basename != entry[0][1]
3443
3366
                or source_exec != target_exec
3444
 
                )
3445
 
            if not changed and not self.include_unchanged:
3446
 
                return None, False
3447
 
            else:
 
3367
                ):
3448
3368
                if old_path is None:
3449
3369
                    old_path = path = pathjoin(old_dirname, old_basename)
3450
3370
                    old_path_u = self.utf8_decode(old_path)[0]
3463
3383
                       (source_parent_id, target_parent_id),
3464
3384
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3465
3385
                       (source_kind, target_kind),
3466
 
                       (source_exec, target_exec)), changed
 
3386
                       (source_exec, target_exec))
 
3387
            else:
 
3388
                return self.uninteresting
3467
3389
        elif source_minikind in 'a' and target_minikind in 'fdlt':
3468
3390
            # looks like a new file
3469
3391
            path = pathjoin(entry[0][0], entry[0][1])
3490
3412
                       (None, parent_id),
3491
3413
                       (None, self.utf8_decode(entry[0][1])[0]),
3492
3414
                       (None, path_info[2]),
3493
 
                       (None, target_exec)), True
 
3415
                       (None, target_exec))
3494
3416
            else:
3495
3417
                # Its a missing file, report it as such.
3496
3418
                return (entry[0][2],
3500
3422
                       (None, parent_id),
3501
3423
                       (None, self.utf8_decode(entry[0][1])[0]),
3502
3424
                       (None, None),
3503
 
                       (None, False)), True
 
3425
                       (None, False))
3504
3426
        elif source_minikind in 'fdlt' and target_minikind in 'a':
3505
3427
            # unversioned, possibly, or possibly not deleted: we dont care.
3506
3428
            # if its still on disk, *and* theres no other entry at this
3518
3440
                   (parent_id, None),
3519
3441
                   (self.utf8_decode(entry[0][1])[0], None),
3520
3442
                   (DirState._minikind_to_kind[source_minikind], None),
3521
 
                   (source_details[3], None)), True
 
3443
                   (source_details[3], None))
3522
3444
        elif source_minikind in 'fdlt' and target_minikind in 'r':
3523
3445
            # a rename; could be a true rename, or a rename inherited from
3524
3446
            # a renamed parent. TODO: handle this efficiently. Its not
3536
3458
                "source_minikind=%r, target_minikind=%r"
3537
3459
                % (source_minikind, target_minikind))
3538
3460
            ## import pdb;pdb.set_trace()
3539
 
        return None, None
 
3461
        return None
3540
3462
 
3541
3463
    def __iter__(self):
3542
3464
        return self
3543
3465
 
3544
 
    def _gather_result_for_consistency(self, result):
3545
 
        """Check a result we will yield to make sure we are consistent later.
3546
 
        
3547
 
        This gathers result's parents into a set to output later.
3548
 
 
3549
 
        :param result: A result tuple.
3550
 
        """
3551
 
        if not self.partial or not result[0]:
3552
 
            return
3553
 
        self.seen_ids.add(result[0])
3554
 
        new_path = result[1][1]
3555
 
        if new_path:
3556
 
            # Not the root and not a delete: queue up the parents of the path.
3557
 
            self.search_specific_file_parents.update(
3558
 
                osutils.parent_directories(new_path.encode('utf8')))
3559
 
            # Add the root directory which parent_directories does not
3560
 
            # provide.
3561
 
            self.search_specific_file_parents.add('')
3562
 
 
3563
3466
    def iter_changes(self):
3564
3467
        """Iterate over the changes."""
3565
3468
        utf8_decode = cache_utf8._utf8_decode
3566
3469
        _cmp_by_dirs = cmp_by_dirs
3567
3470
        _process_entry = self._process_entry
 
3471
        uninteresting = self.uninteresting
3568
3472
        search_specific_files = self.search_specific_files
3569
3473
        searched_specific_files = self.searched_specific_files
3570
3474
        splitpath = osutils.splitpath
3640
3544
                continue
3641
3545
            path_handled = False
3642
3546
            for entry in root_entries:
3643
 
                result, changed = _process_entry(entry, root_dir_info)
3644
 
                if changed is not None:
 
3547
                result = _process_entry(entry, root_dir_info)
 
3548
                if result is not None:
3645
3549
                    path_handled = True
3646
 
                    if changed:
3647
 
                        self._gather_result_for_consistency(result)
3648
 
                    if changed or self.include_unchanged:
 
3550
                    if result is not uninteresting:
3649
3551
                        yield result
3650
3552
            if self.want_unversioned and not path_handled and root_dir_info:
3651
3553
                new_executable = bool(
3761
3663
                        for current_entry in current_block[1]:
3762
3664
                            # entry referring to file not present on disk.
3763
3665
                            # advance the entry only, after processing.
3764
 
                            result, changed = _process_entry(current_entry, None)
3765
 
                            if changed is not None:
3766
 
                                if changed:
3767
 
                                    self._gather_result_for_consistency(result)
3768
 
                                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:
3769
3669
                                    yield result
3770
3670
                        block_index +=1
3771
3671
                        if (block_index < len(self.state._dirblocks) and
3801
3701
                        pass
3802
3702
                    elif current_path_info is None:
3803
3703
                        # no path is fine: the per entry code will handle it.
3804
 
                        result, changed = _process_entry(current_entry, current_path_info)
3805
 
                        if changed is not None:
3806
 
                            if changed:
3807
 
                                self._gather_result_for_consistency(result)
3808
 
                            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:
3809
3707
                                yield result
3810
3708
                    elif (current_entry[0][1] != current_path_info[1]
3811
3709
                          or current_entry[1][self.target_index][0] in 'ar'):
3824
3722
                        else:
3825
3723
                            # entry referring to file not present on disk.
3826
3724
                            # advance the entry only, after processing.
3827
 
                            result, changed = _process_entry(current_entry, None)
3828
 
                            if changed is not None:
3829
 
                                if changed:
3830
 
                                    self._gather_result_for_consistency(result)
3831
 
                                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:
3832
3728
                                    yield result
3833
3729
                            advance_path = False
3834
3730
                    else:
3835
 
                        result, changed = _process_entry(current_entry, current_path_info)
3836
 
                        if changed is not None:
 
3731
                        result = _process_entry(current_entry, current_path_info)
 
3732
                        if result is not None:
3837
3733
                            path_handled = True
3838
 
                            if changed:
3839
 
                                self._gather_result_for_consistency(result)
3840
 
                            if changed or self.include_unchanged:
 
3734
                            if result is not uninteresting:
3841
3735
                                yield result
3842
3736
                    if advance_entry and current_entry is not None:
3843
3737
                        entry_index += 1
3902
3796
                        current_dir_info = dir_iterator.next()
3903
3797
                    except StopIteration:
3904
3798
                        current_dir_info = None
3905
 
        for result in self._iter_specific_file_parents():
3906
 
            yield result
3907
 
 
3908
 
    def _iter_specific_file_parents(self):
3909
 
        """Iter over the specific file parents."""
3910
 
        while self.search_specific_file_parents:
3911
 
            # Process the parent directories for the paths we were iterating.
3912
 
            # Even in extremely large trees this should be modest, so currently
3913
 
            # no attempt is made to optimise.
3914
 
            path_utf8 = self.search_specific_file_parents.pop()
3915
 
            if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3916
 
                # We've examined this path.
3917
 
                continue
3918
 
            if path_utf8 in self.searched_exact_paths:
3919
 
                # We've examined this path.
3920
 
                continue
3921
 
            path_entries = self.state._entries_for_path(path_utf8)
3922
 
            # We need either one or two entries. If the path in
3923
 
            # self.target_index has moved (so the entry in source_index is in
3924
 
            # 'ar') then we need to also look for the entry for this path in
3925
 
            # self.source_index, to output the appropriate delete-or-rename.
3926
 
            selected_entries = []
3927
 
            found_item = False
3928
 
            for candidate_entry in path_entries:
3929
 
                # Find entries present in target at this path:
3930
 
                if candidate_entry[1][self.target_index][0] not in 'ar':
3931
 
                    found_item = True
3932
 
                    selected_entries.append(candidate_entry)
3933
 
                # Find entries present in source at this path:
3934
 
                elif (self.source_index is not None and
3935
 
                    candidate_entry[1][self.source_index][0] not in 'ar'):
3936
 
                    found_item = True
3937
 
                    if candidate_entry[1][self.target_index][0] == 'a':
3938
 
                        # Deleted, emit it here.
3939
 
                        selected_entries.append(candidate_entry)
3940
 
                    else:
3941
 
                        # renamed, emit it when we process the directory it
3942
 
                        # ended up at.
3943
 
                        self.search_specific_file_parents.add(
3944
 
                            candidate_entry[1][self.target_index][1])
3945
 
            if not found_item:
3946
 
                raise AssertionError(
3947
 
                    "Missing entry for specific path parent %r, %r" % (
3948
 
                    path_utf8, path_entries))
3949
 
            path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3950
 
            for entry in selected_entries:
3951
 
                if entry[0][2] in self.seen_ids:
3952
 
                    continue
3953
 
                result, changed = self._process_entry(entry, path_info)
3954
 
                if changed is None:
3955
 
                    raise AssertionError(
3956
 
                        "Got entry<->path mismatch for specific path "
3957
 
                        "%r entry %r path_info %r " % (
3958
 
                        path_utf8, entry, path_info))
3959
 
                # Only include changes - we're outside the users requested
3960
 
                # expansion.
3961
 
                if changed:
3962
 
                    self._gather_result_for_consistency(result)
3963
 
                    if (result[6][0] == 'directory' and
3964
 
                        result[6][1] != 'directory'):
3965
 
                        # This stopped being a directory, the old children have
3966
 
                        # to be included.
3967
 
                        if entry[1][self.source_index][0] == 'r':
3968
 
                            # renamed, take the source path
3969
 
                            entry_path_utf8 = entry[1][self.source_index][1]
3970
 
                        else:
3971
 
                            entry_path_utf8 = path_utf8
3972
 
                        initial_key = (entry_path_utf8, '', '')
3973
 
                        block_index, _ = self.state._find_block_index_from_key(
3974
 
                            initial_key)
3975
 
                        if block_index == 0:
3976
 
                            # The children of the root are in block index 1.
3977
 
                            block_index +=1
3978
 
                        current_block = None
3979
 
                        if block_index < len(self.state._dirblocks):
3980
 
                            current_block = self.state._dirblocks[block_index]
3981
 
                            if not osutils.is_inside(
3982
 
                                entry_path_utf8, current_block[0]):
3983
 
                                # No entries for this directory at all.
3984
 
                                current_block = None
3985
 
                        if current_block is not None:
3986
 
                            for entry in current_block[1]:
3987
 
                                if entry[1][self.source_index][0] in 'ar':
3988
 
                                    # Not in the source tree, so doesn't have to be
3989
 
                                    # included.
3990
 
                                    continue
3991
 
                                # Path of the entry itself.
3992
 
 
3993
 
                                self.search_specific_file_parents.add(
3994
 
                                    osutils.pathjoin(*entry[0][:2]))
3995
 
                if changed or self.include_unchanged:
3996
 
                    yield result
3997
 
            self.searched_exact_paths.add(path_utf8)
3998
 
 
3999
 
    def _path_info(self, utf8_path, unicode_path):
4000
 
        """Generate path_info for unicode_path.
4001
 
 
4002
 
        :return: None if unicode_path does not exist, or a path_info tuple.
4003
 
        """
4004
 
        abspath = self.tree.abspath(unicode_path)
4005
 
        try:
4006
 
            stat = os.lstat(abspath)
4007
 
        except OSError, e:
4008
 
            if e.errno == errno.ENOENT:
4009
 
                # the path does not exist.
4010
 
                return None
4011
 
            else:
4012
 
                raise
4013
 
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
4014
 
        dir_info = (utf8_path, utf8_basename,
4015
 
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
4016
 
            abspath)
4017
 
        if dir_info[2] == 'directory':
4018
 
            if self.tree._directory_is_tree_reference(
4019
 
                unicode_path):
4020
 
                self.root_dir_info = self.root_dir_info[:2] + \
4021
 
                    ('tree-reference',) + self.root_dir_info[3:]
4022
 
        return dir_info
4023
3799
 
4024
3800
 
4025
3801
# Try to load the compiled form if possible
4033
3809
        ProcessEntryC as _process_entry,
4034
3810
        update_entry as update_entry,
4035
3811
        )
4036
 
except ImportError, e:
4037
 
    osutils.failed_to_load_extension(e)
 
3812
except ImportError:
4038
3813
    from bzrlib._dirstate_helpers_py import (
4039
3814
        _read_dirblocks,
4040
3815
        bisect_dirblock,