/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/dirstate.py

  • Committer: Martin
  • Date: 2017-06-10 01:57:00 UTC
  • mto: This revision was merged to the branch mainline in revision 6679.
  • Revision ID: gzlist@googlemail.com-20170610015700-o3xeuyaqry2obiay
Go back to native str for urls and many other py3 changes

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2006-2011 Canonical Ltd
 
2
#
 
3
# This program is free software; you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation; either version 2 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program; if not, write to the Free Software
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
"""DirState objects record the state of a directory and its bzr metadata.
 
18
 
 
19
Pseudo EBNF grammar for the state file. Fields are separated by NULLs, and
 
20
lines by NL. The field delimiters are ommitted in the grammar, line delimiters
 
21
are not - this is done for clarity of reading. All string data is in utf8.
 
22
 
 
23
::
 
24
 
 
25
    MINIKIND = "f" | "d" | "l" | "a" | "r" | "t";
 
26
    NL = "\\n";
 
27
    NULL = "\\0";
 
28
    WHOLE_NUMBER = {digit}, digit;
 
29
    BOOLEAN = "y" | "n";
 
30
    REVISION_ID = a non-empty utf8 string;
 
31
    
 
32
    dirstate format = header line, full checksum, row count, parent details,
 
33
     ghost_details, entries;
 
34
    header line = "#bazaar dirstate flat format 3", NL;
 
35
    full checksum = "crc32: ", ["-"], WHOLE_NUMBER, NL;
 
36
    row count = "num_entries: ", WHOLE_NUMBER, NL;
 
37
    parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
 
38
    ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
 
39
    entries = {entry};
 
40
    entry = entry_key, current_entry_details, {parent_entry_details};
 
41
    entry_key = dirname,  basename, fileid;
 
42
    current_entry_details = common_entry_details, working_entry_details;
 
43
    parent_entry_details = common_entry_details, history_entry_details;
 
44
    common_entry_details = MINIKIND, fingerprint, size, executable
 
45
    working_entry_details = packed_stat
 
46
    history_entry_details = REVISION_ID;
 
47
    executable = BOOLEAN;
 
48
    size = WHOLE_NUMBER;
 
49
    fingerprint = a nonempty utf8 sequence with meaning defined by minikind.
 
50
 
 
51
Given this definition, the following is useful to know::
 
52
 
 
53
    entry (aka row) - all the data for a given key.
 
54
    entry[0]: The key (dirname, basename, fileid)
 
55
    entry[0][0]: dirname
 
56
    entry[0][1]: basename
 
57
    entry[0][2]: fileid
 
58
    entry[1]: The tree(s) data for this path and id combination.
 
59
    entry[1][0]: The current tree
 
60
    entry[1][1]: The second tree
 
61
 
 
62
For an entry for a tree, we have (using tree 0 - current tree) to demonstrate::
 
63
 
 
64
    entry[1][0][0]: minikind
 
65
    entry[1][0][1]: fingerprint
 
66
    entry[1][0][2]: size
 
67
    entry[1][0][3]: executable
 
68
    entry[1][0][4]: packed_stat
 
69
 
 
70
OR (for non tree-0)::
 
71
 
 
72
    entry[1][1][4]: revision_id
 
73
 
 
74
There may be multiple rows at the root, one per id present in the root, so the
 
75
in memory root row is now::
 
76
 
 
77
    self._dirblocks[0] -> ('', [entry ...]),
 
78
 
 
79
and the entries in there are::
 
80
 
 
81
    entries[0][0]: ''
 
82
    entries[0][1]: ''
 
83
    entries[0][2]: file_id
 
84
    entries[1][0]: The tree data for the current tree for this fileid at /
 
85
    etc.
 
86
 
 
87
Kinds::
 
88
 
 
89
    'r' is a relocated entry: This path is not present in this tree with this
 
90
        id, but the id can be found at another location. The fingerprint is
 
91
        used to point to the target location.
 
92
    'a' is an absent entry: In that tree the id is not present at this path.
 
93
    'd' is a directory entry: This path in this tree is a directory with the
 
94
        current file id. There is no fingerprint for directories.
 
95
    'f' is a file entry: As for directory, but it's a file. The fingerprint is
 
96
        the sha1 value of the file's canonical form, i.e. after any read
 
97
        filters have been applied to the convenience form stored in the working
 
98
        tree.
 
99
    'l' is a symlink entry: As for directory, but a symlink. The fingerprint is
 
100
        the link target.
 
101
    't' is a reference to a nested subtree; the fingerprint is the referenced
 
102
        revision.
 
103
 
 
104
Ordering:
 
105
 
 
106
The entries on disk and in memory are ordered according to the following keys::
 
107
 
 
108
    directory, as a list of components
 
109
    filename
 
110
    file-id
 
111
 
 
112
--- Format 1 had the following different definition: ---
 
113
 
 
114
::
 
115
 
 
116
    rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
 
117
        WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
 
118
        {PARENT ROW}
 
119
    PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
 
120
        basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
 
121
        SHA1
 
122
 
 
123
PARENT ROW's are emitted for every parent that is not in the ghosts details
 
124
line. That is, if the parents are foo, bar, baz, and the ghosts are bar, then
 
125
each row will have a PARENT ROW for foo and baz, but not for bar.
 
126
 
 
127
 
 
128
In any tree, a kind of 'moved' indicates that the fingerprint field
 
129
(which we treat as opaque data specific to the 'kind' anyway) has the
 
130
details for the id of this row in that tree.
 
131
 
 
132
I'm strongly tempted to add a id->path index as well, but I think that
 
133
where we need id->path mapping; we also usually read the whole file, so
 
134
I'm going to skip that for the moment, as we have the ability to locate
 
135
via bisect any path in any tree, and if we lookup things by path, we can
 
136
accumulate an id->path mapping as we go, which will tend to match what we
 
137
looked for.
 
138
 
 
139
I plan to implement this asap, so please speak up now to alter/tweak the
 
140
design - and once we stabilise on this, I'll update the wiki page for
 
141
it.
 
142
 
 
143
The rationale for all this is that we want fast operations for the
 
144
common case (diff/status/commit/merge on all files) and extremely fast
 
145
operations for the less common but still occurs a lot status/diff/commit
 
146
on specific files). Operations on specific files involve a scan for all
 
147
the children of a path, *in every involved tree*, which the current
 
148
format did not accommodate.
 
149
----
 
150
 
 
151
Design priorities:
 
152
 1. Fast end to end use for bzr's top 5 uses cases. (commmit/diff/status/merge/???)
 
153
 2. fall back current object model as needed.
 
154
 3. scale usably to the largest trees known today - say 50K entries. (mozilla
 
155
    is an example of this)
 
156
 
 
157
 
 
158
Locking:
 
159
 
 
160
 Eventually reuse dirstate objects across locks IFF the dirstate file has not
 
161
 been modified, but will require that we flush/ignore cached stat-hit data
 
162
 because we won't want to restat all files on disk just because a lock was
 
163
 acquired, yet we cannot trust the data after the previous lock was released.
 
164
 
 
165
Memory representation::
 
166
 
 
167
 vector of all directories, and vector of the childen ?
 
168
   i.e.
 
169
     root_entrie = (direntry for root, [parent_direntries_for_root]),
 
170
     dirblocks = [
 
171
     ('', ['data for achild', 'data for bchild', 'data for cchild'])
 
172
     ('dir', ['achild', 'cchild', 'echild'])
 
173
     ]
 
174
    - single bisect to find N subtrees from a path spec
 
175
    - in-order for serialisation - this is 'dirblock' grouping.
 
176
    - insertion of a file '/a' affects only the '/' child-vector, that is, to
 
177
      insert 10K elements from scratch does not generates O(N^2) memoves of a
 
178
      single vector, rather each individual, which tends to be limited to a
 
179
      manageable number. Will scale badly on trees with 10K entries in a
 
180
      single directory. compare with Inventory.InventoryDirectory which has
 
181
      a dictionary for the children. No bisect capability, can only probe for
 
182
      exact matches, or grab all elements and sort.
 
183
    - What's the risk of error here? Once we have the base format being processed
 
184
      we should have a net win regardless of optimality. So we are going to
 
185
      go with what seems reasonable.
 
186
 
 
187
open questions:
 
188
 
 
189
Maybe we should do a test profile of the core structure - 10K simulated
 
190
searches/lookups/etc?
 
191
 
 
192
Objects for each row?
 
193
The lifetime of Dirstate objects is current per lock, but see above for
 
194
possible extensions. The lifetime of a row from a dirstate is expected to be
 
195
very short in the optimistic case: which we are optimising for. For instance,
 
196
subtree status will determine from analysis of the disk data what rows need to
 
197
be examined at all, and will be able to determine from a single row whether
 
198
that file has altered or not, so we are aiming to process tens of thousands of
 
199
entries each second within the dirstate context, before exposing anything to
 
200
the larger codebase. This suggests we want the time for a single file
 
201
comparison to be < 0.1 milliseconds. That would give us 10000 paths per second
 
202
processed, and to scale to 100 thousand we'll another order of magnitude to do
 
203
that. Now, as the lifetime for all unchanged entries is the time to parse, stat
 
204
the file on disk, and then immediately discard, the overhead of object creation
 
205
becomes a significant cost.
 
206
 
 
207
Figures: Creating a tuple from 3 elements was profiled at 0.0625
 
208
microseconds, whereas creating a object which is subclassed from tuple was
 
209
0.500 microseconds, and creating an object with 3 elements and slots was 3
 
210
microseconds long. 0.1 milliseconds is 100 microseconds, and ideally we'll get
 
211
down to 10 microseconds for the total processing - having 33% of that be object
 
212
creation is a huge overhead. There is a potential cost in using tuples within
 
213
each row which is that the conditional code to do comparisons may be slower
 
214
than method invocation, but method invocation is known to be slow due to stack
 
215
frame creation, so avoiding methods in these tight inner loops in unfortunately
 
216
desirable. We can consider a pyrex version of this with objects in future if
 
217
desired.
 
218
 
 
219
"""
 
220
 
 
221
from __future__ import absolute_import
 
222
 
 
223
import bisect
 
224
import errno
 
225
import operator
 
226
import os
 
227
from stat import S_IEXEC
 
228
import stat
 
229
import sys
 
230
import time
 
231
import zlib
 
232
 
 
233
from breezy import (
 
234
    cache_utf8,
 
235
    config,
 
236
    debug,
 
237
    errors,
 
238
    inventory,
 
239
    lock,
 
240
    osutils,
 
241
    static_tuple,
 
242
    trace,
 
243
    urlutils,
 
244
    )
 
245
from .sixish import (
 
246
    range,
 
247
    viewitems,
 
248
    viewvalues,
 
249
    )
 
250
 
 
251
 
 
252
# This is the Windows equivalent of ENOTDIR
 
253
# It is defined in pywin32.winerror, but we don't want a strong dependency for
 
254
# just an error code.
 
255
ERROR_PATH_NOT_FOUND = 3
 
256
ERROR_DIRECTORY = 267
 
257
 
 
258
 
 
259
class SHA1Provider(object):
 
260
    """An interface for getting sha1s of a file."""
 
261
 
 
262
    def sha1(self, abspath):
 
263
        """Return the sha1 of a file given its absolute path.
 
264
 
 
265
        :param abspath:  May be a filesystem encoded absolute path
 
266
             or a unicode path.
 
267
        """
 
268
        raise NotImplementedError(self.sha1)
 
269
 
 
270
    def stat_and_sha1(self, abspath):
 
271
        """Return the stat and sha1 of a file given its absolute path.
 
272
        
 
273
        :param abspath:  May be a filesystem encoded absolute path
 
274
             or a unicode path.
 
275
 
 
276
        Note: the stat should be the stat of the physical file
 
277
        while the sha may be the sha of its canonical content.
 
278
        """
 
279
        raise NotImplementedError(self.stat_and_sha1)
 
280
 
 
281
 
 
282
class DefaultSHA1Provider(SHA1Provider):
 
283
    """A SHA1Provider that reads directly from the filesystem."""
 
284
 
 
285
    def sha1(self, abspath):
 
286
        """Return the sha1 of a file given its absolute path."""
 
287
        return osutils.sha_file_by_name(abspath)
 
288
 
 
289
    def stat_and_sha1(self, abspath):
 
290
        """Return the stat and sha1 of a file given its absolute path."""
 
291
        file_obj = file(abspath, 'rb')
 
292
        try:
 
293
            statvalue = os.fstat(file_obj.fileno())
 
294
            sha1 = osutils.sha_file(file_obj)
 
295
        finally:
 
296
            file_obj.close()
 
297
        return statvalue, sha1
 
298
 
 
299
 
 
300
class DirState(object):
 
301
    """Record directory and metadata state for fast access.
 
302
 
 
303
    A dirstate is a specialised data structure for managing local working
 
304
    tree state information. Its not yet well defined whether it is platform
 
305
    specific, and if it is how we detect/parameterize that.
 
306
 
 
307
    Dirstates use the usual lock_write, lock_read and unlock mechanisms.
 
308
    Unlike most bzr disk formats, DirStates must be locked for reading, using
 
309
    lock_read.  (This is an os file lock internally.)  This is necessary
 
310
    because the file can be rewritten in place.
 
311
 
 
312
    DirStates must be explicitly written with save() to commit changes; just
 
313
    unlocking them does not write the changes to disk.
 
314
    """
 
315
 
 
316
    _kind_to_minikind = {
 
317
            'absent': 'a',
 
318
            'file': 'f',
 
319
            'directory': 'd',
 
320
            'relocated': 'r',
 
321
            'symlink': 'l',
 
322
            'tree-reference': 't',
 
323
        }
 
324
    _minikind_to_kind = {
 
325
            'a': 'absent',
 
326
            'f': 'file',
 
327
            'd': 'directory',
 
328
            'l':'symlink',
 
329
            'r': 'relocated',
 
330
            't': 'tree-reference',
 
331
        }
 
332
    _stat_to_minikind = {
 
333
        stat.S_IFDIR:'d',
 
334
        stat.S_IFREG:'f',
 
335
        stat.S_IFLNK:'l',
 
336
    }
 
337
    _to_yesno = {True:'y', False: 'n'} # TODO profile the performance gain
 
338
     # of using int conversion rather than a dict here. AND BLAME ANDREW IF
 
339
     # it is faster.
 
340
 
 
341
    # TODO: jam 20070221 Figure out what to do if we have a record that exceeds
 
342
    #       the BISECT_PAGE_SIZE. For now, we just have to make it large enough
 
343
    #       that we are sure a single record will always fit.
 
344
    BISECT_PAGE_SIZE = 4096
 
345
 
 
346
    NOT_IN_MEMORY = 0
 
347
    IN_MEMORY_UNMODIFIED = 1
 
348
    IN_MEMORY_MODIFIED = 2
 
349
    IN_MEMORY_HASH_MODIFIED = 3 # Only hash-cache updates
 
350
 
 
351
    # A pack_stat (the x's) that is just noise and will never match the output
 
352
    # of base64 encode.
 
353
    NULLSTAT = 'x' * 32
 
354
    NULL_PARENT_DETAILS = static_tuple.StaticTuple('a', '', 0, False, '')
 
355
 
 
356
    HEADER_FORMAT_2 = '#bazaar dirstate flat format 2\n'
 
357
    HEADER_FORMAT_3 = '#bazaar dirstate flat format 3\n'
 
358
 
 
359
    def __init__(self, path, sha1_provider, worth_saving_limit=0):
 
360
        """Create a  DirState object.
 
361
 
 
362
        :param path: The path at which the dirstate file on disk should live.
 
363
        :param sha1_provider: an object meeting the SHA1Provider interface.
 
364
        :param worth_saving_limit: when the exact number of hash changed
 
365
            entries is known, only bother saving the dirstate if more than
 
366
            this count of entries have changed.
 
367
            -1 means never save hash changes, 0 means always save hash changes.
 
368
        """
 
369
        # _header_state and _dirblock_state represent the current state
 
370
        # of the dirstate metadata and the per-row data respectiely.
 
371
        # NOT_IN_MEMORY indicates that no data is in memory
 
372
        # IN_MEMORY_UNMODIFIED indicates that what we have in memory
 
373
        #   is the same as is on disk
 
374
        # IN_MEMORY_MODIFIED indicates that we have a modified version
 
375
        #   of what is on disk.
 
376
        # In future we will add more granularity, for instance _dirblock_state
 
377
        # will probably support partially-in-memory as a separate variable,
 
378
        # allowing for partially-in-memory unmodified and partially-in-memory
 
379
        # modified states.
 
380
        self._header_state = DirState.NOT_IN_MEMORY
 
381
        self._dirblock_state = DirState.NOT_IN_MEMORY
 
382
        # If true, an error has been detected while updating the dirstate, and
 
383
        # for safety we're not going to commit to disk.
 
384
        self._changes_aborted = False
 
385
        self._dirblocks = []
 
386
        self._ghosts = []
 
387
        self._parents = []
 
388
        self._state_file = None
 
389
        self._filename = path
 
390
        self._lock_token = None
 
391
        self._lock_state = None
 
392
        self._id_index = None
 
393
        # a map from packed_stat to sha's.
 
394
        self._packed_stat_index = None
 
395
        self._end_of_header = None
 
396
        self._cutoff_time = None
 
397
        self._split_path_cache = {}
 
398
        self._bisect_page_size = DirState.BISECT_PAGE_SIZE
 
399
        self._sha1_provider = sha1_provider
 
400
        if 'hashcache' in debug.debug_flags:
 
401
            self._sha1_file = self._sha1_file_and_mutter
 
402
        else:
 
403
            self._sha1_file = self._sha1_provider.sha1
 
404
        # These two attributes provide a simple cache for lookups into the
 
405
        # dirstate in-memory vectors. By probing respectively for the last
 
406
        # block, and for the next entry, we save nearly 2 bisections per path
 
407
        # during commit.
 
408
        self._last_block_index = None
 
409
        self._last_entry_index = None
 
410
        # The set of known hash changes
 
411
        self._known_hash_changes = set()
 
412
        # How many hash changed entries can we have without saving
 
413
        self._worth_saving_limit = worth_saving_limit
 
414
        self._config_stack = config.LocationStack(urlutils.local_path_to_url(
 
415
            path))
 
416
 
 
417
    def __repr__(self):
 
418
        return "%s(%r)" % \
 
419
            (self.__class__.__name__, self._filename)
 
420
 
 
421
    def _mark_modified(self, hash_changed_entries=None, header_modified=False):
 
422
        """Mark this dirstate as modified.
 
423
 
 
424
        :param hash_changed_entries: if non-None, mark just these entries as
 
425
            having their hash modified.
 
426
        :param header_modified: mark the header modified as well, not just the
 
427
            dirblocks.
 
428
        """
 
429
        #trace.mutter_callsite(3, "modified hash entries: %s", hash_changed_entries)
 
430
        if hash_changed_entries:
 
431
            self._known_hash_changes.update([e[0] for e in hash_changed_entries])
 
432
            if self._dirblock_state in (DirState.NOT_IN_MEMORY,
 
433
                                        DirState.IN_MEMORY_UNMODIFIED):
 
434
                # If the dirstate is already marked a IN_MEMORY_MODIFIED, then
 
435
                # that takes precedence.
 
436
                self._dirblock_state = DirState.IN_MEMORY_HASH_MODIFIED
 
437
        else:
 
438
            # TODO: Since we now have a IN_MEMORY_HASH_MODIFIED state, we
 
439
            #       should fail noisily if someone tries to set
 
440
            #       IN_MEMORY_MODIFIED but we don't have a write-lock!
 
441
            # We don't know exactly what changed so disable smart saving
 
442
            self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
443
        if header_modified:
 
444
            self._header_state = DirState.IN_MEMORY_MODIFIED
 
445
 
 
446
    def _mark_unmodified(self):
 
447
        """Mark this dirstate as unmodified."""
 
448
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
 
449
        self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
450
        self._known_hash_changes = set()
 
451
 
 
452
    def add(self, path, file_id, kind, stat, fingerprint):
 
453
        """Add a path to be tracked.
 
454
 
 
455
        :param path: The path within the dirstate - '' is the root, 'foo' is the
 
456
            path foo within the root, 'foo/bar' is the path bar within foo
 
457
            within the root.
 
458
        :param file_id: The file id of the path being added.
 
459
        :param kind: The kind of the path, as a string like 'file',
 
460
            'directory', etc.
 
461
        :param stat: The output of os.lstat for the path.
 
462
        :param fingerprint: The sha value of the file's canonical form (i.e.
 
463
            after any read filters have been applied),
 
464
            or the target of a symlink,
 
465
            or the referenced revision id for tree-references,
 
466
            or '' for directories.
 
467
        """
 
468
        # adding a file:
 
469
        # find the block its in.
 
470
        # find the location in the block.
 
471
        # check its not there
 
472
        # add it.
 
473
        #------- copied from inventory.ensure_normalized_name - keep synced.
 
474
        # --- normalized_filename wants a unicode basename only, so get one.
 
475
        dirname, basename = osutils.split(path)
 
476
        # we dont import normalized_filename directly because we want to be
 
477
        # able to change the implementation at runtime for tests.
 
478
        norm_name, can_access = osutils.normalized_filename(basename)
 
479
        if norm_name != basename:
 
480
            if can_access:
 
481
                basename = norm_name
 
482
            else:
 
483
                raise errors.InvalidNormalization(path)
 
484
        # you should never have files called . or ..; just add the directory
 
485
        # in the parent, or according to the special treatment for the root
 
486
        if basename == '.' or basename == '..':
 
487
            raise errors.InvalidEntryName(path)
 
488
        # now that we've normalised, we need the correct utf8 path and
 
489
        # dirname and basename elements. This single encode and split should be
 
490
        # faster than three separate encodes.
 
491
        utf8path = (dirname + '/' + basename).strip('/').encode('utf8')
 
492
        dirname, basename = osutils.split(utf8path)
 
493
        # uses __class__ for speed; the check is needed for safety
 
494
        if file_id.__class__ is not str:
 
495
            raise AssertionError(
 
496
                "must be a utf8 file_id not %s" % (type(file_id), ))
 
497
        # Make sure the file_id does not exist in this tree
 
498
        rename_from = None
 
499
        file_id_entry = self._get_entry(0, fileid_utf8=file_id, include_deleted=True)
 
500
        if file_id_entry != (None, None):
 
501
            if file_id_entry[1][0][0] == 'a':
 
502
                if file_id_entry[0] != (dirname, basename, file_id):
 
503
                    # set the old name's current operation to rename
 
504
                    self.update_minimal(file_id_entry[0],
 
505
                        'r',
 
506
                        path_utf8='',
 
507
                        packed_stat='',
 
508
                        fingerprint=utf8path
 
509
                    )
 
510
                    rename_from = file_id_entry[0][0:2]
 
511
            else:
 
512
                path = osutils.pathjoin(file_id_entry[0][0], file_id_entry[0][1])
 
513
                kind = DirState._minikind_to_kind[file_id_entry[1][0][0]]
 
514
                info = '%s:%s' % (kind, path)
 
515
                raise errors.DuplicateFileId(file_id, info)
 
516
        first_key = (dirname, basename, '')
 
517
        block_index, present = self._find_block_index_from_key(first_key)
 
518
        if present:
 
519
            # check the path is not in the tree
 
520
            block = self._dirblocks[block_index][1]
 
521
            entry_index, _ = self._find_entry_index(first_key, block)
 
522
            while (entry_index < len(block) and
 
523
                block[entry_index][0][0:2] == first_key[0:2]):
 
524
                if block[entry_index][1][0][0] not in 'ar':
 
525
                    # this path is in the dirstate in the current tree.
 
526
                    raise Exception("adding already added path!")
 
527
                entry_index += 1
 
528
        else:
 
529
            # The block where we want to put the file is not present. But it
 
530
            # might be because the directory was empty, or not loaded yet. Look
 
531
            # for a parent entry, if not found, raise NotVersionedError
 
532
            parent_dir, parent_base = osutils.split(dirname)
 
533
            parent_block_idx, parent_entry_idx, _, parent_present = \
 
534
                self._get_block_entry_index(parent_dir, parent_base, 0)
 
535
            if not parent_present:
 
536
                raise errors.NotVersionedError(path, str(self))
 
537
            self._ensure_block(parent_block_idx, parent_entry_idx, dirname)
 
538
        block = self._dirblocks[block_index][1]
 
539
        entry_key = (dirname, basename, file_id)
 
540
        if stat is None:
 
541
            size = 0
 
542
            packed_stat = DirState.NULLSTAT
 
543
        else:
 
544
            size = stat.st_size
 
545
            packed_stat = pack_stat(stat)
 
546
        parent_info = self._empty_parent_info()
 
547
        minikind = DirState._kind_to_minikind[kind]
 
548
        if rename_from is not None:
 
549
            if rename_from[0]:
 
550
                old_path_utf8 = '%s/%s' % rename_from
 
551
            else:
 
552
                old_path_utf8 = rename_from[1]
 
553
            parent_info[0] = ('r', old_path_utf8, 0, False, '')
 
554
        if kind == 'file':
 
555
            entry_data = entry_key, [
 
556
                (minikind, fingerprint, size, False, packed_stat),
 
557
                ] + parent_info
 
558
        elif kind == 'directory':
 
559
            entry_data = entry_key, [
 
560
                (minikind, '', 0, False, packed_stat),
 
561
                ] + parent_info
 
562
        elif kind == 'symlink':
 
563
            entry_data = entry_key, [
 
564
                (minikind, fingerprint, size, False, packed_stat),
 
565
                ] + parent_info
 
566
        elif kind == 'tree-reference':
 
567
            entry_data = entry_key, [
 
568
                (minikind, fingerprint, 0, False, packed_stat),
 
569
                ] + parent_info
 
570
        else:
 
571
            raise errors.BzrError('unknown kind %r' % kind)
 
572
        entry_index, present = self._find_entry_index(entry_key, block)
 
573
        if not present:
 
574
            block.insert(entry_index, entry_data)
 
575
        else:
 
576
            if block[entry_index][1][0][0] != 'a':
 
577
                raise AssertionError(" %r(%r) already added" % (basename, file_id))
 
578
            block[entry_index][1][0] = entry_data[1][0]
 
579
 
 
580
        if kind == 'directory':
 
581
           # insert a new dirblock
 
582
           self._ensure_block(block_index, entry_index, utf8path)
 
583
        self._mark_modified()
 
584
        if self._id_index:
 
585
            self._add_to_id_index(self._id_index, entry_key)
 
586
 
 
587
    def _bisect(self, paths):
 
588
        """Bisect through the disk structure for specific rows.
 
589
 
 
590
        :param paths: A list of paths to find
 
591
        :return: A dict mapping path => entries for found entries. Missing
 
592
                 entries will not be in the map.
 
593
                 The list is not sorted, and entries will be populated
 
594
                 based on when they were read.
 
595
        """
 
596
        self._requires_lock()
 
597
        # We need the file pointer to be right after the initial header block
 
598
        self._read_header_if_needed()
 
599
        # If _dirblock_state was in memory, we should just return info from
 
600
        # there, this function is only meant to handle when we want to read
 
601
        # part of the disk.
 
602
        if self._dirblock_state != DirState.NOT_IN_MEMORY:
 
603
            raise AssertionError("bad dirblock state %r" % self._dirblock_state)
 
604
 
 
605
        # The disk representation is generally info + '\0\n\0' at the end. But
 
606
        # for bisecting, it is easier to treat this as '\0' + info + '\0\n'
 
607
        # Because it means we can sync on the '\n'
 
608
        state_file = self._state_file
 
609
        file_size = os.fstat(state_file.fileno()).st_size
 
610
        # We end up with 2 extra fields, we should have a trailing '\n' to
 
611
        # ensure that we read the whole record, and we should have a precursur
 
612
        # '' which ensures that we start after the previous '\n'
 
613
        entry_field_count = self._fields_per_entry() + 1
 
614
 
 
615
        low = self._end_of_header
 
616
        high = file_size - 1 # Ignore the final '\0'
 
617
        # Map from (dir, name) => entry
 
618
        found = {}
 
619
 
 
620
        # Avoid infinite seeking
 
621
        max_count = 30*len(paths)
 
622
        count = 0
 
623
        # pending is a list of places to look.
 
624
        # each entry is a tuple of low, high, dir_names
 
625
        #   low -> the first byte offset to read (inclusive)
 
626
        #   high -> the last byte offset (inclusive)
 
627
        #   dir_names -> The list of (dir, name) pairs that should be found in
 
628
        #                the [low, high] range
 
629
        pending = [(low, high, paths)]
 
630
 
 
631
        page_size = self._bisect_page_size
 
632
 
 
633
        fields_to_entry = self._get_fields_to_entry()
 
634
 
 
635
        while pending:
 
636
            low, high, cur_files = pending.pop()
 
637
 
 
638
            if not cur_files or low >= high:
 
639
                # Nothing to find
 
640
                continue
 
641
 
 
642
            count += 1
 
643
            if count > max_count:
 
644
                raise errors.BzrError('Too many seeks, most likely a bug.')
 
645
 
 
646
            mid = max(low, (low+high-page_size)/2)
 
647
 
 
648
            state_file.seek(mid)
 
649
            # limit the read size, so we don't end up reading data that we have
 
650
            # already read.
 
651
            read_size = min(page_size, (high-mid)+1)
 
652
            block = state_file.read(read_size)
 
653
 
 
654
            start = mid
 
655
            entries = block.split('\n')
 
656
 
 
657
            if len(entries) < 2:
 
658
                # We didn't find a '\n', so we cannot have found any records.
 
659
                # So put this range back and try again. But we know we have to
 
660
                # increase the page size, because a single read did not contain
 
661
                # a record break (so records must be larger than page_size)
 
662
                page_size *= 2
 
663
                pending.append((low, high, cur_files))
 
664
                continue
 
665
 
 
666
            # Check the first and last entries, in case they are partial, or if
 
667
            # we don't care about the rest of this page
 
668
            first_entry_num = 0
 
669
            first_fields = entries[0].split('\0')
 
670
            if len(first_fields) < entry_field_count:
 
671
                # We didn't get the complete first entry
 
672
                # so move start, and grab the next, which
 
673
                # should be a full entry
 
674
                start += len(entries[0])+1
 
675
                first_fields = entries[1].split('\0')
 
676
                first_entry_num = 1
 
677
 
 
678
            if len(first_fields) <= 2:
 
679
                # We didn't even get a filename here... what do we do?
 
680
                # Try a large page size and repeat this query
 
681
                page_size *= 2
 
682
                pending.append((low, high, cur_files))
 
683
                continue
 
684
            else:
 
685
                # Find what entries we are looking for, which occur before and
 
686
                # after this first record.
 
687
                after = start
 
688
                if first_fields[1]:
 
689
                    first_path = first_fields[1] + '/' + first_fields[2]
 
690
                else:
 
691
                    first_path = first_fields[2]
 
692
                first_loc = _bisect_path_left(cur_files, first_path)
 
693
 
 
694
                # These exist before the current location
 
695
                pre = cur_files[:first_loc]
 
696
                # These occur after the current location, which may be in the
 
697
                # data we read, or might be after the last entry
 
698
                post = cur_files[first_loc:]
 
699
 
 
700
            if post and len(first_fields) >= entry_field_count:
 
701
                # We have files after the first entry
 
702
 
 
703
                # Parse the last entry
 
704
                last_entry_num = len(entries)-1
 
705
                last_fields = entries[last_entry_num].split('\0')
 
706
                if len(last_fields) < entry_field_count:
 
707
                    # The very last hunk was not complete,
 
708
                    # read the previous hunk
 
709
                    after = mid + len(block) - len(entries[-1])
 
710
                    last_entry_num -= 1
 
711
                    last_fields = entries[last_entry_num].split('\0')
 
712
                else:
 
713
                    after = mid + len(block)
 
714
 
 
715
                if last_fields[1]:
 
716
                    last_path = last_fields[1] + '/' + last_fields[2]
 
717
                else:
 
718
                    last_path = last_fields[2]
 
719
                last_loc = _bisect_path_right(post, last_path)
 
720
 
 
721
                middle_files = post[:last_loc]
 
722
                post = post[last_loc:]
 
723
 
 
724
                if middle_files:
 
725
                    # We have files that should occur in this block
 
726
                    # (>= first, <= last)
 
727
                    # Either we will find them here, or we can mark them as
 
728
                    # missing.
 
729
 
 
730
                    if middle_files[0] == first_path:
 
731
                        # We might need to go before this location
 
732
                        pre.append(first_path)
 
733
                    if middle_files[-1] == last_path:
 
734
                        post.insert(0, last_path)
 
735
 
 
736
                    # Find out what paths we have
 
737
                    paths = {first_path:[first_fields]}
 
738
                    # last_path might == first_path so we need to be
 
739
                    # careful if we should append rather than overwrite
 
740
                    if last_entry_num != first_entry_num:
 
741
                        paths.setdefault(last_path, []).append(last_fields)
 
742
                    for num in range(first_entry_num+1, last_entry_num):
 
743
                        # TODO: jam 20070223 We are already splitting here, so
 
744
                        #       shouldn't we just split the whole thing rather
 
745
                        #       than doing the split again in add_one_record?
 
746
                        fields = entries[num].split('\0')
 
747
                        if fields[1]:
 
748
                            path = fields[1] + '/' + fields[2]
 
749
                        else:
 
750
                            path = fields[2]
 
751
                        paths.setdefault(path, []).append(fields)
 
752
 
 
753
                    for path in middle_files:
 
754
                        for fields in paths.get(path, []):
 
755
                            # offset by 1 because of the opening '\0'
 
756
                            # consider changing fields_to_entry to avoid the
 
757
                            # extra list slice
 
758
                            entry = fields_to_entry(fields[1:])
 
759
                            found.setdefault(path, []).append(entry)
 
760
 
 
761
            # Now we have split up everything into pre, middle, and post, and
 
762
            # we have handled everything that fell in 'middle'.
 
763
            # We add 'post' first, so that we prefer to seek towards the
 
764
            # beginning, so that we will tend to go as early as we need, and
 
765
            # then only seek forward after that.
 
766
            if post:
 
767
                pending.append((after, high, post))
 
768
            if pre:
 
769
                pending.append((low, start-1, pre))
 
770
 
 
771
        # Consider that we may want to return the directory entries in sorted
 
772
        # order. For now, we just return them in whatever order we found them,
 
773
        # and leave it up to the caller if they care if it is ordered or not.
 
774
        return found
 
775
 
 
776
    def _bisect_dirblocks(self, dir_list):
 
777
        """Bisect through the disk structure to find entries in given dirs.
 
778
 
 
779
        _bisect_dirblocks is meant to find the contents of directories, which
 
780
        differs from _bisect, which only finds individual entries.
 
781
 
 
782
        :param dir_list: A sorted list of directory names ['', 'dir', 'foo'].
 
783
        :return: A map from dir => entries_for_dir
 
784
        """
 
785
        # TODO: jam 20070223 A lot of the bisecting logic could be shared
 
786
        #       between this and _bisect. It would require parameterizing the
 
787
        #       inner loop with a function, though. We should evaluate the
 
788
        #       performance difference.
 
789
        self._requires_lock()
 
790
        # We need the file pointer to be right after the initial header block
 
791
        self._read_header_if_needed()
 
792
        # If _dirblock_state was in memory, we should just return info from
 
793
        # there, this function is only meant to handle when we want to read
 
794
        # part of the disk.
 
795
        if self._dirblock_state != DirState.NOT_IN_MEMORY:
 
796
            raise AssertionError("bad dirblock state %r" % self._dirblock_state)
 
797
        # The disk representation is generally info + '\0\n\0' at the end. But
 
798
        # for bisecting, it is easier to treat this as '\0' + info + '\0\n'
 
799
        # Because it means we can sync on the '\n'
 
800
        state_file = self._state_file
 
801
        file_size = os.fstat(state_file.fileno()).st_size
 
802
        # We end up with 2 extra fields, we should have a trailing '\n' to
 
803
        # ensure that we read the whole record, and we should have a precursur
 
804
        # '' which ensures that we start after the previous '\n'
 
805
        entry_field_count = self._fields_per_entry() + 1
 
806
 
 
807
        low = self._end_of_header
 
808
        high = file_size - 1 # Ignore the final '\0'
 
809
        # Map from dir => entry
 
810
        found = {}
 
811
 
 
812
        # Avoid infinite seeking
 
813
        max_count = 30*len(dir_list)
 
814
        count = 0
 
815
        # pending is a list of places to look.
 
816
        # each entry is a tuple of low, high, dir_names
 
817
        #   low -> the first byte offset to read (inclusive)
 
818
        #   high -> the last byte offset (inclusive)
 
819
        #   dirs -> The list of directories that should be found in
 
820
        #                the [low, high] range
 
821
        pending = [(low, high, dir_list)]
 
822
 
 
823
        page_size = self._bisect_page_size
 
824
 
 
825
        fields_to_entry = self._get_fields_to_entry()
 
826
 
 
827
        while pending:
 
828
            low, high, cur_dirs = pending.pop()
 
829
 
 
830
            if not cur_dirs or low >= high:
 
831
                # Nothing to find
 
832
                continue
 
833
 
 
834
            count += 1
 
835
            if count > max_count:
 
836
                raise errors.BzrError('Too many seeks, most likely a bug.')
 
837
 
 
838
            mid = max(low, (low+high-page_size)/2)
 
839
 
 
840
            state_file.seek(mid)
 
841
            # limit the read size, so we don't end up reading data that we have
 
842
            # already read.
 
843
            read_size = min(page_size, (high-mid)+1)
 
844
            block = state_file.read(read_size)
 
845
 
 
846
            start = mid
 
847
            entries = block.split('\n')
 
848
 
 
849
            if len(entries) < 2:
 
850
                # We didn't find a '\n', so we cannot have found any records.
 
851
                # So put this range back and try again. But we know we have to
 
852
                # increase the page size, because a single read did not contain
 
853
                # a record break (so records must be larger than page_size)
 
854
                page_size *= 2
 
855
                pending.append((low, high, cur_dirs))
 
856
                continue
 
857
 
 
858
            # Check the first and last entries, in case they are partial, or if
 
859
            # we don't care about the rest of this page
 
860
            first_entry_num = 0
 
861
            first_fields = entries[0].split('\0')
 
862
            if len(first_fields) < entry_field_count:
 
863
                # We didn't get the complete first entry
 
864
                # so move start, and grab the next, which
 
865
                # should be a full entry
 
866
                start += len(entries[0])+1
 
867
                first_fields = entries[1].split('\0')
 
868
                first_entry_num = 1
 
869
 
 
870
            if len(first_fields) <= 1:
 
871
                # We didn't even get a dirname here... what do we do?
 
872
                # Try a large page size and repeat this query
 
873
                page_size *= 2
 
874
                pending.append((low, high, cur_dirs))
 
875
                continue
 
876
            else:
 
877
                # Find what entries we are looking for, which occur before and
 
878
                # after this first record.
 
879
                after = start
 
880
                first_dir = first_fields[1]
 
881
                first_loc = bisect.bisect_left(cur_dirs, first_dir)
 
882
 
 
883
                # These exist before the current location
 
884
                pre = cur_dirs[:first_loc]
 
885
                # These occur after the current location, which may be in the
 
886
                # data we read, or might be after the last entry
 
887
                post = cur_dirs[first_loc:]
 
888
 
 
889
            if post and len(first_fields) >= entry_field_count:
 
890
                # We have records to look at after the first entry
 
891
 
 
892
                # Parse the last entry
 
893
                last_entry_num = len(entries)-1
 
894
                last_fields = entries[last_entry_num].split('\0')
 
895
                if len(last_fields) < entry_field_count:
 
896
                    # The very last hunk was not complete,
 
897
                    # read the previous hunk
 
898
                    after = mid + len(block) - len(entries[-1])
 
899
                    last_entry_num -= 1
 
900
                    last_fields = entries[last_entry_num].split('\0')
 
901
                else:
 
902
                    after = mid + len(block)
 
903
 
 
904
                last_dir = last_fields[1]
 
905
                last_loc = bisect.bisect_right(post, last_dir)
 
906
 
 
907
                middle_files = post[:last_loc]
 
908
                post = post[last_loc:]
 
909
 
 
910
                if middle_files:
 
911
                    # We have files that should occur in this block
 
912
                    # (>= first, <= last)
 
913
                    # Either we will find them here, or we can mark them as
 
914
                    # missing.
 
915
 
 
916
                    if middle_files[0] == first_dir:
 
917
                        # We might need to go before this location
 
918
                        pre.append(first_dir)
 
919
                    if middle_files[-1] == last_dir:
 
920
                        post.insert(0, last_dir)
 
921
 
 
922
                    # Find out what paths we have
 
923
                    paths = {first_dir:[first_fields]}
 
924
                    # last_dir might == first_dir so we need to be
 
925
                    # careful if we should append rather than overwrite
 
926
                    if last_entry_num != first_entry_num:
 
927
                        paths.setdefault(last_dir, []).append(last_fields)
 
928
                    for num in range(first_entry_num+1, last_entry_num):
 
929
                        # TODO: jam 20070223 We are already splitting here, so
 
930
                        #       shouldn't we just split the whole thing rather
 
931
                        #       than doing the split again in add_one_record?
 
932
                        fields = entries[num].split('\0')
 
933
                        paths.setdefault(fields[1], []).append(fields)
 
934
 
 
935
                    for cur_dir in middle_files:
 
936
                        for fields in paths.get(cur_dir, []):
 
937
                            # offset by 1 because of the opening '\0'
 
938
                            # consider changing fields_to_entry to avoid the
 
939
                            # extra list slice
 
940
                            entry = fields_to_entry(fields[1:])
 
941
                            found.setdefault(cur_dir, []).append(entry)
 
942
 
 
943
            # Now we have split up everything into pre, middle, and post, and
 
944
            # we have handled everything that fell in 'middle'.
 
945
            # We add 'post' first, so that we prefer to seek towards the
 
946
            # beginning, so that we will tend to go as early as we need, and
 
947
            # then only seek forward after that.
 
948
            if post:
 
949
                pending.append((after, high, post))
 
950
            if pre:
 
951
                pending.append((low, start-1, pre))
 
952
 
 
953
        return found
 
954
 
 
955
    def _bisect_recursive(self, paths):
 
956
        """Bisect for entries for all paths and their children.
 
957
 
 
958
        This will use bisect to find all records for the supplied paths. It
 
959
        will then continue to bisect for any records which are marked as
 
960
        directories. (and renames?)
 
961
 
 
962
        :param paths: A sorted list of (dir, name) pairs
 
963
             eg: [('', 'a'), ('', 'f'), ('a/b', 'c')]
 
964
        :return: A dictionary mapping (dir, name, file_id) => [tree_info]
 
965
        """
 
966
        # Map from (dir, name, file_id) => [tree_info]
 
967
        found = {}
 
968
 
 
969
        found_dir_names = set()
 
970
 
 
971
        # Directories that have been read
 
972
        processed_dirs = set()
 
973
        # Get the ball rolling with the first bisect for all entries.
 
974
        newly_found = self._bisect(paths)
 
975
 
 
976
        while newly_found:
 
977
            # Directories that need to be read
 
978
            pending_dirs = set()
 
979
            paths_to_search = set()
 
980
            for entry_list in viewvalues(newly_found):
 
981
                for dir_name_id, trees_info in entry_list:
 
982
                    found[dir_name_id] = trees_info
 
983
                    found_dir_names.add(dir_name_id[:2])
 
984
                    is_dir = False
 
985
                    for tree_info in trees_info:
 
986
                        minikind = tree_info[0]
 
987
                        if minikind == 'd':
 
988
                            if is_dir:
 
989
                                # We already processed this one as a directory,
 
990
                                # we don't need to do the extra work again.
 
991
                                continue
 
992
                            subdir, name, file_id = dir_name_id
 
993
                            path = osutils.pathjoin(subdir, name)
 
994
                            is_dir = True
 
995
                            if path not in processed_dirs:
 
996
                                pending_dirs.add(path)
 
997
                        elif minikind == 'r':
 
998
                            # Rename, we need to directly search the target
 
999
                            # which is contained in the fingerprint column
 
1000
                            dir_name = osutils.split(tree_info[1])
 
1001
                            if dir_name[0] in pending_dirs:
 
1002
                                # This entry will be found in the dir search
 
1003
                                continue
 
1004
                            if dir_name not in found_dir_names:
 
1005
                                paths_to_search.add(tree_info[1])
 
1006
            # Now we have a list of paths to look for directly, and
 
1007
            # directory blocks that need to be read.
 
1008
            # newly_found is mixing the keys between (dir, name) and path
 
1009
            # entries, but that is okay, because we only really care about the
 
1010
            # targets.
 
1011
            newly_found = self._bisect(sorted(paths_to_search))
 
1012
            newly_found.update(self._bisect_dirblocks(sorted(pending_dirs)))
 
1013
            processed_dirs.update(pending_dirs)
 
1014
        return found
 
1015
 
 
1016
    def _discard_merge_parents(self):
 
1017
        """Discard any parents trees beyond the first.
 
1018
 
 
1019
        Note that if this fails the dirstate is corrupted.
 
1020
 
 
1021
        After this function returns the dirstate contains 2 trees, neither of
 
1022
        which are ghosted.
 
1023
        """
 
1024
        self._read_header_if_needed()
 
1025
        parents = self.get_parent_ids()
 
1026
        if len(parents) < 1:
 
1027
            return
 
1028
        # only require all dirblocks if we are doing a full-pass removal.
 
1029
        self._read_dirblocks_if_needed()
 
1030
        dead_patterns = {('a', 'r'), ('a', 'a'), ('r', 'r'), ('r', 'a')}
 
1031
        def iter_entries_removable():
 
1032
            for block in self._dirblocks:
 
1033
                deleted_positions = []
 
1034
                for pos, entry in enumerate(block[1]):
 
1035
                    yield entry
 
1036
                    if (entry[1][0][0], entry[1][1][0]) in dead_patterns:
 
1037
                        deleted_positions.append(pos)
 
1038
                if deleted_positions:
 
1039
                    if len(deleted_positions) == len(block[1]):
 
1040
                        del block[1][:]
 
1041
                    else:
 
1042
                        for pos in reversed(deleted_positions):
 
1043
                            del block[1][pos]
 
1044
        # if the first parent is a ghost:
 
1045
        if parents[0] in self.get_ghosts():
 
1046
            empty_parent = [DirState.NULL_PARENT_DETAILS]
 
1047
            for entry in iter_entries_removable():
 
1048
                entry[1][1:] = empty_parent
 
1049
        else:
 
1050
            for entry in iter_entries_removable():
 
1051
                del entry[1][2:]
 
1052
 
 
1053
        self._ghosts = []
 
1054
        self._parents = [parents[0]]
 
1055
        self._mark_modified(header_modified=True)
 
1056
 
 
1057
    def _empty_parent_info(self):
 
1058
        return [DirState.NULL_PARENT_DETAILS] * (len(self._parents) -
 
1059
                                                    len(self._ghosts))
 
1060
 
 
1061
    def _ensure_block(self, parent_block_index, parent_row_index, dirname):
 
1062
        """Ensure a block for dirname exists.
 
1063
 
 
1064
        This function exists to let callers which know that there is a
 
1065
        directory dirname ensure that the block for it exists. This block can
 
1066
        fail to exist because of demand loading, or because a directory had no
 
1067
        children. In either case it is not an error. It is however an error to
 
1068
        call this if there is no parent entry for the directory, and thus the
 
1069
        function requires the coordinates of such an entry to be provided.
 
1070
 
 
1071
        The root row is special cased and can be indicated with a parent block
 
1072
        and row index of -1
 
1073
 
 
1074
        :param parent_block_index: The index of the block in which dirname's row
 
1075
            exists.
 
1076
        :param parent_row_index: The index in the parent block where the row
 
1077
            exists.
 
1078
        :param dirname: The utf8 dirname to ensure there is a block for.
 
1079
        :return: The index for the block.
 
1080
        """
 
1081
        if dirname == '' and parent_row_index == 0 and parent_block_index == 0:
 
1082
            # This is the signature of the root row, and the
 
1083
            # contents-of-root row is always index 1
 
1084
            return 1
 
1085
        # the basename of the directory must be the end of its full name.
 
1086
        if not (parent_block_index == -1 and
 
1087
            parent_block_index == -1 and dirname == ''):
 
1088
            if not dirname.endswith(
 
1089
                    self._dirblocks[parent_block_index][1][parent_row_index][0][1]):
 
1090
                raise AssertionError("bad dirname %r" % dirname)
 
1091
        block_index, present = self._find_block_index_from_key((dirname, '', ''))
 
1092
        if not present:
 
1093
            ## In future, when doing partial parsing, this should load and
 
1094
            # populate the entire block.
 
1095
            self._dirblocks.insert(block_index, (dirname, []))
 
1096
        return block_index
 
1097
 
 
1098
    def _entries_to_current_state(self, new_entries):
 
1099
        """Load new_entries into self.dirblocks.
 
1100
 
 
1101
        Process new_entries into the current state object, making them the active
 
1102
        state.  The entries are grouped together by directory to form dirblocks.
 
1103
 
 
1104
        :param new_entries: A sorted list of entries. This function does not sort
 
1105
            to prevent unneeded overhead when callers have a sorted list already.
 
1106
        :return: Nothing.
 
1107
        """
 
1108
        if new_entries[0][0][0:2] != ('', ''):
 
1109
            raise AssertionError(
 
1110
                "Missing root row %r" % (new_entries[0][0],))
 
1111
        # The two blocks here are deliberate: the root block and the
 
1112
        # contents-of-root block.
 
1113
        self._dirblocks = [('', []), ('', [])]
 
1114
        current_block = self._dirblocks[0][1]
 
1115
        current_dirname = ''
 
1116
        root_key = ('', '')
 
1117
        append_entry = current_block.append
 
1118
        for entry in new_entries:
 
1119
            if entry[0][0] != current_dirname:
 
1120
                # new block - different dirname
 
1121
                current_block = []
 
1122
                current_dirname = entry[0][0]
 
1123
                self._dirblocks.append((current_dirname, current_block))
 
1124
                append_entry = current_block.append
 
1125
            # append the entry to the current block
 
1126
            append_entry(entry)
 
1127
        self._split_root_dirblock_into_contents()
 
1128
 
 
1129
    def _split_root_dirblock_into_contents(self):
 
1130
        """Split the root dirblocks into root and contents-of-root.
 
1131
 
 
1132
        After parsing by path, we end up with root entries and contents-of-root
 
1133
        entries in the same block. This loop splits them out again.
 
1134
        """
 
1135
        # The above loop leaves the "root block" entries mixed with the
 
1136
        # "contents-of-root block". But we don't want an if check on
 
1137
        # all entries, so instead we just fix it up here.
 
1138
        if self._dirblocks[1] != ('', []):
 
1139
            raise ValueError("bad dirblock start %r" % (self._dirblocks[1],))
 
1140
        root_block = []
 
1141
        contents_of_root_block = []
 
1142
        for entry in self._dirblocks[0][1]:
 
1143
            if not entry[0][1]: # This is a root entry
 
1144
                root_block.append(entry)
 
1145
            else:
 
1146
                contents_of_root_block.append(entry)
 
1147
        self._dirblocks[0] = ('', root_block)
 
1148
        self._dirblocks[1] = ('', contents_of_root_block)
 
1149
 
 
1150
    def _entries_for_path(self, path):
 
1151
        """Return a list with all the entries that match path for all ids."""
 
1152
        dirname, basename = os.path.split(path)
 
1153
        key = (dirname, basename, '')
 
1154
        block_index, present = self._find_block_index_from_key(key)
 
1155
        if not present:
 
1156
            # the block which should contain path is absent.
 
1157
            return []
 
1158
        result = []
 
1159
        block = self._dirblocks[block_index][1]
 
1160
        entry_index, _ = self._find_entry_index(key, block)
 
1161
        # we may need to look at multiple entries at this path: walk while the specific_files match.
 
1162
        while (entry_index < len(block) and
 
1163
            block[entry_index][0][0:2] == key[0:2]):
 
1164
            result.append(block[entry_index])
 
1165
            entry_index += 1
 
1166
        return result
 
1167
 
 
1168
    def _entry_to_line(self, entry):
 
1169
        """Serialize entry to a NULL delimited line ready for _get_output_lines.
 
1170
 
 
1171
        :param entry: An entry_tuple as defined in the module docstring.
 
1172
        """
 
1173
        entire_entry = list(entry[0])
 
1174
        for tree_number, tree_data in enumerate(entry[1]):
 
1175
            # (minikind, fingerprint, size, executable, tree_specific_string)
 
1176
            entire_entry.extend(tree_data)
 
1177
            # 3 for the key, 5 for the fields per tree.
 
1178
            tree_offset = 3 + tree_number * 5
 
1179
            # minikind
 
1180
            entire_entry[tree_offset + 0] = tree_data[0]
 
1181
            # size
 
1182
            entire_entry[tree_offset + 2] = str(tree_data[2])
 
1183
            # executable
 
1184
            entire_entry[tree_offset + 3] = DirState._to_yesno[tree_data[3]]
 
1185
        return '\0'.join(entire_entry)
 
1186
 
 
1187
    def _fields_per_entry(self):
 
1188
        """How many null separated fields should be in each entry row.
 
1189
 
 
1190
        Each line now has an extra '\\n' field which is not used
 
1191
        so we just skip over it
 
1192
 
 
1193
        entry size::
 
1194
            3 fields for the key
 
1195
            + number of fields per tree_data (5) * tree count
 
1196
            + newline
 
1197
         """
 
1198
        tree_count = 1 + self._num_present_parents()
 
1199
        return 3 + 5 * tree_count + 1
 
1200
 
 
1201
    def _find_block(self, key, add_if_missing=False):
 
1202
        """Return the block that key should be present in.
 
1203
 
 
1204
        :param key: A dirstate entry key.
 
1205
        :return: The block tuple.
 
1206
        """
 
1207
        block_index, present = self._find_block_index_from_key(key)
 
1208
        if not present:
 
1209
            if not add_if_missing:
 
1210
                # check to see if key is versioned itself - we might want to
 
1211
                # add it anyway, because dirs with no entries dont get a
 
1212
                # dirblock at parse time.
 
1213
                # This is an uncommon branch to take: most dirs have children,
 
1214
                # and most code works with versioned paths.
 
1215
                parent_base, parent_name = osutils.split(key[0])
 
1216
                if not self._get_block_entry_index(parent_base, parent_name, 0)[3]:
 
1217
                    # some parent path has not been added - its an error to add
 
1218
                    # this child
 
1219
                    raise errors.NotVersionedError(key[0:2], str(self))
 
1220
            self._dirblocks.insert(block_index, (key[0], []))
 
1221
        return self._dirblocks[block_index]
 
1222
 
 
1223
    def _find_block_index_from_key(self, key):
 
1224
        """Find the dirblock index for a key.
 
1225
 
 
1226
        :return: The block index, True if the block for the key is present.
 
1227
        """
 
1228
        if key[0:2] == ('', ''):
 
1229
            return 0, True
 
1230
        try:
 
1231
            if (self._last_block_index is not None and
 
1232
                self._dirblocks[self._last_block_index][0] == key[0]):
 
1233
                return self._last_block_index, True
 
1234
        except IndexError:
 
1235
            pass
 
1236
        block_index = bisect_dirblock(self._dirblocks, key[0], 1,
 
1237
                                      cache=self._split_path_cache)
 
1238
        # _right returns one-past-where-key is so we have to subtract
 
1239
        # one to use it. we use _right here because there are two
 
1240
        # '' blocks - the root, and the contents of root
 
1241
        # we always have a minimum of 2 in self._dirblocks: root and
 
1242
        # root-contents, and for '', we get 2 back, so this is
 
1243
        # simple and correct:
 
1244
        present = (block_index < len(self._dirblocks) and
 
1245
            self._dirblocks[block_index][0] == key[0])
 
1246
        self._last_block_index = block_index
 
1247
        # Reset the entry index cache to the beginning of the block.
 
1248
        self._last_entry_index = -1
 
1249
        return block_index, present
 
1250
 
 
1251
    def _find_entry_index(self, key, block):
 
1252
        """Find the entry index for a key in a block.
 
1253
 
 
1254
        :return: The entry index, True if the entry for the key is present.
 
1255
        """
 
1256
        len_block = len(block)
 
1257
        try:
 
1258
            if self._last_entry_index is not None:
 
1259
                # mini-bisect here.
 
1260
                entry_index = self._last_entry_index + 1
 
1261
                # A hit is when the key is after the last slot, and before or
 
1262
                # equal to the next slot.
 
1263
                if ((entry_index > 0 and block[entry_index - 1][0] < key) and
 
1264
                    key <= block[entry_index][0]):
 
1265
                    self._last_entry_index = entry_index
 
1266
                    present = (block[entry_index][0] == key)
 
1267
                    return entry_index, present
 
1268
        except IndexError:
 
1269
            pass
 
1270
        entry_index = bisect.bisect_left(block, (key, []))
 
1271
        present = (entry_index < len_block and
 
1272
            block[entry_index][0] == key)
 
1273
        self._last_entry_index = entry_index
 
1274
        return entry_index, present
 
1275
 
 
1276
    @staticmethod
 
1277
    def from_tree(tree, dir_state_filename, sha1_provider=None):
 
1278
        """Create a dirstate from a bzr Tree.
 
1279
 
 
1280
        :param tree: The tree which should provide parent information and
 
1281
            inventory ids.
 
1282
        :param sha1_provider: an object meeting the SHA1Provider interface.
 
1283
            If None, a DefaultSHA1Provider is used.
 
1284
        :return: a DirState object which is currently locked for writing.
 
1285
            (it was locked by DirState.initialize)
 
1286
        """
 
1287
        result = DirState.initialize(dir_state_filename,
 
1288
            sha1_provider=sha1_provider)
 
1289
        try:
 
1290
            tree.lock_read()
 
1291
            try:
 
1292
                parent_ids = tree.get_parent_ids()
 
1293
                num_parents = len(parent_ids)
 
1294
                parent_trees = []
 
1295
                for parent_id in parent_ids:
 
1296
                    parent_tree = tree.branch.repository.revision_tree(parent_id)
 
1297
                    parent_trees.append((parent_id, parent_tree))
 
1298
                    parent_tree.lock_read()
 
1299
                result.set_parent_trees(parent_trees, [])
 
1300
                result.set_state_from_inventory(tree.root_inventory)
 
1301
            finally:
 
1302
                for revid, parent_tree in parent_trees:
 
1303
                    parent_tree.unlock()
 
1304
                tree.unlock()
 
1305
        except:
 
1306
            # The caller won't have a chance to unlock this, so make sure we
 
1307
            # cleanup ourselves
 
1308
            result.unlock()
 
1309
            raise
 
1310
        return result
 
1311
 
 
1312
    def _check_delta_is_valid(self, delta):
 
1313
        return list(inventory._check_delta_unique_ids(
 
1314
                    inventory._check_delta_unique_old_paths(
 
1315
                    inventory._check_delta_unique_new_paths(
 
1316
                    inventory._check_delta_ids_match_entry(
 
1317
                    inventory._check_delta_ids_are_valid(
 
1318
                    inventory._check_delta_new_path_entry_both_or_None(delta)))))))
 
1319
 
 
1320
    def update_by_delta(self, delta):
 
1321
        """Apply an inventory delta to the dirstate for tree 0
 
1322
 
 
1323
        This is the workhorse for apply_inventory_delta in dirstate based
 
1324
        trees.
 
1325
 
 
1326
        :param delta: An inventory delta.  See Inventory.apply_delta for
 
1327
            details.
 
1328
        """
 
1329
        self._read_dirblocks_if_needed()
 
1330
        encode = cache_utf8.encode
 
1331
        insertions = {}
 
1332
        removals = {}
 
1333
        # Accumulate parent references (path_utf8, id), to check for parentless
 
1334
        # items or items placed under files/links/tree-references. We get
 
1335
        # references from every item in the delta that is not a deletion and
 
1336
        # is not itself the root.
 
1337
        parents = set()
 
1338
        # Added ids must not be in the dirstate already. This set holds those
 
1339
        # ids.
 
1340
        new_ids = set()
 
1341
        # This loop transforms the delta to single atomic operations that can
 
1342
        # be executed and validated.
 
1343
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
 
1344
        for old_path, new_path, file_id, inv_entry in delta:
 
1345
            if (file_id in insertions) or (file_id in removals):
 
1346
                self._raise_invalid(old_path or new_path, file_id,
 
1347
                    "repeated file_id")
 
1348
            if old_path is not None:
 
1349
                old_path = old_path.encode('utf-8')
 
1350
                removals[file_id] = old_path
 
1351
            else:
 
1352
                new_ids.add(file_id)
 
1353
            if new_path is not None:
 
1354
                if inv_entry is None:
 
1355
                    self._raise_invalid(new_path, file_id,
 
1356
                        "new_path with no entry")
 
1357
                new_path = new_path.encode('utf-8')
 
1358
                dirname_utf8, basename = osutils.split(new_path)
 
1359
                if basename:
 
1360
                    parents.add((dirname_utf8, inv_entry.parent_id))
 
1361
                key = (dirname_utf8, basename, file_id)
 
1362
                minikind = DirState._kind_to_minikind[inv_entry.kind]
 
1363
                if minikind == 't':
 
1364
                    fingerprint = inv_entry.reference_revision or ''
 
1365
                else:
 
1366
                    fingerprint = ''
 
1367
                insertions[file_id] = (key, minikind, inv_entry.executable,
 
1368
                                       fingerprint, new_path)
 
1369
            # Transform moves into delete+add pairs
 
1370
            if None not in (old_path, new_path):
 
1371
                for child in self._iter_child_entries(0, old_path):
 
1372
                    if child[0][2] in insertions or child[0][2] in removals:
 
1373
                        continue
 
1374
                    child_dirname = child[0][0]
 
1375
                    child_basename = child[0][1]
 
1376
                    minikind = child[1][0][0]
 
1377
                    fingerprint = child[1][0][4]
 
1378
                    executable = child[1][0][3]
 
1379
                    old_child_path = osutils.pathjoin(child_dirname,
 
1380
                                                      child_basename)
 
1381
                    removals[child[0][2]] = old_child_path
 
1382
                    child_suffix = child_dirname[len(old_path):]
 
1383
                    new_child_dirname = (new_path + child_suffix)
 
1384
                    key = (new_child_dirname, child_basename, child[0][2])
 
1385
                    new_child_path = osutils.pathjoin(new_child_dirname,
 
1386
                                                      child_basename)
 
1387
                    insertions[child[0][2]] = (key, minikind, executable,
 
1388
                                               fingerprint, new_child_path)
 
1389
        self._check_delta_ids_absent(new_ids, delta, 0)
 
1390
        try:
 
1391
            self._apply_removals(viewitems(removals))
 
1392
            self._apply_insertions(viewvalues(insertions))
 
1393
            # Validate parents
 
1394
            self._after_delta_check_parents(parents, 0)
 
1395
        except errors.BzrError as e:
 
1396
            self._changes_aborted = True
 
1397
            if 'integrity error' not in str(e):
 
1398
                raise
 
1399
            # _get_entry raises BzrError when a request is inconsistent; we
 
1400
            # want such errors to be shown as InconsistentDelta - and that 
 
1401
            # fits the behaviour we trigger.
 
1402
            raise errors.InconsistentDeltaDelta(delta,
 
1403
                "error from _get_entry. %s" % (e,))
 
1404
 
 
1405
    def _apply_removals(self, removals):
 
1406
        for file_id, path in sorted(removals, reverse=True,
 
1407
            key=operator.itemgetter(1)):
 
1408
            dirname, basename = osutils.split(path)
 
1409
            block_i, entry_i, d_present, f_present = \
 
1410
                self._get_block_entry_index(dirname, basename, 0)
 
1411
            try:
 
1412
                entry = self._dirblocks[block_i][1][entry_i]
 
1413
            except IndexError:
 
1414
                self._raise_invalid(path, file_id,
 
1415
                    "Wrong path for old path.")
 
1416
            if not f_present or entry[1][0][0] in 'ar':
 
1417
                self._raise_invalid(path, file_id,
 
1418
                    "Wrong path for old path.")
 
1419
            if file_id != entry[0][2]:
 
1420
                self._raise_invalid(path, file_id,
 
1421
                    "Attempt to remove path has wrong id - found %r."
 
1422
                    % entry[0][2])
 
1423
            self._make_absent(entry)
 
1424
            # See if we have a malformed delta: deleting a directory must not
 
1425
            # leave crud behind. This increases the number of bisects needed
 
1426
            # substantially, but deletion or renames of large numbers of paths
 
1427
            # is rare enough it shouldn't be an issue (famous last words?) RBC
 
1428
            # 20080730.
 
1429
            block_i, entry_i, d_present, f_present = \
 
1430
                self._get_block_entry_index(path, '', 0)
 
1431
            if d_present:
 
1432
                # The dir block is still present in the dirstate; this could
 
1433
                # be due to it being in a parent tree, or a corrupt delta.
 
1434
                for child_entry in self._dirblocks[block_i][1]:
 
1435
                    if child_entry[1][0][0] not in ('r', 'a'):
 
1436
                        self._raise_invalid(path, entry[0][2],
 
1437
                            "The file id was deleted but its children were "
 
1438
                            "not deleted.")
 
1439
 
 
1440
    def _apply_insertions(self, adds):
 
1441
        try:
 
1442
            for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
 
1443
                self.update_minimal(key, minikind, executable, fingerprint,
 
1444
                                    path_utf8=path_utf8)
 
1445
        except errors.NotVersionedError:
 
1446
            self._raise_invalid(path_utf8.decode('utf8'), key[2],
 
1447
                "Missing parent")
 
1448
 
 
1449
    def update_basis_by_delta(self, delta, new_revid):
 
1450
        """Update the parents of this tree after a commit.
 
1451
 
 
1452
        This gives the tree one parent, with revision id new_revid. The
 
1453
        inventory delta is applied to the current basis tree to generate the
 
1454
        inventory for the parent new_revid, and all other parent trees are
 
1455
        discarded.
 
1456
 
 
1457
        Note that an exception during the operation of this method will leave
 
1458
        the dirstate in a corrupt state where it should not be saved.
 
1459
 
 
1460
        :param new_revid: The new revision id for the trees parent.
 
1461
        :param delta: An inventory delta (see apply_inventory_delta) describing
 
1462
            the changes from the current left most parent revision to new_revid.
 
1463
        """
 
1464
        self._read_dirblocks_if_needed()
 
1465
        self._discard_merge_parents()
 
1466
        if self._ghosts != []:
 
1467
            raise NotImplementedError(self.update_basis_by_delta)
 
1468
        if len(self._parents) == 0:
 
1469
            # setup a blank tree, the most simple way.
 
1470
            empty_parent = DirState.NULL_PARENT_DETAILS
 
1471
            for entry in self._iter_entries():
 
1472
                entry[1].append(empty_parent)
 
1473
            self._parents.append(new_revid)
 
1474
 
 
1475
        self._parents[0] = new_revid
 
1476
 
 
1477
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
 
1478
        adds = []
 
1479
        changes = []
 
1480
        deletes = []
 
1481
        # The paths this function accepts are unicode and must be encoded as we
 
1482
        # go.
 
1483
        encode = cache_utf8.encode
 
1484
        inv_to_entry = self._inv_entry_to_details
 
1485
        # delta is now (deletes, changes), (adds) in reverse lexographical
 
1486
        # order.
 
1487
        # deletes in reverse lexographic order are safe to process in situ.
 
1488
        # renames are not, as a rename from any path could go to a path
 
1489
        # lexographically lower, so we transform renames into delete, add pairs,
 
1490
        # expanding them recursively as needed.
 
1491
        # At the same time, to reduce interface friction we convert the input
 
1492
        # inventory entries to dirstate.
 
1493
        root_only = ('', '')
 
1494
        # Accumulate parent references (path_utf8, id), to check for parentless
 
1495
        # items or items placed under files/links/tree-references. We get
 
1496
        # references from every item in the delta that is not a deletion and
 
1497
        # is not itself the root.
 
1498
        parents = set()
 
1499
        # Added ids must not be in the dirstate already. This set holds those
 
1500
        # ids.
 
1501
        new_ids = set()
 
1502
        for old_path, new_path, file_id, inv_entry in delta:
 
1503
            if inv_entry is not None and file_id != inv_entry.file_id:
 
1504
                self._raise_invalid(new_path, file_id,
 
1505
                    "mismatched entry file_id %r" % inv_entry)
 
1506
            if new_path is None:
 
1507
                new_path_utf8 = None
 
1508
            else:
 
1509
                if inv_entry is None:
 
1510
                    self._raise_invalid(new_path, file_id,
 
1511
                        "new_path with no entry")
 
1512
                new_path_utf8 = encode(new_path)
 
1513
                # note the parent for validation
 
1514
                dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
 
1515
                if basename_utf8:
 
1516
                    parents.add((dirname_utf8, inv_entry.parent_id))
 
1517
            if old_path is None:
 
1518
                old_path_utf8 = None
 
1519
            else:
 
1520
                old_path_utf8 = encode(old_path)
 
1521
            if old_path is None:
 
1522
                adds.append((None, new_path_utf8, file_id,
 
1523
                    inv_to_entry(inv_entry), True))
 
1524
                new_ids.add(file_id)
 
1525
            elif new_path is None:
 
1526
                deletes.append((old_path_utf8, None, file_id, None, True))
 
1527
            elif (old_path, new_path) == root_only:
 
1528
                # change things in-place
 
1529
                # Note: the case of a parent directory changing its file_id
 
1530
                #       tends to break optimizations here, because officially
 
1531
                #       the file has actually been moved, it just happens to
 
1532
                #       end up at the same path. If we can figure out how to
 
1533
                #       handle that case, we can avoid a lot of add+delete
 
1534
                #       pairs for objects that stay put.
 
1535
                # elif old_path == new_path:
 
1536
                changes.append((old_path_utf8, new_path_utf8, file_id,
 
1537
                                inv_to_entry(inv_entry)))
 
1538
            else:
 
1539
                # Renames:
 
1540
                # Because renames must preserve their children we must have
 
1541
                # processed all relocations and removes before hand. The sort
 
1542
                # order ensures we've examined the child paths, but we also
 
1543
                # have to execute the removals, or the split to an add/delete
 
1544
                # pair will result in the deleted item being reinserted, or
 
1545
                # renamed items being reinserted twice - and possibly at the
 
1546
                # wrong place. Splitting into a delete/add pair also simplifies
 
1547
                # the handling of entries with ('f', ...), ('r' ...) because
 
1548
                # the target of the 'r' is old_path here, and we add that to
 
1549
                # deletes, meaning that the add handler does not need to check
 
1550
                # for 'r' items on every pass.
 
1551
                self._update_basis_apply_deletes(deletes)
 
1552
                deletes = []
 
1553
                # Split into an add/delete pair recursively.
 
1554
                adds.append((old_path_utf8, new_path_utf8, file_id,
 
1555
                             inv_to_entry(inv_entry), False))
 
1556
                # Expunge deletes that we've seen so that deleted/renamed
 
1557
                # children of a rename directory are handled correctly.
 
1558
                new_deletes = reversed(list(
 
1559
                    self._iter_child_entries(1, old_path_utf8)))
 
1560
                # Remove the current contents of the tree at orig_path, and
 
1561
                # reinsert at the correct new path.
 
1562
                for entry in new_deletes:
 
1563
                    child_dirname, child_basename, child_file_id = entry[0]
 
1564
                    if child_dirname:
 
1565
                        source_path = child_dirname + '/' + child_basename
 
1566
                    else:
 
1567
                        source_path = child_basename
 
1568
                    if new_path_utf8:
 
1569
                        target_path = \
 
1570
                            new_path_utf8 + source_path[len(old_path_utf8):]
 
1571
                    else:
 
1572
                        if old_path_utf8 == '':
 
1573
                            raise AssertionError("cannot rename directory to"
 
1574
                                                 " itself")
 
1575
                        target_path = source_path[len(old_path_utf8) + 1:]
 
1576
                    adds.append((None, target_path, entry[0][2], entry[1][1], False))
 
1577
                    deletes.append(
 
1578
                        (source_path, target_path, entry[0][2], None, False))
 
1579
                deletes.append(
 
1580
                    (old_path_utf8, new_path_utf8, file_id, None, False))
 
1581
 
 
1582
        self._check_delta_ids_absent(new_ids, delta, 1)
 
1583
        try:
 
1584
            # Finish expunging deletes/first half of renames.
 
1585
            self._update_basis_apply_deletes(deletes)
 
1586
            # Reinstate second half of renames and new paths.
 
1587
            self._update_basis_apply_adds(adds)
 
1588
            # Apply in-situ changes.
 
1589
            self._update_basis_apply_changes(changes)
 
1590
            # Validate parents
 
1591
            self._after_delta_check_parents(parents, 1)
 
1592
        except errors.BzrError as e:
 
1593
            self._changes_aborted = True
 
1594
            if 'integrity error' not in str(e):
 
1595
                raise
 
1596
            # _get_entry raises BzrError when a request is inconsistent; we
 
1597
            # want such errors to be shown as InconsistentDelta - and that
 
1598
            # fits the behaviour we trigger.
 
1599
            raise errors.InconsistentDeltaDelta(delta,
 
1600
                "error from _get_entry. %s" % (e,))
 
1601
 
 
1602
        self._mark_modified(header_modified=True)
 
1603
        self._id_index = None
 
1604
        return
 
1605
 
 
1606
    def _check_delta_ids_absent(self, new_ids, delta, tree_index):
 
1607
        """Check that none of the file_ids in new_ids are present in a tree."""
 
1608
        if not new_ids:
 
1609
            return
 
1610
        id_index = self._get_id_index()
 
1611
        for file_id in new_ids:
 
1612
            for key in id_index.get(file_id, ()):
 
1613
                block_i, entry_i, d_present, f_present = \
 
1614
                    self._get_block_entry_index(key[0], key[1], tree_index)
 
1615
                if not f_present:
 
1616
                    # In a different tree
 
1617
                    continue
 
1618
                entry = self._dirblocks[block_i][1][entry_i]
 
1619
                if entry[0][2] != file_id:
 
1620
                    # Different file_id, so not what we want.
 
1621
                    continue
 
1622
                self._raise_invalid(("%s/%s" % key[0:2]).decode('utf8'), file_id,
 
1623
                    "This file_id is new in the delta but already present in "
 
1624
                    "the target")
 
1625
 
 
1626
    def _raise_invalid(self, path, file_id, reason):
 
1627
        self._changes_aborted = True
 
1628
        raise errors.InconsistentDelta(path, file_id, reason)
 
1629
 
 
1630
    def _update_basis_apply_adds(self, adds):
 
1631
        """Apply a sequence of adds to tree 1 during update_basis_by_delta.
 
1632
 
 
1633
        They may be adds, or renames that have been split into add/delete
 
1634
        pairs.
 
1635
 
 
1636
        :param adds: A sequence of adds. Each add is a tuple:
 
1637
            (None, new_path_utf8, file_id, (entry_details), real_add). real_add
 
1638
            is False when the add is the second half of a remove-and-reinsert
 
1639
            pair created to handle renames and deletes.
 
1640
        """
 
1641
        # Adds are accumulated partly from renames, so can be in any input
 
1642
        # order - sort it.
 
1643
        # TODO: we may want to sort in dirblocks order. That way each entry
 
1644
        #       will end up in the same directory, allowing the _get_entry
 
1645
        #       fast-path for looking up 2 items in the same dir work.
 
1646
        adds.sort(key=lambda x: x[1])
 
1647
        # adds is now in lexographic order, which places all parents before
 
1648
        # their children, so we can process it linearly.
 
1649
        absent = 'ar'
 
1650
        st = static_tuple.StaticTuple
 
1651
        for old_path, new_path, file_id, new_details, real_add in adds:
 
1652
            dirname, basename = osutils.split(new_path)
 
1653
            entry_key = st(dirname, basename, file_id)
 
1654
            block_index, present = self._find_block_index_from_key(entry_key)
 
1655
            if not present:
 
1656
                # The block where we want to put the file is not present.
 
1657
                # However, it might have just been an empty directory. Look for
 
1658
                # the parent in the basis-so-far before throwing an error.
 
1659
                parent_dir, parent_base = osutils.split(dirname)
 
1660
                parent_block_idx, parent_entry_idx, _, parent_present = \
 
1661
                    self._get_block_entry_index(parent_dir, parent_base, 1)
 
1662
                if not parent_present:
 
1663
                    self._raise_invalid(new_path, file_id,
 
1664
                        "Unable to find block for this record."
 
1665
                        " Was the parent added?")
 
1666
                self._ensure_block(parent_block_idx, parent_entry_idx, dirname)
 
1667
 
 
1668
            block = self._dirblocks[block_index][1]
 
1669
            entry_index, present = self._find_entry_index(entry_key, block)
 
1670
            if real_add:
 
1671
                if old_path is not None:
 
1672
                    self._raise_invalid(new_path, file_id,
 
1673
                        'considered a real add but still had old_path at %s'
 
1674
                        % (old_path,))
 
1675
            if present:
 
1676
                entry = block[entry_index]
 
1677
                basis_kind = entry[1][1][0]
 
1678
                if basis_kind == 'a':
 
1679
                    entry[1][1] = new_details
 
1680
                elif basis_kind == 'r':
 
1681
                    raise NotImplementedError()
 
1682
                else:
 
1683
                    self._raise_invalid(new_path, file_id,
 
1684
                        "An entry was marked as a new add"
 
1685
                        " but the basis target already existed")
 
1686
            else:
 
1687
                # The exact key was not found in the block. However, we need to
 
1688
                # check if there is a key next to us that would have matched.
 
1689
                # We only need to check 2 locations, because there are only 2
 
1690
                # trees present.
 
1691
                for maybe_index in range(entry_index-1, entry_index+1):
 
1692
                    if maybe_index < 0 or maybe_index >= len(block):
 
1693
                        continue
 
1694
                    maybe_entry = block[maybe_index]
 
1695
                    if maybe_entry[0][:2] != (dirname, basename):
 
1696
                        # Just a random neighbor
 
1697
                        continue
 
1698
                    if maybe_entry[0][2] == file_id:
 
1699
                        raise AssertionError(
 
1700
                            '_find_entry_index didnt find a key match'
 
1701
                            ' but walking the data did, for %s'
 
1702
                            % (entry_key,))
 
1703
                    basis_kind = maybe_entry[1][1][0]
 
1704
                    if basis_kind not in 'ar':
 
1705
                        self._raise_invalid(new_path, file_id,
 
1706
                            "we have an add record for path, but the path"
 
1707
                            " is already present with another file_id %s"
 
1708
                            % (maybe_entry[0][2],))
 
1709
 
 
1710
                entry = (entry_key, [DirState.NULL_PARENT_DETAILS,
 
1711
                                     new_details])
 
1712
                block.insert(entry_index, entry)
 
1713
 
 
1714
            active_kind = entry[1][0][0]
 
1715
            if active_kind == 'a':
 
1716
                # The active record shows up as absent, this could be genuine,
 
1717
                # or it could be present at some other location. We need to
 
1718
                # verify.
 
1719
                id_index = self._get_id_index()
 
1720
                # The id_index may not be perfectly accurate for tree1, because
 
1721
                # we haven't been keeping it updated. However, it should be
 
1722
                # fine for tree0, and that gives us enough info for what we
 
1723
                # need
 
1724
                keys = id_index.get(file_id, ())
 
1725
                for key in keys:
 
1726
                    block_i, entry_i, d_present, f_present = \
 
1727
                        self._get_block_entry_index(key[0], key[1], 0)
 
1728
                    if not f_present:
 
1729
                        continue
 
1730
                    active_entry = self._dirblocks[block_i][1][entry_i]
 
1731
                    if (active_entry[0][2] != file_id):
 
1732
                        # Some other file is at this path, we don't need to
 
1733
                        # link it.
 
1734
                        continue
 
1735
                    real_active_kind = active_entry[1][0][0]
 
1736
                    if real_active_kind in 'ar':
 
1737
                        # We found a record, which was not *this* record,
 
1738
                        # which matches the file_id, but is not actually
 
1739
                        # present. Something seems *really* wrong.
 
1740
                        self._raise_invalid(new_path, file_id,
 
1741
                            "We found a tree0 entry that doesnt make sense")
 
1742
                    # Now, we've found a tree0 entry which matches the file_id
 
1743
                    # but is at a different location. So update them to be
 
1744
                    # rename records.
 
1745
                    active_dir, active_name = active_entry[0][:2]
 
1746
                    if active_dir:
 
1747
                        active_path = active_dir + '/' + active_name
 
1748
                    else:
 
1749
                        active_path = active_name
 
1750
                    active_entry[1][1] = st('r', new_path, 0, False, '')
 
1751
                    entry[1][0] = st('r', active_path, 0, False, '')
 
1752
            elif active_kind == 'r':
 
1753
                raise NotImplementedError()
 
1754
 
 
1755
            new_kind = new_details[0]
 
1756
            if new_kind == 'd':
 
1757
                self._ensure_block(block_index, entry_index, new_path)
 
1758
 
 
1759
    def _update_basis_apply_changes(self, changes):
 
1760
        """Apply a sequence of changes to tree 1 during update_basis_by_delta.
 
1761
 
 
1762
        :param adds: A sequence of changes. Each change is a tuple:
 
1763
            (path_utf8, path_utf8, file_id, (entry_details))
 
1764
        """
 
1765
        absent = 'ar'
 
1766
        for old_path, new_path, file_id, new_details in changes:
 
1767
            # the entry for this file_id must be in tree 0.
 
1768
            entry = self._get_entry(1, file_id, new_path)
 
1769
            if entry[0] is None or entry[1][1][0] in 'ar':
 
1770
                self._raise_invalid(new_path, file_id,
 
1771
                    'changed entry considered not present')
 
1772
            entry[1][1] = new_details
 
1773
 
 
1774
    def _update_basis_apply_deletes(self, deletes):
 
1775
        """Apply a sequence of deletes to tree 1 during update_basis_by_delta.
 
1776
 
 
1777
        They may be deletes, or renames that have been split into add/delete
 
1778
        pairs.
 
1779
 
 
1780
        :param deletes: A sequence of deletes. Each delete is a tuple:
 
1781
            (old_path_utf8, new_path_utf8, file_id, None, real_delete).
 
1782
            real_delete is True when the desired outcome is an actual deletion
 
1783
            rather than the rename handling logic temporarily deleting a path
 
1784
            during the replacement of a parent.
 
1785
        """
 
1786
        null = DirState.NULL_PARENT_DETAILS
 
1787
        for old_path, new_path, file_id, _, real_delete in deletes:
 
1788
            if real_delete != (new_path is None):
 
1789
                self._raise_invalid(old_path, file_id, "bad delete delta")
 
1790
            # the entry for this file_id must be in tree 1.
 
1791
            dirname, basename = osutils.split(old_path)
 
1792
            block_index, entry_index, dir_present, file_present = \
 
1793
                self._get_block_entry_index(dirname, basename, 1)
 
1794
            if not file_present:
 
1795
                self._raise_invalid(old_path, file_id,
 
1796
                    'basis tree does not contain removed entry')
 
1797
            entry = self._dirblocks[block_index][1][entry_index]
 
1798
            # The state of the entry in the 'active' WT
 
1799
            active_kind = entry[1][0][0]
 
1800
            if entry[0][2] != file_id:
 
1801
                self._raise_invalid(old_path, file_id,
 
1802
                    'mismatched file_id in tree 1')
 
1803
            dir_block = ()
 
1804
            old_kind = entry[1][1][0]
 
1805
            if active_kind in 'ar':
 
1806
                # The active tree doesn't have this file_id.
 
1807
                # The basis tree is changing this record. If this is a
 
1808
                # rename, then we don't want the record here at all
 
1809
                # anymore. If it is just an in-place change, we want the
 
1810
                # record here, but we'll add it if we need to. So we just
 
1811
                # delete it
 
1812
                if active_kind == 'r':
 
1813
                    active_path = entry[1][0][1]
 
1814
                    active_entry = self._get_entry(0, file_id, active_path)
 
1815
                    if active_entry[1][1][0] != 'r':
 
1816
                        self._raise_invalid(old_path, file_id,
 
1817
                            "Dirstate did not have matching rename entries")
 
1818
                    elif active_entry[1][0][0] in 'ar':
 
1819
                        self._raise_invalid(old_path, file_id,
 
1820
                            "Dirstate had a rename pointing at an inactive"
 
1821
                            " tree0")
 
1822
                    active_entry[1][1] = null
 
1823
                del self._dirblocks[block_index][1][entry_index]
 
1824
                if old_kind == 'd':
 
1825
                    # This was a directory, and the active tree says it
 
1826
                    # doesn't exist, and now the basis tree says it doesn't
 
1827
                    # exist. Remove its dirblock if present
 
1828
                    (dir_block_index,
 
1829
                     present) = self._find_block_index_from_key(
 
1830
                        (old_path, '', ''))
 
1831
                    if present:
 
1832
                        dir_block = self._dirblocks[dir_block_index][1]
 
1833
                        if not dir_block:
 
1834
                            # This entry is empty, go ahead and just remove it
 
1835
                            del self._dirblocks[dir_block_index]
 
1836
            else:
 
1837
                # There is still an active record, so just mark this
 
1838
                # removed.
 
1839
                entry[1][1] = null
 
1840
                block_i, entry_i, d_present, f_present = \
 
1841
                    self._get_block_entry_index(old_path, '', 1)
 
1842
                if d_present:
 
1843
                    dir_block = self._dirblocks[block_i][1]
 
1844
            for child_entry in dir_block:
 
1845
                child_basis_kind = child_entry[1][1][0]
 
1846
                if child_basis_kind not in 'ar':
 
1847
                    self._raise_invalid(old_path, file_id,
 
1848
                        "The file id was deleted but its children were "
 
1849
                        "not deleted.")
 
1850
 
 
1851
    def _after_delta_check_parents(self, parents, index):
 
1852
        """Check that parents required by the delta are all intact.
 
1853
        
 
1854
        :param parents: An iterable of (path_utf8, file_id) tuples which are
 
1855
            required to be present in tree 'index' at path_utf8 with id file_id
 
1856
            and be a directory.
 
1857
        :param index: The column in the dirstate to check for parents in.
 
1858
        """
 
1859
        for dirname_utf8, file_id in parents:
 
1860
            # Get the entry - the ensures that file_id, dirname_utf8 exists and
 
1861
            # has the right file id.
 
1862
            entry = self._get_entry(index, file_id, dirname_utf8)
 
1863
            if entry[1] is None:
 
1864
                self._raise_invalid(dirname_utf8.decode('utf8'),
 
1865
                    file_id, "This parent is not present.")
 
1866
            # Parents of things must be directories
 
1867
            if entry[1][index][0] != 'd':
 
1868
                self._raise_invalid(dirname_utf8.decode('utf8'),
 
1869
                    file_id, "This parent is not a directory.")
 
1870
 
 
1871
    def _observed_sha1(self, entry, sha1, stat_value,
 
1872
        _stat_to_minikind=_stat_to_minikind):
 
1873
        """Note the sha1 of a file.
 
1874
 
 
1875
        :param entry: The entry the sha1 is for.
 
1876
        :param sha1: The observed sha1.
 
1877
        :param stat_value: The os.lstat for the file.
 
1878
        """
 
1879
        try:
 
1880
            minikind = _stat_to_minikind[stat_value.st_mode & 0o170000]
 
1881
        except KeyError:
 
1882
            # Unhandled kind
 
1883
            return None
 
1884
        if minikind == 'f':
 
1885
            if self._cutoff_time is None:
 
1886
                self._sha_cutoff_time()
 
1887
            if (stat_value.st_mtime < self._cutoff_time
 
1888
                and stat_value.st_ctime < self._cutoff_time):
 
1889
                entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
 
1890
                               pack_stat(stat_value))
 
1891
                self._mark_modified([entry])
 
1892
 
 
1893
    def _sha_cutoff_time(self):
 
1894
        """Return cutoff time.
 
1895
 
 
1896
        Files modified more recently than this time are at risk of being
 
1897
        undetectably modified and so can't be cached.
 
1898
        """
 
1899
        # Cache the cutoff time as long as we hold a lock.
 
1900
        # time.time() isn't super expensive (approx 3.38us), but
 
1901
        # when you call it 50,000 times it adds up.
 
1902
        # For comparison, os.lstat() costs 7.2us if it is hot.
 
1903
        self._cutoff_time = int(time.time()) - 3
 
1904
        return self._cutoff_time
 
1905
 
 
1906
    def _lstat(self, abspath, entry):
 
1907
        """Return the os.lstat value for this path."""
 
1908
        return os.lstat(abspath)
 
1909
 
 
1910
    def _sha1_file_and_mutter(self, abspath):
 
1911
        # when -Dhashcache is turned on, this is monkey-patched in to log
 
1912
        # file reads
 
1913
        trace.mutter("dirstate sha1 " + abspath)
 
1914
        return self._sha1_provider.sha1(abspath)
 
1915
 
 
1916
    def _is_executable(self, mode, old_executable):
 
1917
        """Is this file executable?"""
 
1918
        return bool(S_IEXEC & mode)
 
1919
 
 
1920
    def _is_executable_win32(self, mode, old_executable):
 
1921
        """On win32 the executable bit is stored in the dirstate."""
 
1922
        return old_executable
 
1923
 
 
1924
    if sys.platform == 'win32':
 
1925
        _is_executable = _is_executable_win32
 
1926
 
 
1927
    def _read_link(self, abspath, old_link):
 
1928
        """Read the target of a symlink"""
 
1929
        # TODO: jam 200700301 On Win32, this could just return the value
 
1930
        #       already in memory. However, this really needs to be done at a
 
1931
        #       higher level, because there either won't be anything on disk,
 
1932
        #       or the thing on disk will be a file.
 
1933
        fs_encoding = osutils._fs_enc
 
1934
        if isinstance(abspath, unicode):
 
1935
            # abspath is defined as the path to pass to lstat. readlink is
 
1936
            # buggy in python < 2.6 (it doesn't encode unicode path into FS
 
1937
            # encoding), so we need to encode ourselves knowing that unicode
 
1938
            # paths are produced by UnicodeDirReader on purpose.
 
1939
            abspath = abspath.encode(fs_encoding)
 
1940
        target = os.readlink(abspath)
 
1941
        if fs_encoding not in ('utf-8', 'ascii'):
 
1942
            # Change encoding if needed
 
1943
            target = target.decode(fs_encoding).encode('UTF-8')
 
1944
        return target
 
1945
 
 
1946
    def get_ghosts(self):
 
1947
        """Return a list of the parent tree revision ids that are ghosts."""
 
1948
        self._read_header_if_needed()
 
1949
        return self._ghosts
 
1950
 
 
1951
    def get_lines(self):
 
1952
        """Serialise the entire dirstate to a sequence of lines."""
 
1953
        if (self._header_state == DirState.IN_MEMORY_UNMODIFIED and
 
1954
            self._dirblock_state == DirState.IN_MEMORY_UNMODIFIED):
 
1955
            # read what's on disk.
 
1956
            self._state_file.seek(0)
 
1957
            return self._state_file.readlines()
 
1958
        lines = []
 
1959
        lines.append(self._get_parents_line(self.get_parent_ids()))
 
1960
        lines.append(self._get_ghosts_line(self._ghosts))
 
1961
        lines.extend(self._iter_entry_lines())
 
1962
        return self._get_output_lines(lines)
 
1963
 
 
1964
    def _get_ghosts_line(self, ghost_ids):
 
1965
        """Create a line for the state file for ghost information."""
 
1966
        return '\0'.join([str(len(ghost_ids))] + ghost_ids)
 
1967
 
 
1968
    def _get_parents_line(self, parent_ids):
 
1969
        """Create a line for the state file for parents information."""
 
1970
        return '\0'.join([str(len(parent_ids))] + parent_ids)
 
1971
 
 
1972
    def _iter_entry_lines(self):
 
1973
        """Create lines for entries."""
 
1974
        return map(self._entry_to_line, self._iter_entries())
 
1975
 
 
1976
    def _get_fields_to_entry(self):
 
1977
        """Get a function which converts entry fields into a entry record.
 
1978
 
 
1979
        This handles size and executable, as well as parent records.
 
1980
 
 
1981
        :return: A function which takes a list of fields, and returns an
 
1982
            appropriate record for storing in memory.
 
1983
        """
 
1984
        # This is intentionally unrolled for performance
 
1985
        num_present_parents = self._num_present_parents()
 
1986
        if num_present_parents == 0:
 
1987
            def fields_to_entry_0_parents(fields, _int=int):
 
1988
                path_name_file_id_key = (fields[0], fields[1], fields[2])
 
1989
                return (path_name_file_id_key, [
 
1990
                    ( # Current tree
 
1991
                        fields[3],                # minikind
 
1992
                        fields[4],                # fingerprint
 
1993
                        _int(fields[5]),          # size
 
1994
                        fields[6] == 'y',         # executable
 
1995
                        fields[7],                # packed_stat or revision_id
 
1996
                    )])
 
1997
            return fields_to_entry_0_parents
 
1998
        elif num_present_parents == 1:
 
1999
            def fields_to_entry_1_parent(fields, _int=int):
 
2000
                path_name_file_id_key = (fields[0], fields[1], fields[2])
 
2001
                return (path_name_file_id_key, [
 
2002
                    ( # Current tree
 
2003
                        fields[3],                # minikind
 
2004
                        fields[4],                # fingerprint
 
2005
                        _int(fields[5]),          # size
 
2006
                        fields[6] == 'y',         # executable
 
2007
                        fields[7],                # packed_stat or revision_id
 
2008
                    ),
 
2009
                    ( # Parent 1
 
2010
                        fields[8],                # minikind
 
2011
                        fields[9],                # fingerprint
 
2012
                        _int(fields[10]),         # size
 
2013
                        fields[11] == 'y',        # executable
 
2014
                        fields[12],               # packed_stat or revision_id
 
2015
                    ),
 
2016
                    ])
 
2017
            return fields_to_entry_1_parent
 
2018
        elif num_present_parents == 2:
 
2019
            def fields_to_entry_2_parents(fields, _int=int):
 
2020
                path_name_file_id_key = (fields[0], fields[1], fields[2])
 
2021
                return (path_name_file_id_key, [
 
2022
                    ( # Current tree
 
2023
                        fields[3],                # minikind
 
2024
                        fields[4],                # fingerprint
 
2025
                        _int(fields[5]),          # size
 
2026
                        fields[6] == 'y',         # executable
 
2027
                        fields[7],                # packed_stat or revision_id
 
2028
                    ),
 
2029
                    ( # Parent 1
 
2030
                        fields[8],                # minikind
 
2031
                        fields[9],                # fingerprint
 
2032
                        _int(fields[10]),         # size
 
2033
                        fields[11] == 'y',        # executable
 
2034
                        fields[12],               # packed_stat or revision_id
 
2035
                    ),
 
2036
                    ( # Parent 2
 
2037
                        fields[13],               # minikind
 
2038
                        fields[14],               # fingerprint
 
2039
                        _int(fields[15]),         # size
 
2040
                        fields[16] == 'y',        # executable
 
2041
                        fields[17],               # packed_stat or revision_id
 
2042
                    ),
 
2043
                    ])
 
2044
            return fields_to_entry_2_parents
 
2045
        else:
 
2046
            def fields_to_entry_n_parents(fields, _int=int):
 
2047
                path_name_file_id_key = (fields[0], fields[1], fields[2])
 
2048
                trees = [(fields[cur],                # minikind
 
2049
                          fields[cur+1],              # fingerprint
 
2050
                          _int(fields[cur+2]),        # size
 
2051
                          fields[cur+3] == 'y',       # executable
 
2052
                          fields[cur+4],              # stat or revision_id
 
2053
                         ) for cur in range(3, len(fields)-1, 5)]
 
2054
                return path_name_file_id_key, trees
 
2055
            return fields_to_entry_n_parents
 
2056
 
 
2057
    def get_parent_ids(self):
 
2058
        """Return a list of the parent tree ids for the directory state."""
 
2059
        self._read_header_if_needed()
 
2060
        return list(self._parents)
 
2061
 
 
2062
    def _get_block_entry_index(self, dirname, basename, tree_index):
 
2063
        """Get the coordinates for a path in the state structure.
 
2064
 
 
2065
        :param dirname: The utf8 dirname to lookup.
 
2066
        :param basename: The utf8 basename to lookup.
 
2067
        :param tree_index: The index of the tree for which this lookup should
 
2068
            be attempted.
 
2069
        :return: A tuple describing where the path is located, or should be
 
2070
            inserted. The tuple contains four fields: the block index, the row
 
2071
            index, the directory is present (boolean), the entire path is
 
2072
            present (boolean).  There is no guarantee that either
 
2073
            coordinate is currently reachable unless the found field for it is
 
2074
            True. For instance, a directory not present in the searched tree
 
2075
            may be returned with a value one greater than the current highest
 
2076
            block offset. The directory present field will always be True when
 
2077
            the path present field is True. The directory present field does
 
2078
            NOT indicate that the directory is present in the searched tree,
 
2079
            rather it indicates that there are at least some files in some
 
2080
            tree present there.
 
2081
        """
 
2082
        self._read_dirblocks_if_needed()
 
2083
        key = dirname, basename, ''
 
2084
        block_index, present = self._find_block_index_from_key(key)
 
2085
        if not present:
 
2086
            # no such directory - return the dir index and 0 for the row.
 
2087
            return block_index, 0, False, False
 
2088
        block = self._dirblocks[block_index][1] # access the entries only
 
2089
        entry_index, present = self._find_entry_index(key, block)
 
2090
        # linear search through entries at this path to find the one
 
2091
        # requested.
 
2092
        while entry_index < len(block) and block[entry_index][0][1] == basename:
 
2093
            if block[entry_index][1][tree_index][0] not in 'ar':
 
2094
                # neither absent or relocated
 
2095
                return block_index, entry_index, True, True
 
2096
            entry_index += 1
 
2097
        return block_index, entry_index, True, False
 
2098
 
 
2099
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None,
 
2100
                   include_deleted=False):
 
2101
        """Get the dirstate entry for path in tree tree_index.
 
2102
 
 
2103
        If either file_id or path is supplied, it is used as the key to lookup.
 
2104
        If both are supplied, the fastest lookup is used, and an error is
 
2105
        raised if they do not both point at the same row.
 
2106
 
 
2107
        :param tree_index: The index of the tree we wish to locate this path
 
2108
            in. If the path is present in that tree, the entry containing its
 
2109
            details is returned, otherwise (None, None) is returned
 
2110
            0 is the working tree, higher indexes are successive parent
 
2111
            trees.
 
2112
        :param fileid_utf8: A utf8 file_id to look up.
 
2113
        :param path_utf8: An utf8 path to be looked up.
 
2114
        :param include_deleted: If True, and performing a lookup via
 
2115
            fileid_utf8 rather than path_utf8, return an entry for deleted
 
2116
            (absent) paths.
 
2117
        :return: The dirstate entry tuple for path, or (None, None)
 
2118
        """
 
2119
        self._read_dirblocks_if_needed()
 
2120
        if path_utf8 is not None:
 
2121
            if not isinstance(path_utf8, str):
 
2122
                raise errors.BzrError('path_utf8 is not a str: %s %r'
 
2123
                    % (type(path_utf8), path_utf8))
 
2124
            # path lookups are faster
 
2125
            dirname, basename = osutils.split(path_utf8)
 
2126
            block_index, entry_index, dir_present, file_present = \
 
2127
                self._get_block_entry_index(dirname, basename, tree_index)
 
2128
            if not file_present:
 
2129
                return None, None
 
2130
            entry = self._dirblocks[block_index][1][entry_index]
 
2131
            if not (entry[0][2] and entry[1][tree_index][0] not in ('a', 'r')):
 
2132
                raise AssertionError('unversioned entry?')
 
2133
            if fileid_utf8:
 
2134
                if entry[0][2] != fileid_utf8:
 
2135
                    self._changes_aborted = True
 
2136
                    raise errors.BzrError('integrity error ? : mismatching'
 
2137
                                          ' tree_index, file_id and path')
 
2138
            return entry
 
2139
        else:
 
2140
            possible_keys = self._get_id_index().get(fileid_utf8, ())
 
2141
            if not possible_keys:
 
2142
                return None, None
 
2143
            for key in possible_keys:
 
2144
                block_index, present = \
 
2145
                    self._find_block_index_from_key(key)
 
2146
                # strange, probably indicates an out of date
 
2147
                # id index - for now, allow this.
 
2148
                if not present:
 
2149
                    continue
 
2150
                # WARNING: DO not change this code to use _get_block_entry_index
 
2151
                # as that function is not suitable: it does not use the key
 
2152
                # to lookup, and thus the wrong coordinates are returned.
 
2153
                block = self._dirblocks[block_index][1]
 
2154
                entry_index, present = self._find_entry_index(key, block)
 
2155
                if present:
 
2156
                    entry = self._dirblocks[block_index][1][entry_index]
 
2157
                    # TODO: We might want to assert that entry[0][2] ==
 
2158
                    #       fileid_utf8.
 
2159
                    if entry[1][tree_index][0] in 'fdlt':
 
2160
                        # this is the result we are looking for: the
 
2161
                        # real home of this file_id in this tree.
 
2162
                        return entry
 
2163
                    if entry[1][tree_index][0] == 'a':
 
2164
                        # there is no home for this entry in this tree
 
2165
                        if include_deleted:
 
2166
                            return entry
 
2167
                        return None, None
 
2168
                    if entry[1][tree_index][0] != 'r':
 
2169
                        raise AssertionError(
 
2170
                            "entry %r has invalid minikind %r for tree %r" \
 
2171
                            % (entry,
 
2172
                               entry[1][tree_index][0],
 
2173
                               tree_index))
 
2174
                    real_path = entry[1][tree_index][1]
 
2175
                    return self._get_entry(tree_index, fileid_utf8=fileid_utf8,
 
2176
                        path_utf8=real_path)
 
2177
            return None, None
 
2178
 
 
2179
    @classmethod
 
2180
    def initialize(cls, path, sha1_provider=None):
 
2181
        """Create a new dirstate on path.
 
2182
 
 
2183
        The new dirstate will be an empty tree - that is it has no parents,
 
2184
        and only a root node - which has id ROOT_ID.
 
2185
 
 
2186
        :param path: The name of the file for the dirstate.
 
2187
        :param sha1_provider: an object meeting the SHA1Provider interface.
 
2188
            If None, a DefaultSHA1Provider is used.
 
2189
        :return: A write-locked DirState object.
 
2190
        """
 
2191
        # This constructs a new DirState object on a path, sets the _state_file
 
2192
        # to a new empty file for that path. It then calls _set_data() with our
 
2193
        # stock empty dirstate information - a root with ROOT_ID, no children,
 
2194
        # and no parents. Finally it calls save() to ensure that this data will
 
2195
        # persist.
 
2196
        if sha1_provider is None:
 
2197
            sha1_provider = DefaultSHA1Provider()
 
2198
        result = cls(path, sha1_provider)
 
2199
        # root dir and root dir contents with no children.
 
2200
        empty_tree_dirblocks = [('', []), ('', [])]
 
2201
        # a new root directory, with a NULLSTAT.
 
2202
        empty_tree_dirblocks[0][1].append(
 
2203
            (('', '', inventory.ROOT_ID), [
 
2204
                ('d', '', 0, False, DirState.NULLSTAT),
 
2205
            ]))
 
2206
        result.lock_write()
 
2207
        try:
 
2208
            result._set_data([], empty_tree_dirblocks)
 
2209
            result.save()
 
2210
        except:
 
2211
            result.unlock()
 
2212
            raise
 
2213
        return result
 
2214
 
 
2215
    @staticmethod
 
2216
    def _inv_entry_to_details(inv_entry):
 
2217
        """Convert an inventory entry (from a revision tree) to state details.
 
2218
 
 
2219
        :param inv_entry: An inventory entry whose sha1 and link targets can be
 
2220
            relied upon, and which has a revision set.
 
2221
        :return: A details tuple - the details for a single tree at a path +
 
2222
            id.
 
2223
        """
 
2224
        kind = inv_entry.kind
 
2225
        minikind = DirState._kind_to_minikind[kind]
 
2226
        tree_data = inv_entry.revision
 
2227
        if kind == 'directory':
 
2228
            fingerprint = ''
 
2229
            size = 0
 
2230
            executable = False
 
2231
        elif kind == 'symlink':
 
2232
            if inv_entry.symlink_target is None:
 
2233
                fingerprint = ''
 
2234
            else:
 
2235
                fingerprint = inv_entry.symlink_target.encode('utf8')
 
2236
            size = 0
 
2237
            executable = False
 
2238
        elif kind == 'file':
 
2239
            fingerprint = inv_entry.text_sha1 or ''
 
2240
            size = inv_entry.text_size or 0
 
2241
            executable = inv_entry.executable
 
2242
        elif kind == 'tree-reference':
 
2243
            fingerprint = inv_entry.reference_revision or ''
 
2244
            size = 0
 
2245
            executable = False
 
2246
        else:
 
2247
            raise Exception("can't pack %s" % inv_entry)
 
2248
        return static_tuple.StaticTuple(minikind, fingerprint, size,
 
2249
                                        executable, tree_data)
 
2250
 
 
2251
    def _iter_child_entries(self, tree_index, path_utf8):
 
2252
        """Iterate over all the entries that are children of path_utf.
 
2253
 
 
2254
        This only returns entries that are present (not in 'a', 'r') in
 
2255
        tree_index. tree_index data is not refreshed, so if tree 0 is used,
 
2256
        results may differ from that obtained if paths were statted to
 
2257
        determine what ones were directories.
 
2258
 
 
2259
        Asking for the children of a non-directory will return an empty
 
2260
        iterator.
 
2261
        """
 
2262
        pending_dirs = []
 
2263
        next_pending_dirs = [path_utf8]
 
2264
        absent = 'ar'
 
2265
        while next_pending_dirs:
 
2266
            pending_dirs = next_pending_dirs
 
2267
            next_pending_dirs = []
 
2268
            for path in pending_dirs:
 
2269
                block_index, present = self._find_block_index_from_key(
 
2270
                    (path, '', ''))
 
2271
                if block_index == 0:
 
2272
                    block_index = 1
 
2273
                    if len(self._dirblocks) == 1:
 
2274
                        # asked for the children of the root with no other
 
2275
                        # contents.
 
2276
                        return
 
2277
                if not present:
 
2278
                    # children of a non-directory asked for.
 
2279
                    continue
 
2280
                block = self._dirblocks[block_index]
 
2281
                for entry in block[1]:
 
2282
                    kind = entry[1][tree_index][0]
 
2283
                    if kind not in absent:
 
2284
                        yield entry
 
2285
                    if kind == 'd':
 
2286
                        if entry[0][0]:
 
2287
                            path = entry[0][0] + '/' + entry[0][1]
 
2288
                        else:
 
2289
                            path = entry[0][1]
 
2290
                        next_pending_dirs.append(path)
 
2291
 
 
2292
    def _iter_entries(self):
 
2293
        """Iterate over all the entries in the dirstate.
 
2294
 
 
2295
        Each yelt item is an entry in the standard format described in the
 
2296
        docstring of breezy.dirstate.
 
2297
        """
 
2298
        self._read_dirblocks_if_needed()
 
2299
        for directory in self._dirblocks:
 
2300
            for entry in directory[1]:
 
2301
                yield entry
 
2302
 
 
2303
    def _get_id_index(self):
 
2304
        """Get an id index of self._dirblocks.
 
2305
 
 
2306
        This maps from file_id => [(directory, name, file_id)] entries where
 
2307
        that file_id appears in one of the trees.
 
2308
        """
 
2309
        if self._id_index is None:
 
2310
            id_index = {}
 
2311
            for key, tree_details in self._iter_entries():
 
2312
                self._add_to_id_index(id_index, key)
 
2313
            self._id_index = id_index
 
2314
        return self._id_index
 
2315
 
 
2316
    def _add_to_id_index(self, id_index, entry_key):
 
2317
        """Add this entry to the _id_index mapping."""
 
2318
        # This code used to use a set for every entry in the id_index. However,
 
2319
        # it is *rare* to have more than one entry. So a set is a large
 
2320
        # overkill. And even when we do, we won't ever have more than the
 
2321
        # number of parent trees. Which is still a small number (rarely >2). As
 
2322
        # such, we use a simple tuple, and do our own uniqueness checks. While
 
2323
        # the 'in' check is O(N) since N is nicely bounded it shouldn't ever
 
2324
        # cause quadratic failure.
 
2325
        file_id = entry_key[2]
 
2326
        entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
 
2327
        if file_id not in id_index:
 
2328
            id_index[file_id] = static_tuple.StaticTuple(entry_key,)
 
2329
        else:
 
2330
            entry_keys = id_index[file_id]
 
2331
            if entry_key not in entry_keys:
 
2332
                id_index[file_id] = entry_keys + (entry_key,)
 
2333
 
 
2334
    def _remove_from_id_index(self, id_index, entry_key):
 
2335
        """Remove this entry from the _id_index mapping.
 
2336
 
 
2337
        It is an programming error to call this when the entry_key is not
 
2338
        already present.
 
2339
        """
 
2340
        file_id = entry_key[2]
 
2341
        entry_keys = list(id_index[file_id])
 
2342
        entry_keys.remove(entry_key)
 
2343
        id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
 
2344
 
 
2345
    def _get_output_lines(self, lines):
 
2346
        """Format lines for final output.
 
2347
 
 
2348
        :param lines: A sequence of lines containing the parents list and the
 
2349
            path lines.
 
2350
        """
 
2351
        output_lines = [DirState.HEADER_FORMAT_3]
 
2352
        lines.append('') # a final newline
 
2353
        inventory_text = '\0\n\0'.join(lines)
 
2354
        output_lines.append('crc32: %s\n' % (zlib.crc32(inventory_text),))
 
2355
        # -3, 1 for num parents, 1 for ghosts, 1 for final newline
 
2356
        num_entries = len(lines)-3
 
2357
        output_lines.append('num_entries: %s\n' % (num_entries,))
 
2358
        output_lines.append(inventory_text)
 
2359
        return output_lines
 
2360
 
 
2361
    def _make_deleted_row(self, fileid_utf8, parents):
 
2362
        """Return a deleted row for fileid_utf8."""
 
2363
        return ('/', 'RECYCLED.BIN', 'file', fileid_utf8, 0, DirState.NULLSTAT,
 
2364
            ''), parents
 
2365
 
 
2366
    def _num_present_parents(self):
 
2367
        """The number of parent entries in each record row."""
 
2368
        return len(self._parents) - len(self._ghosts)
 
2369
 
 
2370
    @classmethod
 
2371
    def on_file(cls, path, sha1_provider=None, worth_saving_limit=0):
 
2372
        """Construct a DirState on the file at path "path".
 
2373
 
 
2374
        :param path: The path at which the dirstate file on disk should live.
 
2375
        :param sha1_provider: an object meeting the SHA1Provider interface.
 
2376
            If None, a DefaultSHA1Provider is used.
 
2377
        :param worth_saving_limit: when the exact number of hash changed
 
2378
            entries is known, only bother saving the dirstate if more than
 
2379
            this count of entries have changed. -1 means never save.
 
2380
        :return: An unlocked DirState object, associated with the given path.
 
2381
        """
 
2382
        if sha1_provider is None:
 
2383
            sha1_provider = DefaultSHA1Provider()
 
2384
        result = cls(path, sha1_provider,
 
2385
                     worth_saving_limit=worth_saving_limit)
 
2386
        return result
 
2387
 
 
2388
    def _read_dirblocks_if_needed(self):
 
2389
        """Read in all the dirblocks from the file if they are not in memory.
 
2390
 
 
2391
        This populates self._dirblocks, and sets self._dirblock_state to
 
2392
        IN_MEMORY_UNMODIFIED. It is not currently ready for incremental block
 
2393
        loading.
 
2394
        """
 
2395
        self._read_header_if_needed()
 
2396
        if self._dirblock_state == DirState.NOT_IN_MEMORY:
 
2397
            _read_dirblocks(self)
 
2398
 
 
2399
    def _read_header(self):
 
2400
        """This reads in the metadata header, and the parent ids.
 
2401
 
 
2402
        After reading in, the file should be positioned at the null
 
2403
        just before the start of the first record in the file.
 
2404
 
 
2405
        :return: (expected crc checksum, number of entries, parent list)
 
2406
        """
 
2407
        self._read_prelude()
 
2408
        parent_line = self._state_file.readline()
 
2409
        info = parent_line.split('\0')
 
2410
        num_parents = int(info[0])
 
2411
        self._parents = info[1:-1]
 
2412
        ghost_line = self._state_file.readline()
 
2413
        info = ghost_line.split('\0')
 
2414
        num_ghosts = int(info[1])
 
2415
        self._ghosts = info[2:-1]
 
2416
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
 
2417
        self._end_of_header = self._state_file.tell()
 
2418
 
 
2419
    def _read_header_if_needed(self):
 
2420
        """Read the header of the dirstate file if needed."""
 
2421
        # inline this as it will be called a lot
 
2422
        if not self._lock_token:
 
2423
            raise errors.ObjectNotLocked(self)
 
2424
        if self._header_state == DirState.NOT_IN_MEMORY:
 
2425
            self._read_header()
 
2426
 
 
2427
    def _read_prelude(self):
 
2428
        """Read in the prelude header of the dirstate file.
 
2429
 
 
2430
        This only reads in the stuff that is not connected to the crc
 
2431
        checksum. The position will be correct to read in the rest of
 
2432
        the file and check the checksum after this point.
 
2433
        The next entry in the file should be the number of parents,
 
2434
        and their ids. Followed by a newline.
 
2435
        """
 
2436
        header = self._state_file.readline()
 
2437
        if header != DirState.HEADER_FORMAT_3:
 
2438
            raise errors.BzrError(
 
2439
                'invalid header line: %r' % (header,))
 
2440
        crc_line = self._state_file.readline()
 
2441
        if not crc_line.startswith('crc32: '):
 
2442
            raise errors.BzrError('missing crc32 checksum: %r' % crc_line)
 
2443
        self.crc_expected = int(crc_line[len('crc32: '):-1])
 
2444
        num_entries_line = self._state_file.readline()
 
2445
        if not num_entries_line.startswith('num_entries: '):
 
2446
            raise errors.BzrError('missing num_entries line')
 
2447
        self._num_entries = int(num_entries_line[len('num_entries: '):-1])
 
2448
 
 
2449
    def sha1_from_stat(self, path, stat_result):
 
2450
        """Find a sha1 given a stat lookup."""
 
2451
        return self._get_packed_stat_index().get(pack_stat(stat_result), None)
 
2452
 
 
2453
    def _get_packed_stat_index(self):
 
2454
        """Get a packed_stat index of self._dirblocks."""
 
2455
        if self._packed_stat_index is None:
 
2456
            index = {}
 
2457
            for key, tree_details in self._iter_entries():
 
2458
                if tree_details[0][0] == 'f':
 
2459
                    index[tree_details[0][4]] = tree_details[0][1]
 
2460
            self._packed_stat_index = index
 
2461
        return self._packed_stat_index
 
2462
 
 
2463
    def save(self):
 
2464
        """Save any pending changes created during this session.
 
2465
 
 
2466
        We reuse the existing file, because that prevents race conditions with
 
2467
        file creation, and use oslocks on it to prevent concurrent modification
 
2468
        and reads - because dirstate's incremental data aggregation is not
 
2469
        compatible with reading a modified file, and replacing a file in use by
 
2470
        another process is impossible on Windows.
 
2471
 
 
2472
        A dirstate in read only mode should be smart enough though to validate
 
2473
        that the file has not changed, and otherwise discard its cache and
 
2474
        start over, to allow for fine grained read lock duration, so 'status'
 
2475
        wont block 'commit' - for example.
 
2476
        """
 
2477
        if self._changes_aborted:
 
2478
            # Should this be a warning? For now, I'm expecting that places that
 
2479
            # mark it inconsistent will warn, making a warning here redundant.
 
2480
            trace.mutter('Not saving DirState because '
 
2481
                    '_changes_aborted is set.')
 
2482
            return
 
2483
        # TODO: Since we now distinguish IN_MEMORY_MODIFIED from
 
2484
        #       IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail
 
2485
        #       to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we
 
2486
        #       fail to save IN_MEMORY_MODIFIED
 
2487
        if not self._worth_saving():
 
2488
            return
 
2489
 
 
2490
        grabbed_write_lock = False
 
2491
        if self._lock_state != 'w':
 
2492
            grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
 
2493
            # Switch over to the new lock, as the old one may be closed.
 
2494
            # TODO: jam 20070315 We should validate the disk file has
 
2495
            #       not changed contents, since temporary_write_lock may
 
2496
            #       not be an atomic operation.
 
2497
            self._lock_token = new_lock
 
2498
            self._state_file = new_lock.f
 
2499
            if not grabbed_write_lock:
 
2500
                # We couldn't grab a write lock, so we switch back to a read one
 
2501
                return
 
2502
        try:
 
2503
            lines = self.get_lines()
 
2504
            self._state_file.seek(0)
 
2505
            self._state_file.writelines(lines)
 
2506
            self._state_file.truncate()
 
2507
            self._state_file.flush()
 
2508
            self._maybe_fdatasync()
 
2509
            self._mark_unmodified()
 
2510
        finally:
 
2511
            if grabbed_write_lock:
 
2512
                self._lock_token = self._lock_token.restore_read_lock()
 
2513
                self._state_file = self._lock_token.f
 
2514
                # TODO: jam 20070315 We should validate the disk file has
 
2515
                #       not changed contents. Since restore_read_lock may
 
2516
                #       not be an atomic operation.                
 
2517
 
 
2518
    def _maybe_fdatasync(self):
 
2519
        """Flush to disk if possible and if not configured off."""
 
2520
        if self._config_stack.get('dirstate.fdatasync'):
 
2521
            osutils.fdatasync(self._state_file.fileno())
 
2522
 
 
2523
    def _worth_saving(self):
 
2524
        """Is it worth saving the dirstate or not?"""
 
2525
        if (self._header_state == DirState.IN_MEMORY_MODIFIED
 
2526
            or self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
 
2527
            return True
 
2528
        if self._dirblock_state == DirState.IN_MEMORY_HASH_MODIFIED:
 
2529
            if self._worth_saving_limit == -1:
 
2530
                # We never save hash changes when the limit is -1
 
2531
                return False
 
2532
            # If we're using smart saving and only a small number of
 
2533
            # entries have changed their hash, don't bother saving. John has
 
2534
            # suggested using a heuristic here based on the size of the
 
2535
            # changed files and/or tree. For now, we go with a configurable
 
2536
            # number of changes, keeping the calculation time
 
2537
            # as low overhead as possible. (This also keeps all existing
 
2538
            # tests passing as the default is 0, i.e. always save.)
 
2539
            if len(self._known_hash_changes) >= self._worth_saving_limit:
 
2540
                return True
 
2541
        return False
 
2542
 
 
2543
    def _set_data(self, parent_ids, dirblocks):
 
2544
        """Set the full dirstate data in memory.
 
2545
 
 
2546
        This is an internal function used to completely replace the objects
 
2547
        in memory state. It puts the dirstate into state 'full-dirty'.
 
2548
 
 
2549
        :param parent_ids: A list of parent tree revision ids.
 
2550
        :param dirblocks: A list containing one tuple for each directory in the
 
2551
            tree. Each tuple contains the directory path and a list of entries
 
2552
            found in that directory.
 
2553
        """
 
2554
        # our memory copy is now authoritative.
 
2555
        self._dirblocks = dirblocks
 
2556
        self._mark_modified(header_modified=True)
 
2557
        self._parents = list(parent_ids)
 
2558
        self._id_index = None
 
2559
        self._packed_stat_index = None
 
2560
 
 
2561
    def set_path_id(self, path, new_id):
 
2562
        """Change the id of path to new_id in the current working tree.
 
2563
 
 
2564
        :param path: The path inside the tree to set - '' is the root, 'foo'
 
2565
            is the path foo in the root.
 
2566
        :param new_id: The new id to assign to the path. This must be a utf8
 
2567
            file id (not unicode, and not None).
 
2568
        """
 
2569
        self._read_dirblocks_if_needed()
 
2570
        if len(path):
 
2571
            # TODO: logic not written
 
2572
            raise NotImplementedError(self.set_path_id)
 
2573
        # TODO: check new id is unique
 
2574
        entry = self._get_entry(0, path_utf8=path)
 
2575
        if entry[0][2] == new_id:
 
2576
            # Nothing to change.
 
2577
            return
 
2578
        # mark the old path absent, and insert a new root path
 
2579
        self._make_absent(entry)
 
2580
        self.update_minimal(('', '', new_id), 'd',
 
2581
            path_utf8='', packed_stat=entry[1][0][4])
 
2582
        self._mark_modified()
 
2583
 
 
2584
    def set_parent_trees(self, trees, ghosts):
 
2585
        """Set the parent trees for the dirstate.
 
2586
 
 
2587
        :param trees: A list of revision_id, tree tuples. tree must be provided
 
2588
            even if the revision_id refers to a ghost: supply an empty tree in
 
2589
            this case.
 
2590
        :param ghosts: A list of the revision_ids that are ghosts at the time
 
2591
            of setting.
 
2592
        """
 
2593
        # TODO: generate a list of parent indexes to preserve to save
 
2594
        # processing specific parent trees. In the common case one tree will
 
2595
        # be preserved - the left most parent.
 
2596
        # TODO: if the parent tree is a dirstate, we might want to walk them
 
2597
        # all by path in parallel for 'optimal' common-case performance.
 
2598
        # generate new root row.
 
2599
        self._read_dirblocks_if_needed()
 
2600
        # TODO future sketch: Examine the existing parents to generate a change
 
2601
        # map and then walk the new parent trees only, mapping them into the
 
2602
        # dirstate. Walk the dirstate at the same time to remove unreferenced
 
2603
        # entries.
 
2604
        # for now:
 
2605
        # sketch: loop over all entries in the dirstate, cherry picking
 
2606
        # entries from the parent trees, if they are not ghost trees.
 
2607
        # after we finish walking the dirstate, all entries not in the dirstate
 
2608
        # are deletes, so we want to append them to the end as per the design
 
2609
        # discussions. So do a set difference on ids with the parents to
 
2610
        # get deletes, and add them to the end.
 
2611
        # During the update process we need to answer the following questions:
 
2612
        # - find other keys containing a fileid in order to create cross-path
 
2613
        #   links. We dont't trivially use the inventory from other trees
 
2614
        #   because this leads to either double touching, or to accessing
 
2615
        #   missing keys,
 
2616
        # - find other keys containing a path
 
2617
        # We accumulate each entry via this dictionary, including the root
 
2618
        by_path = {}
 
2619
        id_index = {}
 
2620
        # we could do parallel iterators, but because file id data may be
 
2621
        # scattered throughout, we dont save on index overhead: we have to look
 
2622
        # at everything anyway. We can probably save cycles by reusing parent
 
2623
        # data and doing an incremental update when adding an additional
 
2624
        # parent, but for now the common cases are adding a new parent (merge),
 
2625
        # and replacing completely (commit), and commit is more common: so
 
2626
        # optimise merge later.
 
2627
 
 
2628
        # ---- start generation of full tree mapping data
 
2629
        # what trees should we use?
 
2630
        parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
 
2631
        # how many trees do we end up with
 
2632
        parent_count = len(parent_trees)
 
2633
        st = static_tuple.StaticTuple
 
2634
 
 
2635
        # one: the current tree
 
2636
        for entry in self._iter_entries():
 
2637
            # skip entries not in the current tree
 
2638
            if entry[1][0][0] in 'ar': # absent, relocated
 
2639
                continue
 
2640
            by_path[entry[0]] = [entry[1][0]] + \
 
2641
                [DirState.NULL_PARENT_DETAILS] * parent_count
 
2642
            # TODO: Possibly inline this, since we know it isn't present yet
 
2643
            #       id_index[entry[0][2]] = (entry[0],)
 
2644
            self._add_to_id_index(id_index, entry[0])
 
2645
 
 
2646
        # now the parent trees:
 
2647
        for tree_index, tree in enumerate(parent_trees):
 
2648
            # the index is off by one, adjust it.
 
2649
            tree_index = tree_index + 1
 
2650
            # when we add new locations for a fileid we need these ranges for
 
2651
            # any fileid in this tree as we set the by_path[id] to:
 
2652
            # already_processed_tree_details + new_details + new_location_suffix
 
2653
            # the suffix is from tree_index+1:parent_count+1.
 
2654
            new_location_suffix = [DirState.NULL_PARENT_DETAILS] * (parent_count - tree_index)
 
2655
            # now stitch in all the entries from this tree
 
2656
            last_dirname = None
 
2657
            for path, entry in tree.iter_entries_by_dir():
 
2658
                # here we process each trees details for each item in the tree.
 
2659
                # we first update any existing entries for the id at other paths,
 
2660
                # then we either create or update the entry for the id at the
 
2661
                # right path, and finally we add (if needed) a mapping from
 
2662
                # file_id to this path. We do it in this order to allow us to
 
2663
                # avoid checking all known paths for the id when generating a
 
2664
                # new entry at this path: by adding the id->path mapping last,
 
2665
                # all the mappings are valid and have correct relocation
 
2666
                # records where needed.
 
2667
                file_id = entry.file_id
 
2668
                path_utf8 = path.encode('utf8')
 
2669
                dirname, basename = osutils.split(path_utf8)
 
2670
                if dirname == last_dirname:
 
2671
                    # Try to re-use objects as much as possible
 
2672
                    dirname = last_dirname
 
2673
                else:
 
2674
                    last_dirname = dirname
 
2675
                new_entry_key = st(dirname, basename, file_id)
 
2676
                # tree index consistency: All other paths for this id in this tree
 
2677
                # index must point to the correct path.
 
2678
                entry_keys = id_index.get(file_id, ())
 
2679
                for entry_key in entry_keys:
 
2680
                    # TODO:PROFILING: It might be faster to just update
 
2681
                    # rather than checking if we need to, and then overwrite
 
2682
                    # the one we are located at.
 
2683
                    if entry_key != new_entry_key:
 
2684
                        # this file id is at a different path in one of the
 
2685
                        # other trees, so put absent pointers there
 
2686
                        # This is the vertical axis in the matrix, all pointing
 
2687
                        # to the real path.
 
2688
                        by_path[entry_key][tree_index] = st('r', path_utf8, 0,
 
2689
                                                            False, '')
 
2690
                # by path consistency: Insert into an existing path record
 
2691
                # (trivial), or add a new one with relocation pointers for the
 
2692
                # other tree indexes.
 
2693
                if new_entry_key in entry_keys:
 
2694
                    # there is already an entry where this data belongs, just
 
2695
                    # insert it.
 
2696
                    by_path[new_entry_key][tree_index] = \
 
2697
                        self._inv_entry_to_details(entry)
 
2698
                else:
 
2699
                    # add relocated entries to the horizontal axis - this row
 
2700
                    # mapping from path,id. We need to look up the correct path
 
2701
                    # for the indexes from 0 to tree_index -1
 
2702
                    new_details = []
 
2703
                    for lookup_index in range(tree_index):
 
2704
                        # boundary case: this is the first occurence of file_id
 
2705
                        # so there are no id_indexes, possibly take this out of
 
2706
                        # the loop?
 
2707
                        if not len(entry_keys):
 
2708
                            new_details.append(DirState.NULL_PARENT_DETAILS)
 
2709
                        else:
 
2710
                            # grab any one entry, use it to find the right path.
 
2711
                            a_key = next(iter(entry_keys))
 
2712
                            if by_path[a_key][lookup_index][0] in ('r', 'a'):
 
2713
                                # its a pointer or missing statement, use it as
 
2714
                                # is.
 
2715
                                new_details.append(by_path[a_key][lookup_index])
 
2716
                            else:
 
2717
                                # we have the right key, make a pointer to it.
 
2718
                                real_path = ('/'.join(a_key[0:2])).strip('/')
 
2719
                                new_details.append(st('r', real_path, 0, False,
 
2720
                                                      ''))
 
2721
                    new_details.append(self._inv_entry_to_details(entry))
 
2722
                    new_details.extend(new_location_suffix)
 
2723
                    by_path[new_entry_key] = new_details
 
2724
                    self._add_to_id_index(id_index, new_entry_key)
 
2725
        # --- end generation of full tree mappings
 
2726
 
 
2727
        # sort and output all the entries
 
2728
        new_entries = self._sort_entries(viewitems(by_path))
 
2729
        self._entries_to_current_state(new_entries)
 
2730
        self._parents = [rev_id for rev_id, tree in trees]
 
2731
        self._ghosts = list(ghosts)
 
2732
        self._mark_modified(header_modified=True)
 
2733
        self._id_index = id_index
 
2734
 
 
2735
    def _sort_entries(self, entry_list):
 
2736
        """Given a list of entries, sort them into the right order.
 
2737
 
 
2738
        This is done when constructing a new dirstate from trees - normally we
 
2739
        try to keep everything in sorted blocks all the time, but sometimes
 
2740
        it's easier to sort after the fact.
 
2741
        """
 
2742
        # When sorting, we usually have 10x more entries than directories. (69k
 
2743
        # total entries, 4k directories). So cache the results of splitting.
 
2744
        # Saving time and objects. Also, use StaticTuple to avoid putting all
 
2745
        # of these object into python's garbage collector.
 
2746
        split_dirs = {}
 
2747
        def _key(entry, _split_dirs=split_dirs, _st=static_tuple.StaticTuple):
 
2748
            # sort by: directory parts, file name, file id
 
2749
            dirpath, fname, file_id = entry[0]
 
2750
            try:
 
2751
                split = _split_dirs[dirpath]
 
2752
            except KeyError:
 
2753
                split = _st.from_sequence(dirpath.split('/'))
 
2754
                _split_dirs[dirpath] = split
 
2755
            return _st(split, fname, file_id)
 
2756
        return sorted(entry_list, key=_key)
 
2757
 
 
2758
    def set_state_from_inventory(self, new_inv):
 
2759
        """Set new_inv as the current state.
 
2760
 
 
2761
        This API is called by tree transform, and will usually occur with
 
2762
        existing parent trees.
 
2763
 
 
2764
        :param new_inv: The inventory object to set current state from.
 
2765
        """
 
2766
        if 'evil' in debug.debug_flags:
 
2767
            trace.mutter_callsite(1,
 
2768
                "set_state_from_inventory called; please mutate the tree instead")
 
2769
        tracing = 'dirstate' in debug.debug_flags
 
2770
        if tracing:
 
2771
            trace.mutter("set_state_from_inventory trace:")
 
2772
        self._read_dirblocks_if_needed()
 
2773
        # sketch:
 
2774
        # Two iterators: current data and new data, both in dirblock order.
 
2775
        # We zip them together, which tells about entries that are new in the
 
2776
        # inventory, or removed in the inventory, or present in both and
 
2777
        # possibly changed.
 
2778
        #
 
2779
        # You might think we could just synthesize a new dirstate directly
 
2780
        # since we're processing it in the right order.  However, we need to
 
2781
        # also consider there may be any number of parent trees and relocation
 
2782
        # pointers, and we don't want to duplicate that here.
 
2783
        new_iterator = new_inv.iter_entries_by_dir()
 
2784
        # we will be modifying the dirstate, so we need a stable iterator. In
 
2785
        # future we might write one, for now we just clone the state into a
 
2786
        # list using a copy so that we see every original item and don't have
 
2787
        # to adjust the position when items are inserted or deleted in the
 
2788
        # underlying dirstate.
 
2789
        old_iterator = iter(list(self._iter_entries()))
 
2790
        # both must have roots so this is safe:
 
2791
        current_new = next(new_iterator)
 
2792
        current_old = next(old_iterator)
 
2793
        def advance(iterator):
 
2794
            try:
 
2795
                return next(iterator)
 
2796
            except StopIteration:
 
2797
                return None
 
2798
        while current_new or current_old:
 
2799
            # skip entries in old that are not really there
 
2800
            if current_old and current_old[1][0][0] in 'ar':
 
2801
                # relocated or absent
 
2802
                current_old = advance(old_iterator)
 
2803
                continue
 
2804
            if current_new:
 
2805
                # convert new into dirblock style
 
2806
                new_path_utf8 = current_new[0].encode('utf8')
 
2807
                new_dirname, new_basename = osutils.split(new_path_utf8)
 
2808
                new_id = current_new[1].file_id
 
2809
                new_entry_key = (new_dirname, new_basename, new_id)
 
2810
                current_new_minikind = \
 
2811
                    DirState._kind_to_minikind[current_new[1].kind]
 
2812
                if current_new_minikind == 't':
 
2813
                    fingerprint = current_new[1].reference_revision or ''
 
2814
                else:
 
2815
                    # We normally only insert or remove records, or update
 
2816
                    # them when it has significantly changed.  Then we want to
 
2817
                    # erase its fingerprint.  Unaffected records should
 
2818
                    # normally not be updated at all.
 
2819
                    fingerprint = ''
 
2820
            else:
 
2821
                # for safety disable variables
 
2822
                new_path_utf8 = new_dirname = new_basename = new_id = \
 
2823
                    new_entry_key = None
 
2824
            # 5 cases, we dont have a value that is strictly greater than everything, so
 
2825
            # we make both end conditions explicit
 
2826
            if not current_old:
 
2827
                # old is finished: insert current_new into the state.
 
2828
                if tracing:
 
2829
                    trace.mutter("Appending from new '%s'.",
 
2830
                        new_path_utf8.decode('utf8'))
 
2831
                self.update_minimal(new_entry_key, current_new_minikind,
 
2832
                    executable=current_new[1].executable,
 
2833
                    path_utf8=new_path_utf8, fingerprint=fingerprint,
 
2834
                    fullscan=True)
 
2835
                current_new = advance(new_iterator)
 
2836
            elif not current_new:
 
2837
                # new is finished
 
2838
                if tracing:
 
2839
                    trace.mutter("Truncating from old '%s/%s'.",
 
2840
                        current_old[0][0].decode('utf8'),
 
2841
                        current_old[0][1].decode('utf8'))
 
2842
                self._make_absent(current_old)
 
2843
                current_old = advance(old_iterator)
 
2844
            elif new_entry_key == current_old[0]:
 
2845
                # same -  common case
 
2846
                # We're looking at the same path and id in both the dirstate
 
2847
                # and inventory, so just need to update the fields in the
 
2848
                # dirstate from the one in the inventory.
 
2849
                # TODO: update the record if anything significant has changed.
 
2850
                # the minimal required trigger is if the execute bit or cached
 
2851
                # kind has changed.
 
2852
                if (current_old[1][0][3] != current_new[1].executable or
 
2853
                    current_old[1][0][0] != current_new_minikind):
 
2854
                    if tracing:
 
2855
                        trace.mutter("Updating in-place change '%s'.",
 
2856
                            new_path_utf8.decode('utf8'))
 
2857
                    self.update_minimal(current_old[0], current_new_minikind,
 
2858
                        executable=current_new[1].executable,
 
2859
                        path_utf8=new_path_utf8, fingerprint=fingerprint,
 
2860
                        fullscan=True)
 
2861
                # both sides are dealt with, move on
 
2862
                current_old = advance(old_iterator)
 
2863
                current_new = advance(new_iterator)
 
2864
            elif (lt_by_dirs(new_dirname, current_old[0][0])
 
2865
                  or (new_dirname == current_old[0][0]
 
2866
                      and new_entry_key[1:] < current_old[0][1:])):
 
2867
                # new comes before:
 
2868
                # add a entry for this and advance new
 
2869
                if tracing:
 
2870
                    trace.mutter("Inserting from new '%s'.",
 
2871
                        new_path_utf8.decode('utf8'))
 
2872
                self.update_minimal(new_entry_key, current_new_minikind,
 
2873
                    executable=current_new[1].executable,
 
2874
                    path_utf8=new_path_utf8, fingerprint=fingerprint,
 
2875
                    fullscan=True)
 
2876
                current_new = advance(new_iterator)
 
2877
            else:
 
2878
                # we've advanced past the place where the old key would be,
 
2879
                # without seeing it in the new list.  so it must be gone.
 
2880
                if tracing:
 
2881
                    trace.mutter("Deleting from old '%s/%s'.",
 
2882
                        current_old[0][0].decode('utf8'),
 
2883
                        current_old[0][1].decode('utf8'))
 
2884
                self._make_absent(current_old)
 
2885
                current_old = advance(old_iterator)
 
2886
        self._mark_modified()
 
2887
        self._id_index = None
 
2888
        self._packed_stat_index = None
 
2889
        if tracing:
 
2890
            trace.mutter("set_state_from_inventory complete.")
 
2891
 
 
2892
    def set_state_from_scratch(self, working_inv, parent_trees, parent_ghosts):
 
2893
        """Wipe the currently stored state and set it to something new.
 
2894
 
 
2895
        This is a hard-reset for the data we are working with.
 
2896
        """
 
2897
        # Technically, we really want a write lock, but until we write, we
 
2898
        # don't really need it.
 
2899
        self._requires_lock()
 
2900
        # root dir and root dir contents with no children. We have to have a
 
2901
        # root for set_state_from_inventory to work correctly.
 
2902
        empty_root = (('', '', inventory.ROOT_ID),
 
2903
                      [('d', '', 0, False, DirState.NULLSTAT)])
 
2904
        empty_tree_dirblocks = [('', [empty_root]), ('', [])]
 
2905
        self._set_data([], empty_tree_dirblocks)
 
2906
        self.set_state_from_inventory(working_inv)
 
2907
        self.set_parent_trees(parent_trees, parent_ghosts)
 
2908
 
 
2909
    def _make_absent(self, current_old):
 
2910
        """Mark current_old - an entry - as absent for tree 0.
 
2911
 
 
2912
        :return: True if this was the last details entry for the entry key:
 
2913
            that is, if the underlying block has had the entry removed, thus
 
2914
            shrinking in length.
 
2915
        """
 
2916
        # build up paths that this id will be left at after the change is made,
 
2917
        # so we can update their cross references in tree 0
 
2918
        all_remaining_keys = set()
 
2919
        # Dont check the working tree, because it's going.
 
2920
        for details in current_old[1][1:]:
 
2921
            if details[0] not in 'ar': # absent, relocated
 
2922
                all_remaining_keys.add(current_old[0])
 
2923
            elif details[0] == 'r': # relocated
 
2924
                # record the key for the real path.
 
2925
                all_remaining_keys.add(tuple(osutils.split(details[1])) + (current_old[0][2],))
 
2926
            # absent rows are not present at any path.
 
2927
        last_reference = current_old[0] not in all_remaining_keys
 
2928
        if last_reference:
 
2929
            # the current row consists entire of the current item (being marked
 
2930
            # absent), and relocated or absent entries for the other trees:
 
2931
            # Remove it, its meaningless.
 
2932
            block = self._find_block(current_old[0])
 
2933
            entry_index, present = self._find_entry_index(current_old[0], block[1])
 
2934
            if not present:
 
2935
                raise AssertionError('could not find entry for %s' % (current_old,))
 
2936
            block[1].pop(entry_index)
 
2937
            # if we have an id_index in use, remove this key from it for this id.
 
2938
            if self._id_index is not None:
 
2939
                self._remove_from_id_index(self._id_index, current_old[0])
 
2940
        # update all remaining keys for this id to record it as absent. The
 
2941
        # existing details may either be the record we are marking as deleted
 
2942
        # (if there were other trees with the id present at this path), or may
 
2943
        # be relocations.
 
2944
        for update_key in all_remaining_keys:
 
2945
            update_block_index, present = \
 
2946
                self._find_block_index_from_key(update_key)
 
2947
            if not present:
 
2948
                raise AssertionError('could not find block for %s' % (update_key,))
 
2949
            update_entry_index, present = \
 
2950
                self._find_entry_index(update_key, self._dirblocks[update_block_index][1])
 
2951
            if not present:
 
2952
                raise AssertionError('could not find entry for %s' % (update_key,))
 
2953
            update_tree_details = self._dirblocks[update_block_index][1][update_entry_index][1]
 
2954
            # it must not be absent at the moment
 
2955
            if update_tree_details[0][0] == 'a': # absent
 
2956
                raise AssertionError('bad row %r' % (update_tree_details,))
 
2957
            update_tree_details[0] = DirState.NULL_PARENT_DETAILS
 
2958
        self._mark_modified()
 
2959
        return last_reference
 
2960
 
 
2961
    def update_minimal(self, key, minikind, executable=False, fingerprint='',
 
2962
        packed_stat=None, size=0, path_utf8=None, fullscan=False):
 
2963
        """Update an entry to the state in tree 0.
 
2964
 
 
2965
        This will either create a new entry at 'key' or update an existing one.
 
2966
        It also makes sure that any other records which might mention this are
 
2967
        updated as well.
 
2968
 
 
2969
        :param key: (dir, name, file_id) for the new entry
 
2970
        :param minikind: The type for the entry ('f' == 'file', 'd' ==
 
2971
                'directory'), etc.
 
2972
        :param executable: Should the executable bit be set?
 
2973
        :param fingerprint: Simple fingerprint for new entry: canonical-form
 
2974
            sha1 for files, referenced revision id for subtrees, etc.
 
2975
        :param packed_stat: Packed stat value for new entry.
 
2976
        :param size: Size information for new entry
 
2977
        :param path_utf8: key[0] + '/' + key[1], just passed in to avoid doing
 
2978
                extra computation.
 
2979
        :param fullscan: If True then a complete scan of the dirstate is being
 
2980
            done and checking for duplicate rows should not be done. This
 
2981
            should only be set by set_state_from_inventory and similar methods.
 
2982
 
 
2983
        If packed_stat and fingerprint are not given, they're invalidated in
 
2984
        the entry.
 
2985
        """
 
2986
        block = self._find_block(key)[1]
 
2987
        if packed_stat is None:
 
2988
            packed_stat = DirState.NULLSTAT
 
2989
        # XXX: Some callers pass '' as the packed_stat, and it seems to be
 
2990
        # sometimes present in the dirstate - this seems oddly inconsistent.
 
2991
        # mbp 20071008
 
2992
        entry_index, present = self._find_entry_index(key, block)
 
2993
        new_details = (minikind, fingerprint, size, executable, packed_stat)
 
2994
        id_index = self._get_id_index()
 
2995
        if not present:
 
2996
            # New record. Check there isn't a entry at this path already.
 
2997
            if not fullscan:
 
2998
                low_index, _ = self._find_entry_index(key[0:2] + ('',), block)
 
2999
                while low_index < len(block):
 
3000
                    entry = block[low_index]
 
3001
                    if entry[0][0:2] == key[0:2]:
 
3002
                        if entry[1][0][0] not in 'ar':
 
3003
                            # This entry has the same path (but a different id) as
 
3004
                            # the new entry we're adding, and is present in ths
 
3005
                            # tree.
 
3006
                            self._raise_invalid(
 
3007
                                ("%s/%s" % key[0:2]).decode('utf8'), key[2],
 
3008
                                "Attempt to add item at path already occupied by "
 
3009
                                "id %r" % entry[0][2])
 
3010
                        low_index += 1
 
3011
                    else:
 
3012
                        break
 
3013
            # new entry, synthesis cross reference here,
 
3014
            existing_keys = id_index.get(key[2], ())
 
3015
            if not existing_keys:
 
3016
                # not currently in the state, simplest case
 
3017
                new_entry = key, [new_details] + self._empty_parent_info()
 
3018
            else:
 
3019
                # present at one or more existing other paths.
 
3020
                # grab one of them and use it to generate parent
 
3021
                # relocation/absent entries.
 
3022
                new_entry = key, [new_details]
 
3023
                # existing_keys can be changed as we iterate.
 
3024
                for other_key in tuple(existing_keys):
 
3025
                    # change the record at other to be a pointer to this new
 
3026
                    # record. The loop looks similar to the change to
 
3027
                    # relocations when updating an existing record but its not:
 
3028
                    # the test for existing kinds is different: this can be
 
3029
                    # factored out to a helper though.
 
3030
                    other_block_index, present = self._find_block_index_from_key(
 
3031
                        other_key)
 
3032
                    if not present:
 
3033
                        raise AssertionError('could not find block for %s' % (
 
3034
                            other_key,))
 
3035
                    other_block = self._dirblocks[other_block_index][1]
 
3036
                    other_entry_index, present = self._find_entry_index(
 
3037
                        other_key, other_block)
 
3038
                    if not present:
 
3039
                        raise AssertionError(
 
3040
                            'update_minimal: could not find other entry for %s'
 
3041
                            % (other_key,))
 
3042
                    if path_utf8 is None:
 
3043
                        raise AssertionError('no path')
 
3044
                    # Turn this other location into a reference to the new
 
3045
                    # location. This also updates the aliased iterator
 
3046
                    # (current_old in set_state_from_inventory) so that the old
 
3047
                    # entry, if not already examined, is skipped over by that
 
3048
                    # loop.
 
3049
                    other_entry = other_block[other_entry_index]
 
3050
                    other_entry[1][0] = ('r', path_utf8, 0, False, '')
 
3051
                    if self._maybe_remove_row(other_block, other_entry_index,
 
3052
                                              id_index):
 
3053
                        # If the row holding this was removed, we need to
 
3054
                        # recompute where this entry goes
 
3055
                        entry_index, _ = self._find_entry_index(key, block)
 
3056
 
 
3057
                # This loop:
 
3058
                # adds a tuple to the new details for each column
 
3059
                #  - either by copying an existing relocation pointer inside that column
 
3060
                #  - or by creating a new pointer to the right row inside that column
 
3061
                num_present_parents = self._num_present_parents()
 
3062
                if num_present_parents:
 
3063
                    # TODO: This re-evaluates the existing_keys set, do we need
 
3064
                    #       to do that ourselves?
 
3065
                    other_key = list(existing_keys)[0]
 
3066
                for lookup_index in range(1, num_present_parents + 1):
 
3067
                    # grab any one entry, use it to find the right path.
 
3068
                    # TODO: optimise this to reduce memory use in highly
 
3069
                    # fragmented situations by reusing the relocation
 
3070
                    # records.
 
3071
                    update_block_index, present = \
 
3072
                        self._find_block_index_from_key(other_key)
 
3073
                    if not present:
 
3074
                        raise AssertionError('could not find block for %s' % (other_key,))
 
3075
                    update_entry_index, present = \
 
3076
                        self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
 
3077
                    if not present:
 
3078
                        raise AssertionError('update_minimal: could not find entry for %s' % (other_key,))
 
3079
                    update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
 
3080
                    if update_details[0] in 'ar': # relocated, absent
 
3081
                        # its a pointer or absent in lookup_index's tree, use
 
3082
                        # it as is.
 
3083
                        new_entry[1].append(update_details)
 
3084
                    else:
 
3085
                        # we have the right key, make a pointer to it.
 
3086
                        pointer_path = osutils.pathjoin(*other_key[0:2])
 
3087
                        new_entry[1].append(('r', pointer_path, 0, False, ''))
 
3088
            block.insert(entry_index, new_entry)
 
3089
            self._add_to_id_index(id_index, key)
 
3090
        else:
 
3091
            # Does the new state matter?
 
3092
            block[entry_index][1][0] = new_details
 
3093
            # parents cannot be affected by what we do.
 
3094
            # other occurences of this id can be found
 
3095
            # from the id index.
 
3096
            # ---
 
3097
            # tree index consistency: All other paths for this id in this tree
 
3098
            # index must point to the correct path. We have to loop here because
 
3099
            # we may have passed entries in the state with this file id already
 
3100
            # that were absent - where parent entries are - and they need to be
 
3101
            # converted to relocated.
 
3102
            if path_utf8 is None:
 
3103
                raise AssertionError('no path')
 
3104
            existing_keys = id_index.get(key[2], ())
 
3105
            if key not in existing_keys:
 
3106
                raise AssertionError('We found the entry in the blocks, but'
 
3107
                    ' the key is not in the id_index.'
 
3108
                    ' key: %s, existing_keys: %s' % (key, existing_keys))
 
3109
            for entry_key in existing_keys:
 
3110
                # TODO:PROFILING: It might be faster to just update
 
3111
                # rather than checking if we need to, and then overwrite
 
3112
                # the one we are located at.
 
3113
                if entry_key != key:
 
3114
                    # this file id is at a different path in one of the
 
3115
                    # other trees, so put absent pointers there
 
3116
                    # This is the vertical axis in the matrix, all pointing
 
3117
                    # to the real path.
 
3118
                    block_index, present = self._find_block_index_from_key(entry_key)
 
3119
                    if not present:
 
3120
                        raise AssertionError('not present: %r', entry_key)
 
3121
                    entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
 
3122
                    if not present:
 
3123
                        raise AssertionError('not present: %r', entry_key)
 
3124
                    self._dirblocks[block_index][1][entry_index][1][0] = \
 
3125
                        ('r', path_utf8, 0, False, '')
 
3126
        # add a containing dirblock if needed.
 
3127
        if new_details[0] == 'd':
 
3128
            subdir_key = (osutils.pathjoin(*key[0:2]), '', '')
 
3129
            block_index, present = self._find_block_index_from_key(subdir_key)
 
3130
            if not present:
 
3131
                self._dirblocks.insert(block_index, (subdir_key[0], []))
 
3132
 
 
3133
        self._mark_modified()
 
3134
 
 
3135
    def _maybe_remove_row(self, block, index, id_index):
 
3136
        """Remove index if it is absent or relocated across the row.
 
3137
        
 
3138
        id_index is updated accordingly.
 
3139
        :return: True if we removed the row, False otherwise
 
3140
        """
 
3141
        present_in_row = False
 
3142
        entry = block[index]
 
3143
        for column in entry[1]:
 
3144
            if column[0] not in 'ar':
 
3145
                present_in_row = True
 
3146
                break
 
3147
        if not present_in_row:
 
3148
            block.pop(index)
 
3149
            self._remove_from_id_index(id_index, entry[0])
 
3150
            return True
 
3151
        return False
 
3152
 
 
3153
    def _validate(self):
 
3154
        """Check that invariants on the dirblock are correct.
 
3155
 
 
3156
        This can be useful in debugging; it shouldn't be necessary in
 
3157
        normal code.
 
3158
 
 
3159
        This must be called with a lock held.
 
3160
        """
 
3161
        # NOTE: This must always raise AssertionError not just assert,
 
3162
        # otherwise it may not behave properly under python -O
 
3163
        #
 
3164
        # TODO: All entries must have some content that's not 'a' or 'r',
 
3165
        # otherwise it could just be removed.
 
3166
        #
 
3167
        # TODO: All relocations must point directly to a real entry.
 
3168
        #
 
3169
        # TODO: No repeated keys.
 
3170
        #
 
3171
        # -- mbp 20070325
 
3172
        from pprint import pformat
 
3173
        self._read_dirblocks_if_needed()
 
3174
        if len(self._dirblocks) > 0:
 
3175
            if not self._dirblocks[0][0] == '':
 
3176
                raise AssertionError(
 
3177
                    "dirblocks don't start with root block:\n" + \
 
3178
                    pformat(self._dirblocks))
 
3179
        if len(self._dirblocks) > 1:
 
3180
            if not self._dirblocks[1][0] == '':
 
3181
                raise AssertionError(
 
3182
                    "dirblocks missing root directory:\n" + \
 
3183
                    pformat(self._dirblocks))
 
3184
        # the dirblocks are sorted by their path components, name, and dir id
 
3185
        dir_names = [d[0].split('/')
 
3186
                for d in self._dirblocks[1:]]
 
3187
        if dir_names != sorted(dir_names):
 
3188
            raise AssertionError(
 
3189
                "dir names are not in sorted order:\n" + \
 
3190
                pformat(self._dirblocks) + \
 
3191
                "\nkeys:\n" +
 
3192
                pformat(dir_names))
 
3193
        for dirblock in self._dirblocks:
 
3194
            # within each dirblock, the entries are sorted by filename and
 
3195
            # then by id.
 
3196
            for entry in dirblock[1]:
 
3197
                if dirblock[0] != entry[0][0]:
 
3198
                    raise AssertionError(
 
3199
                        "entry key for %r"
 
3200
                        "doesn't match directory name in\n%r" %
 
3201
                        (entry, pformat(dirblock)))
 
3202
            if dirblock[1] != sorted(dirblock[1]):
 
3203
                raise AssertionError(
 
3204
                    "dirblock for %r is not sorted:\n%s" % \
 
3205
                    (dirblock[0], pformat(dirblock)))
 
3206
 
 
3207
        def check_valid_parent():
 
3208
            """Check that the current entry has a valid parent.
 
3209
 
 
3210
            This makes sure that the parent has a record,
 
3211
            and that the parent isn't marked as "absent" in the
 
3212
            current tree. (It is invalid to have a non-absent file in an absent
 
3213
            directory.)
 
3214
            """
 
3215
            if entry[0][0:2] == ('', ''):
 
3216
                # There should be no parent for the root row
 
3217
                return
 
3218
            parent_entry = self._get_entry(tree_index, path_utf8=entry[0][0])
 
3219
            if parent_entry == (None, None):
 
3220
                raise AssertionError(
 
3221
                    "no parent entry for: %s in tree %s"
 
3222
                    % (this_path, tree_index))
 
3223
            if parent_entry[1][tree_index][0] != 'd':
 
3224
                raise AssertionError(
 
3225
                    "Parent entry for %s is not marked as a valid"
 
3226
                    " directory. %s" % (this_path, parent_entry,))
 
3227
 
 
3228
        # For each file id, for each tree: either
 
3229
        # the file id is not present at all; all rows with that id in the
 
3230
        # key have it marked as 'absent'
 
3231
        # OR the file id is present under exactly one name; any other entries
 
3232
        # that mention that id point to the correct name.
 
3233
        #
 
3234
        # We check this with a dict per tree pointing either to the present
 
3235
        # name, or None if absent.
 
3236
        tree_count = self._num_present_parents() + 1
 
3237
        id_path_maps = [{} for _ in range(tree_count)]
 
3238
        # Make sure that all renamed entries point to the correct location.
 
3239
        for entry in self._iter_entries():
 
3240
            file_id = entry[0][2]
 
3241
            this_path = osutils.pathjoin(entry[0][0], entry[0][1])
 
3242
            if len(entry[1]) != tree_count:
 
3243
                raise AssertionError(
 
3244
                "wrong number of entry details for row\n%s" \
 
3245
                ",\nexpected %d" % \
 
3246
                (pformat(entry), tree_count))
 
3247
            absent_positions = 0
 
3248
            for tree_index, tree_state in enumerate(entry[1]):
 
3249
                this_tree_map = id_path_maps[tree_index]
 
3250
                minikind = tree_state[0]
 
3251
                if minikind in 'ar':
 
3252
                    absent_positions += 1
 
3253
                # have we seen this id before in this column?
 
3254
                if file_id in this_tree_map:
 
3255
                    previous_path, previous_loc = this_tree_map[file_id]
 
3256
                    # any later mention of this file must be consistent with
 
3257
                    # what was said before
 
3258
                    if minikind == 'a':
 
3259
                        if previous_path is not None:
 
3260
                            raise AssertionError(
 
3261
                            "file %s is absent in row %r but also present " \
 
3262
                            "at %r"% \
 
3263
                            (file_id, entry, previous_path))
 
3264
                    elif minikind == 'r':
 
3265
                        target_location = tree_state[1]
 
3266
                        if previous_path != target_location:
 
3267
                            raise AssertionError(
 
3268
                            "file %s relocation in row %r but also at %r" \
 
3269
                            % (file_id, entry, previous_path))
 
3270
                    else:
 
3271
                        # a file, directory, etc - may have been previously
 
3272
                        # pointed to by a relocation, which must point here
 
3273
                        if previous_path != this_path:
 
3274
                            raise AssertionError(
 
3275
                                "entry %r inconsistent with previous path %r "
 
3276
                                "seen at %r" %
 
3277
                                (entry, previous_path, previous_loc))
 
3278
                        check_valid_parent()
 
3279
                else:
 
3280
                    if minikind == 'a':
 
3281
                        # absent; should not occur anywhere else
 
3282
                        this_tree_map[file_id] = None, this_path
 
3283
                    elif minikind == 'r':
 
3284
                        # relocation, must occur at expected location
 
3285
                        this_tree_map[file_id] = tree_state[1], this_path
 
3286
                    else:
 
3287
                        this_tree_map[file_id] = this_path, this_path
 
3288
                        check_valid_parent()
 
3289
            if absent_positions == tree_count:
 
3290
                raise AssertionError(
 
3291
                    "entry %r has no data for any tree." % (entry,))
 
3292
        if self._id_index is not None:
 
3293
            for file_id, entry_keys in viewitems(self._id_index):
 
3294
                for entry_key in entry_keys:
 
3295
                    # Check that the entry in the map is pointing to the same
 
3296
                    # file_id
 
3297
                    if entry_key[2] != file_id:
 
3298
                        raise AssertionError(
 
3299
                            'file_id %r did not match entry key %s'
 
3300
                            % (file_id, entry_key))
 
3301
                    # And that from this entry key, we can look up the original
 
3302
                    # record
 
3303
                    block_index, present = self._find_block_index_from_key(entry_key)
 
3304
                    if not present:
 
3305
                        raise AssertionError('missing block for entry key: %r', entry_key)
 
3306
                    entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
 
3307
                    if not present:
 
3308
                        raise AssertionError('missing entry for key: %r', entry_key)
 
3309
                if len(entry_keys) != len(set(entry_keys)):
 
3310
                    raise AssertionError(
 
3311
                        'id_index contained non-unique data for %s'
 
3312
                        % (entry_keys,))
 
3313
 
 
3314
    def _wipe_state(self):
 
3315
        """Forget all state information about the dirstate."""
 
3316
        self._header_state = DirState.NOT_IN_MEMORY
 
3317
        self._dirblock_state = DirState.NOT_IN_MEMORY
 
3318
        self._changes_aborted = False
 
3319
        self._parents = []
 
3320
        self._ghosts = []
 
3321
        self._dirblocks = []
 
3322
        self._id_index = None
 
3323
        self._packed_stat_index = None
 
3324
        self._end_of_header = None
 
3325
        self._cutoff_time = None
 
3326
        self._split_path_cache = {}
 
3327
 
 
3328
    def lock_read(self):
 
3329
        """Acquire a read lock on the dirstate."""
 
3330
        if self._lock_token is not None:
 
3331
            raise errors.LockContention(self._lock_token)
 
3332
        # TODO: jam 20070301 Rather than wiping completely, if the blocks are
 
3333
        #       already in memory, we could read just the header and check for
 
3334
        #       any modification. If not modified, we can just leave things
 
3335
        #       alone
 
3336
        self._lock_token = lock.ReadLock(self._filename)
 
3337
        self._lock_state = 'r'
 
3338
        self._state_file = self._lock_token.f
 
3339
        self._wipe_state()
 
3340
 
 
3341
    def lock_write(self):
 
3342
        """Acquire a write lock on the dirstate."""
 
3343
        if self._lock_token is not None:
 
3344
            raise errors.LockContention(self._lock_token)
 
3345
        # TODO: jam 20070301 Rather than wiping completely, if the blocks are
 
3346
        #       already in memory, we could read just the header and check for
 
3347
        #       any modification. If not modified, we can just leave things
 
3348
        #       alone
 
3349
        self._lock_token = lock.WriteLock(self._filename)
 
3350
        self._lock_state = 'w'
 
3351
        self._state_file = self._lock_token.f
 
3352
        self._wipe_state()
 
3353
 
 
3354
    def unlock(self):
 
3355
        """Drop any locks held on the dirstate."""
 
3356
        if self._lock_token is None:
 
3357
            raise errors.LockNotHeld(self)
 
3358
        # TODO: jam 20070301 Rather than wiping completely, if the blocks are
 
3359
        #       already in memory, we could read just the header and check for
 
3360
        #       any modification. If not modified, we can just leave things
 
3361
        #       alone
 
3362
        self._state_file = None
 
3363
        self._lock_state = None
 
3364
        self._lock_token.unlock()
 
3365
        self._lock_token = None
 
3366
        self._split_path_cache = {}
 
3367
 
 
3368
    def _requires_lock(self):
 
3369
        """Check that a lock is currently held by someone on the dirstate."""
 
3370
        if not self._lock_token:
 
3371
            raise errors.ObjectNotLocked(self)
 
3372
 
 
3373
 
 
3374
def py_update_entry(state, entry, abspath, stat_value,
 
3375
                 _stat_to_minikind=DirState._stat_to_minikind):
 
3376
    """Update the entry based on what is actually on disk.
 
3377
 
 
3378
    This function only calculates the sha if it needs to - if the entry is
 
3379
    uncachable, or clearly different to the first parent's entry, no sha
 
3380
    is calculated, and None is returned.
 
3381
 
 
3382
    :param state: The dirstate this entry is in.
 
3383
    :param entry: This is the dirblock entry for the file in question.
 
3384
    :param abspath: The path on disk for this file.
 
3385
    :param stat_value: The stat value done on the path.
 
3386
    :return: None, or The sha1 hexdigest of the file (40 bytes) or link
 
3387
        target of a symlink.
 
3388
    """
 
3389
    try:
 
3390
        minikind = _stat_to_minikind[stat_value.st_mode & 0o170000]
 
3391
    except KeyError:
 
3392
        # Unhandled kind
 
3393
        return None
 
3394
    packed_stat = pack_stat(stat_value)
 
3395
    (saved_minikind, saved_link_or_sha1, saved_file_size,
 
3396
     saved_executable, saved_packed_stat) = entry[1][0]
 
3397
 
 
3398
    if minikind == 'd' and saved_minikind == 't':
 
3399
        minikind = 't'
 
3400
    if (minikind == saved_minikind
 
3401
        and packed_stat == saved_packed_stat):
 
3402
        # The stat hasn't changed since we saved, so we can re-use the
 
3403
        # saved sha hash.
 
3404
        if minikind == 'd':
 
3405
            return None
 
3406
 
 
3407
        # size should also be in packed_stat
 
3408
        if saved_file_size == stat_value.st_size:
 
3409
            return saved_link_or_sha1
 
3410
 
 
3411
    # If we have gotten this far, that means that we need to actually
 
3412
    # process this entry.
 
3413
    link_or_sha1 = None
 
3414
    worth_saving = True
 
3415
    if minikind == 'f':
 
3416
        executable = state._is_executable(stat_value.st_mode,
 
3417
                                         saved_executable)
 
3418
        if state._cutoff_time is None:
 
3419
            state._sha_cutoff_time()
 
3420
        if (stat_value.st_mtime < state._cutoff_time
 
3421
            and stat_value.st_ctime < state._cutoff_time
 
3422
            and len(entry[1]) > 1
 
3423
            and entry[1][1][0] != 'a'):
 
3424
            # Could check for size changes for further optimised
 
3425
            # avoidance of sha1's. However the most prominent case of
 
3426
            # over-shaing is during initial add, which this catches.
 
3427
            # Besides, if content filtering happens, size and sha
 
3428
            # are calculated at the same time, so checking just the size
 
3429
            # gains nothing w.r.t. performance.
 
3430
            link_or_sha1 = state._sha1_file(abspath)
 
3431
            entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
 
3432
                           executable, packed_stat)
 
3433
        else:
 
3434
            entry[1][0] = ('f', '', stat_value.st_size,
 
3435
                           executable, DirState.NULLSTAT)
 
3436
            worth_saving = False
 
3437
    elif minikind == 'd':
 
3438
        link_or_sha1 = None
 
3439
        entry[1][0] = ('d', '', 0, False, packed_stat)
 
3440
        if saved_minikind != 'd':
 
3441
            # This changed from something into a directory. Make sure we
 
3442
            # have a directory block for it. This doesn't happen very
 
3443
            # often, so this doesn't have to be super fast.
 
3444
            block_index, entry_index, dir_present, file_present = \
 
3445
                state._get_block_entry_index(entry[0][0], entry[0][1], 0)
 
3446
            state._ensure_block(block_index, entry_index,
 
3447
                               osutils.pathjoin(entry[0][0], entry[0][1]))
 
3448
        else:
 
3449
            worth_saving = False
 
3450
    elif minikind == 'l':
 
3451
        if saved_minikind == 'l':
 
3452
            worth_saving = False
 
3453
        link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
 
3454
        if state._cutoff_time is None:
 
3455
            state._sha_cutoff_time()
 
3456
        if (stat_value.st_mtime < state._cutoff_time
 
3457
            and stat_value.st_ctime < state._cutoff_time):
 
3458
            entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
 
3459
                           False, packed_stat)
 
3460
        else:
 
3461
            entry[1][0] = ('l', '', stat_value.st_size,
 
3462
                           False, DirState.NULLSTAT)
 
3463
    if worth_saving:
 
3464
        state._mark_modified([entry])
 
3465
    return link_or_sha1
 
3466
 
 
3467
 
 
3468
class ProcessEntryPython(object):
 
3469
 
 
3470
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
 
3471
        "last_source_parent", "last_target_parent", "include_unchanged",
 
3472
        "partial", "use_filesystem_for_exec", "utf8_decode",
 
3473
        "searched_specific_files", "search_specific_files",
 
3474
        "searched_exact_paths", "search_specific_file_parents", "seen_ids",
 
3475
        "state", "source_index", "target_index", "want_unversioned", "tree"]
 
3476
 
 
3477
    def __init__(self, include_unchanged, use_filesystem_for_exec,
 
3478
        search_specific_files, state, source_index, target_index,
 
3479
        want_unversioned, tree):
 
3480
        self.old_dirname_to_file_id = {}
 
3481
        self.new_dirname_to_file_id = {}
 
3482
        # Are we doing a partial iter_changes?
 
3483
        self.partial = search_specific_files != {''}
 
3484
        # Using a list so that we can access the values and change them in
 
3485
        # nested scope. Each one is [path, file_id, entry]
 
3486
        self.last_source_parent = [None, None]
 
3487
        self.last_target_parent = [None, None]
 
3488
        self.include_unchanged = include_unchanged
 
3489
        self.use_filesystem_for_exec = use_filesystem_for_exec
 
3490
        self.utf8_decode = cache_utf8._utf8_decode
 
3491
        # for all search_indexs in each path at or under each element of
 
3492
        # search_specific_files, if the detail is relocated: add the id, and
 
3493
        # add the relocated path as one to search if its not searched already.
 
3494
        # If the detail is not relocated, add the id.
 
3495
        self.searched_specific_files = set()
 
3496
        # When we search exact paths without expanding downwards, we record
 
3497
        # that here.
 
3498
        self.searched_exact_paths = set()
 
3499
        self.search_specific_files = search_specific_files
 
3500
        # The parents up to the root of the paths we are searching.
 
3501
        # After all normal paths are returned, these specific items are returned.
 
3502
        self.search_specific_file_parents = set()
 
3503
        # The ids we've sent out in the delta.
 
3504
        self.seen_ids = set()
 
3505
        self.state = state
 
3506
        self.source_index = source_index
 
3507
        self.target_index = target_index
 
3508
        if target_index != 0:
 
3509
            # A lot of code in here depends on target_index == 0
 
3510
            raise errors.BzrError('unsupported target index')
 
3511
        self.want_unversioned = want_unversioned
 
3512
        self.tree = tree
 
3513
 
 
3514
    def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
 
3515
        """Compare an entry and real disk to generate delta information.
 
3516
 
 
3517
        :param path_info: top_relpath, basename, kind, lstat, abspath for
 
3518
            the path of entry. If None, then the path is considered absent in 
 
3519
            the target (Perhaps we should pass in a concrete entry for this ?)
 
3520
            Basename is returned as a utf8 string because we expect this
 
3521
            tuple will be ignored, and don't want to take the time to
 
3522
            decode.
 
3523
        :return: (iter_changes_result, changed). If the entry has not been
 
3524
            handled then changed is None. Otherwise it is False if no content
 
3525
            or metadata changes have occurred, and True if any content or
 
3526
            metadata change has occurred. If self.include_unchanged is True then
 
3527
            if changed is not None, iter_changes_result will always be a result
 
3528
            tuple. Otherwise, iter_changes_result is None unless changed is
 
3529
            True.
 
3530
        """
 
3531
        if self.source_index is None:
 
3532
            source_details = DirState.NULL_PARENT_DETAILS
 
3533
        else:
 
3534
            source_details = entry[1][self.source_index]
 
3535
        target_details = entry[1][self.target_index]
 
3536
        target_minikind = target_details[0]
 
3537
        if path_info is not None and target_minikind in 'fdlt':
 
3538
            if not (self.target_index == 0):
 
3539
                raise AssertionError()
 
3540
            link_or_sha1 = update_entry(self.state, entry,
 
3541
                abspath=path_info[4], stat_value=path_info[3])
 
3542
            # The entry may have been modified by update_entry
 
3543
            target_details = entry[1][self.target_index]
 
3544
            target_minikind = target_details[0]
 
3545
        else:
 
3546
            link_or_sha1 = None
 
3547
        file_id = entry[0][2]
 
3548
        source_minikind = source_details[0]
 
3549
        if source_minikind in 'fdltr' and target_minikind in 'fdlt':
 
3550
            # claimed content in both: diff
 
3551
            #   r    | fdlt   |      | add source to search, add id path move and perform
 
3552
            #        |        |      | diff check on source-target
 
3553
            #   r    | fdlt   |  a   | dangling file that was present in the basis.
 
3554
            #        |        |      | ???
 
3555
            if source_minikind in 'r':
 
3556
                # add the source to the search path to find any children it
 
3557
                # has.  TODO ? : only add if it is a container ?
 
3558
                if not osutils.is_inside_any(self.searched_specific_files,
 
3559
                                             source_details[1]):
 
3560
                    self.search_specific_files.add(source_details[1])
 
3561
                # generate the old path; this is needed for stating later
 
3562
                # as well.
 
3563
                old_path = source_details[1]
 
3564
                old_dirname, old_basename = os.path.split(old_path)
 
3565
                path = pathjoin(entry[0][0], entry[0][1])
 
3566
                old_entry = self.state._get_entry(self.source_index,
 
3567
                                             path_utf8=old_path)
 
3568
                # update the source details variable to be the real
 
3569
                # location.
 
3570
                if old_entry == (None, None):
 
3571
                    raise errors.CorruptDirstate(self.state._filename,
 
3572
                        "entry '%s/%s' is considered renamed from %r"
 
3573
                        " but source does not exist\n"
 
3574
                        "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
 
3575
                source_details = old_entry[1][self.source_index]
 
3576
                source_minikind = source_details[0]
 
3577
            else:
 
3578
                old_dirname = entry[0][0]
 
3579
                old_basename = entry[0][1]
 
3580
                old_path = path = None
 
3581
            if path_info is None:
 
3582
                # the file is missing on disk, show as removed.
 
3583
                content_change = True
 
3584
                target_kind = None
 
3585
                target_exec = False
 
3586
            else:
 
3587
                # source and target are both versioned and disk file is present.
 
3588
                target_kind = path_info[2]
 
3589
                if target_kind == 'directory':
 
3590
                    if path is None:
 
3591
                        old_path = path = pathjoin(old_dirname, old_basename)
 
3592
                    self.new_dirname_to_file_id[path] = file_id
 
3593
                    if source_minikind != 'd':
 
3594
                        content_change = True
 
3595
                    else:
 
3596
                        # directories have no fingerprint
 
3597
                        content_change = False
 
3598
                    target_exec = False
 
3599
                elif target_kind == 'file':
 
3600
                    if source_minikind != 'f':
 
3601
                        content_change = True
 
3602
                    else:
 
3603
                        # Check the sha. We can't just rely on the size as
 
3604
                        # content filtering may mean differ sizes actually
 
3605
                        # map to the same content
 
3606
                        if link_or_sha1 is None:
 
3607
                            # Stat cache miss:
 
3608
                            statvalue, link_or_sha1 = \
 
3609
                                self.state._sha1_provider.stat_and_sha1(
 
3610
                                path_info[4])
 
3611
                            self.state._observed_sha1(entry, link_or_sha1,
 
3612
                                statvalue)
 
3613
                        content_change = (link_or_sha1 != source_details[1])
 
3614
                    # Target details is updated at update_entry time
 
3615
                    if self.use_filesystem_for_exec:
 
3616
                        # We don't need S_ISREG here, because we are sure
 
3617
                        # we are dealing with a file.
 
3618
                        target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
 
3619
                    else:
 
3620
                        target_exec = target_details[3]
 
3621
                elif target_kind == 'symlink':
 
3622
                    if source_minikind != 'l':
 
3623
                        content_change = True
 
3624
                    else:
 
3625
                        content_change = (link_or_sha1 != source_details[1])
 
3626
                    target_exec = False
 
3627
                elif target_kind == 'tree-reference':
 
3628
                    if source_minikind != 't':
 
3629
                        content_change = True
 
3630
                    else:
 
3631
                        content_change = False
 
3632
                    target_exec = False
 
3633
                else:
 
3634
                    if path is None:
 
3635
                        path = pathjoin(old_dirname, old_basename)
 
3636
                    raise errors.BadFileKindError(path, path_info[2])
 
3637
            if source_minikind == 'd':
 
3638
                if path is None:
 
3639
                    old_path = path = pathjoin(old_dirname, old_basename)
 
3640
                self.old_dirname_to_file_id[old_path] = file_id
 
3641
            # parent id is the entry for the path in the target tree
 
3642
            if old_basename and old_dirname == self.last_source_parent[0]:
 
3643
                source_parent_id = self.last_source_parent[1]
 
3644
            else:
 
3645
                try:
 
3646
                    source_parent_id = self.old_dirname_to_file_id[old_dirname]
 
3647
                except KeyError:
 
3648
                    source_parent_entry = self.state._get_entry(self.source_index,
 
3649
                                                           path_utf8=old_dirname)
 
3650
                    source_parent_id = source_parent_entry[0][2]
 
3651
                if source_parent_id == entry[0][2]:
 
3652
                    # This is the root, so the parent is None
 
3653
                    source_parent_id = None
 
3654
                else:
 
3655
                    self.last_source_parent[0] = old_dirname
 
3656
                    self.last_source_parent[1] = source_parent_id
 
3657
            new_dirname = entry[0][0]
 
3658
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
 
3659
                target_parent_id = self.last_target_parent[1]
 
3660
            else:
 
3661
                try:
 
3662
                    target_parent_id = self.new_dirname_to_file_id[new_dirname]
 
3663
                except KeyError:
 
3664
                    # TODO: We don't always need to do the lookup, because the
 
3665
                    #       parent entry will be the same as the source entry.
 
3666
                    target_parent_entry = self.state._get_entry(self.target_index,
 
3667
                                                           path_utf8=new_dirname)
 
3668
                    if target_parent_entry == (None, None):
 
3669
                        raise AssertionError(
 
3670
                            "Could not find target parent in wt: %s\nparent of: %s"
 
3671
                            % (new_dirname, entry))
 
3672
                    target_parent_id = target_parent_entry[0][2]
 
3673
                if target_parent_id == entry[0][2]:
 
3674
                    # This is the root, so the parent is None
 
3675
                    target_parent_id = None
 
3676
                else:
 
3677
                    self.last_target_parent[0] = new_dirname
 
3678
                    self.last_target_parent[1] = target_parent_id
 
3679
 
 
3680
            source_exec = source_details[3]
 
3681
            changed = (content_change
 
3682
                or source_parent_id != target_parent_id
 
3683
                or old_basename != entry[0][1]
 
3684
                or source_exec != target_exec
 
3685
                )
 
3686
            if not changed and not self.include_unchanged:
 
3687
                return None, False
 
3688
            else:
 
3689
                if old_path is None:
 
3690
                    old_path = path = pathjoin(old_dirname, old_basename)
 
3691
                    old_path_u = self.utf8_decode(old_path)[0]
 
3692
                    path_u = old_path_u
 
3693
                else:
 
3694
                    old_path_u = self.utf8_decode(old_path)[0]
 
3695
                    if old_path == path:
 
3696
                        path_u = old_path_u
 
3697
                    else:
 
3698
                        path_u = self.utf8_decode(path)[0]
 
3699
                source_kind = DirState._minikind_to_kind[source_minikind]
 
3700
                return (entry[0][2],
 
3701
                       (old_path_u, path_u),
 
3702
                       content_change,
 
3703
                       (True, True),
 
3704
                       (source_parent_id, target_parent_id),
 
3705
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
 
3706
                       (source_kind, target_kind),
 
3707
                       (source_exec, target_exec)), changed
 
3708
        elif source_minikind in 'a' and target_minikind in 'fdlt':
 
3709
            # looks like a new file
 
3710
            path = pathjoin(entry[0][0], entry[0][1])
 
3711
            # parent id is the entry for the path in the target tree
 
3712
            # TODO: these are the same for an entire directory: cache em.
 
3713
            parent_id = self.state._get_entry(self.target_index,
 
3714
                                         path_utf8=entry[0][0])[0][2]
 
3715
            if parent_id == entry[0][2]:
 
3716
                parent_id = None
 
3717
            if path_info is not None:
 
3718
                # Present on disk:
 
3719
                if self.use_filesystem_for_exec:
 
3720
                    # We need S_ISREG here, because we aren't sure if this
 
3721
                    # is a file or not.
 
3722
                    target_exec = bool(
 
3723
                        stat.S_ISREG(path_info[3].st_mode)
 
3724
                        and stat.S_IEXEC & path_info[3].st_mode)
 
3725
                else:
 
3726
                    target_exec = target_details[3]
 
3727
                return (entry[0][2],
 
3728
                       (None, self.utf8_decode(path)[0]),
 
3729
                       True,
 
3730
                       (False, True),
 
3731
                       (None, parent_id),
 
3732
                       (None, self.utf8_decode(entry[0][1])[0]),
 
3733
                       (None, path_info[2]),
 
3734
                       (None, target_exec)), True
 
3735
            else:
 
3736
                # Its a missing file, report it as such.
 
3737
                return (entry[0][2],
 
3738
                       (None, self.utf8_decode(path)[0]),
 
3739
                       False,
 
3740
                       (False, True),
 
3741
                       (None, parent_id),
 
3742
                       (None, self.utf8_decode(entry[0][1])[0]),
 
3743
                       (None, None),
 
3744
                       (None, False)), True
 
3745
        elif source_minikind in 'fdlt' and target_minikind in 'a':
 
3746
            # unversioned, possibly, or possibly not deleted: we dont care.
 
3747
            # if its still on disk, *and* theres no other entry at this
 
3748
            # path [we dont know this in this routine at the moment -
 
3749
            # perhaps we should change this - then it would be an unknown.
 
3750
            old_path = pathjoin(entry[0][0], entry[0][1])
 
3751
            # parent id is the entry for the path in the target tree
 
3752
            parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
 
3753
            if parent_id == entry[0][2]:
 
3754
                parent_id = None
 
3755
            return (entry[0][2],
 
3756
                   (self.utf8_decode(old_path)[0], None),
 
3757
                   True,
 
3758
                   (True, False),
 
3759
                   (parent_id, None),
 
3760
                   (self.utf8_decode(entry[0][1])[0], None),
 
3761
                   (DirState._minikind_to_kind[source_minikind], None),
 
3762
                   (source_details[3], None)), True
 
3763
        elif source_minikind in 'fdlt' and target_minikind in 'r':
 
3764
            # a rename; could be a true rename, or a rename inherited from
 
3765
            # a renamed parent. TODO: handle this efficiently. Its not
 
3766
            # common case to rename dirs though, so a correct but slow
 
3767
            # implementation will do.
 
3768
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
 
3769
                self.search_specific_files.add(target_details[1])
 
3770
        elif source_minikind in 'ra' and target_minikind in 'ra':
 
3771
            # neither of the selected trees contain this file,
 
3772
            # so skip over it. This is not currently directly tested, but
 
3773
            # is indirectly via test_too_much.TestCommands.test_conflicts.
 
3774
            pass
 
3775
        else:
 
3776
            raise AssertionError("don't know how to compare "
 
3777
                "source_minikind=%r, target_minikind=%r"
 
3778
                % (source_minikind, target_minikind))
 
3779
        return None, None
 
3780
 
 
3781
    def __iter__(self):
 
3782
        return self
 
3783
 
 
3784
    def _gather_result_for_consistency(self, result):
 
3785
        """Check a result we will yield to make sure we are consistent later.
 
3786
        
 
3787
        This gathers result's parents into a set to output later.
 
3788
 
 
3789
        :param result: A result tuple.
 
3790
        """
 
3791
        if not self.partial or not result[0]:
 
3792
            return
 
3793
        self.seen_ids.add(result[0])
 
3794
        new_path = result[1][1]
 
3795
        if new_path:
 
3796
            # Not the root and not a delete: queue up the parents of the path.
 
3797
            self.search_specific_file_parents.update(
 
3798
                osutils.parent_directories(new_path.encode('utf8')))
 
3799
            # Add the root directory which parent_directories does not
 
3800
            # provide.
 
3801
            self.search_specific_file_parents.add('')
 
3802
 
 
3803
    def iter_changes(self):
 
3804
        """Iterate over the changes."""
 
3805
        utf8_decode = cache_utf8._utf8_decode
 
3806
        _lt_by_dirs = lt_by_dirs
 
3807
        _process_entry = self._process_entry
 
3808
        search_specific_files = self.search_specific_files
 
3809
        searched_specific_files = self.searched_specific_files
 
3810
        splitpath = osutils.splitpath
 
3811
        # sketch:
 
3812
        # compare source_index and target_index at or under each element of search_specific_files.
 
3813
        # follow the following comparison table. Note that we only want to do diff operations when
 
3814
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
 
3815
        # for the target.
 
3816
        # cases:
 
3817
        #
 
3818
        # Source | Target | disk | action
 
3819
        #   r    | fdlt   |      | add source to search, add id path move and perform
 
3820
        #        |        |      | diff check on source-target
 
3821
        #   r    | fdlt   |  a   | dangling file that was present in the basis.
 
3822
        #        |        |      | ???
 
3823
        #   r    |  a     |      | add source to search
 
3824
        #   r    |  a     |  a   |
 
3825
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
 
3826
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
 
3827
        #   a    | fdlt   |      | add new id
 
3828
        #   a    | fdlt   |  a   | dangling locally added file, skip
 
3829
        #   a    |  a     |      | not present in either tree, skip
 
3830
        #   a    |  a     |  a   | not present in any tree, skip
 
3831
        #   a    |  r     |      | not present in either tree at this path, skip as it
 
3832
        #        |        |      | may not be selected by the users list of paths.
 
3833
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
 
3834
        #        |        |      | may not be selected by the users list of paths.
 
3835
        #  fdlt  | fdlt   |      | content in both: diff them
 
3836
        #  fdlt  | fdlt   |  a   | deleted locally, but not unversioned - show as deleted ?
 
3837
        #  fdlt  |  a     |      | unversioned: output deleted id for now
 
3838
        #  fdlt  |  a     |  a   | unversioned and deleted: output deleted id
 
3839
        #  fdlt  |  r     |      | relocated in this tree, so add target to search.
 
3840
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
3841
        #        |        |      | this id at the other path.
 
3842
        #  fdlt  |  r     |  a   | relocated in this tree, so add target to search.
 
3843
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
 
3844
        #        |        |      | this id at the other path.
 
3845
 
 
3846
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
 
3847
        #       keeping a cache of directories that we have seen.
 
3848
 
 
3849
        while search_specific_files:
 
3850
            # TODO: the pending list should be lexically sorted?  the
 
3851
            # interface doesn't require it.
 
3852
            current_root = search_specific_files.pop()
 
3853
            current_root_unicode = current_root.decode('utf8')
 
3854
            searched_specific_files.add(current_root)
 
3855
            # process the entries for this containing directory: the rest will be
 
3856
            # found by their parents recursively.
 
3857
            root_entries = self.state._entries_for_path(current_root)
 
3858
            root_abspath = self.tree.abspath(current_root_unicode)
 
3859
            try:
 
3860
                root_stat = os.lstat(root_abspath)
 
3861
            except OSError as e:
 
3862
                if e.errno == errno.ENOENT:
 
3863
                    # the path does not exist: let _process_entry know that.
 
3864
                    root_dir_info = None
 
3865
                else:
 
3866
                    # some other random error: hand it up.
 
3867
                    raise
 
3868
            else:
 
3869
                root_dir_info = ('', current_root,
 
3870
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
 
3871
                    root_abspath)
 
3872
                if root_dir_info[2] == 'directory':
 
3873
                    if self.tree._directory_is_tree_reference(
 
3874
                        current_root.decode('utf8')):
 
3875
                        root_dir_info = root_dir_info[:2] + \
 
3876
                            ('tree-reference',) + root_dir_info[3:]
 
3877
 
 
3878
            if not root_entries and not root_dir_info:
 
3879
                # this specified path is not present at all, skip it.
 
3880
                continue
 
3881
            path_handled = False
 
3882
            for entry in root_entries:
 
3883
                result, changed = _process_entry(entry, root_dir_info)
 
3884
                if changed is not None:
 
3885
                    path_handled = True
 
3886
                    if changed:
 
3887
                        self._gather_result_for_consistency(result)
 
3888
                    if changed or self.include_unchanged:
 
3889
                        yield result
 
3890
            if self.want_unversioned and not path_handled and root_dir_info:
 
3891
                new_executable = bool(
 
3892
                    stat.S_ISREG(root_dir_info[3].st_mode)
 
3893
                    and stat.S_IEXEC & root_dir_info[3].st_mode)
 
3894
                yield (None,
 
3895
                       (None, current_root_unicode),
 
3896
                       True,
 
3897
                       (False, False),
 
3898
                       (None, None),
 
3899
                       (None, splitpath(current_root_unicode)[-1]),
 
3900
                       (None, root_dir_info[2]),
 
3901
                       (None, new_executable)
 
3902
                      )
 
3903
            initial_key = (current_root, '', '')
 
3904
            block_index, _ = self.state._find_block_index_from_key(initial_key)
 
3905
            if block_index == 0:
 
3906
                # we have processed the total root already, but because the
 
3907
                # initial key matched it we should skip it here.
 
3908
                block_index +=1
 
3909
            if root_dir_info and root_dir_info[2] == 'tree-reference':
 
3910
                current_dir_info = None
 
3911
            else:
 
3912
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
 
3913
                try:
 
3914
                    current_dir_info = next(dir_iterator)
 
3915
                except OSError as e:
 
3916
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
 
3917
                    # python 2.5 has e.errno == EINVAL,
 
3918
                    #            and e.winerror == ERROR_DIRECTORY
 
3919
                    e_winerror = getattr(e, 'winerror', None)
 
3920
                    win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
 
3921
                    # there may be directories in the inventory even though
 
3922
                    # this path is not a file on disk: so mark it as end of
 
3923
                    # iterator
 
3924
                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
 
3925
                        current_dir_info = None
 
3926
                    elif (sys.platform == 'win32'
 
3927
                          and (e.errno in win_errors
 
3928
                               or e_winerror in win_errors)):
 
3929
                        current_dir_info = None
 
3930
                    else:
 
3931
                        raise
 
3932
                else:
 
3933
                    if current_dir_info[0][0] == '':
 
3934
                        # remove .bzr from iteration
 
3935
                        bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
 
3936
                        if current_dir_info[1][bzr_index][0] != '.bzr':
 
3937
                            raise AssertionError()
 
3938
                        del current_dir_info[1][bzr_index]
 
3939
            # walk until both the directory listing and the versioned metadata
 
3940
            # are exhausted.
 
3941
            if (block_index < len(self.state._dirblocks) and
 
3942
                osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
 
3943
                current_block = self.state._dirblocks[block_index]
 
3944
            else:
 
3945
                current_block = None
 
3946
            while (current_dir_info is not None or
 
3947
                   current_block is not None):
 
3948
                if (current_dir_info and current_block
 
3949
                    and current_dir_info[0][0] != current_block[0]):
 
3950
                    if _lt_by_dirs(current_dir_info[0][0], current_block[0]):
 
3951
                        # filesystem data refers to paths not covered by the dirblock.
 
3952
                        # this has two possibilities:
 
3953
                        # A) it is versioned but empty, so there is no block for it
 
3954
                        # B) it is not versioned.
 
3955
 
 
3956
                        # if (A) then we need to recurse into it to check for
 
3957
                        # new unknown files or directories.
 
3958
                        # if (B) then we should ignore it, because we don't
 
3959
                        # recurse into unknown directories.
 
3960
                        path_index = 0
 
3961
                        while path_index < len(current_dir_info[1]):
 
3962
                                current_path_info = current_dir_info[1][path_index]
 
3963
                                if self.want_unversioned:
 
3964
                                    if current_path_info[2] == 'directory':
 
3965
                                        if self.tree._directory_is_tree_reference(
 
3966
                                            current_path_info[0].decode('utf8')):
 
3967
                                            current_path_info = current_path_info[:2] + \
 
3968
                                                ('tree-reference',) + current_path_info[3:]
 
3969
                                    new_executable = bool(
 
3970
                                        stat.S_ISREG(current_path_info[3].st_mode)
 
3971
                                        and stat.S_IEXEC & current_path_info[3].st_mode)
 
3972
                                    yield (None,
 
3973
                                        (None, utf8_decode(current_path_info[0])[0]),
 
3974
                                        True,
 
3975
                                        (False, False),
 
3976
                                        (None, None),
 
3977
                                        (None, utf8_decode(current_path_info[1])[0]),
 
3978
                                        (None, current_path_info[2]),
 
3979
                                        (None, new_executable))
 
3980
                                # dont descend into this unversioned path if it is
 
3981
                                # a dir
 
3982
                                if current_path_info[2] in ('directory',
 
3983
                                                            'tree-reference'):
 
3984
                                    del current_dir_info[1][path_index]
 
3985
                                    path_index -= 1
 
3986
                                path_index += 1
 
3987
 
 
3988
                        # This dir info has been handled, go to the next
 
3989
                        try:
 
3990
                            current_dir_info = next(dir_iterator)
 
3991
                        except StopIteration:
 
3992
                            current_dir_info = None
 
3993
                    else:
 
3994
                        # We have a dirblock entry for this location, but there
 
3995
                        # is no filesystem path for this. This is most likely
 
3996
                        # because a directory was removed from the disk.
 
3997
                        # We don't have to report the missing directory,
 
3998
                        # because that should have already been handled, but we
 
3999
                        # need to handle all of the files that are contained
 
4000
                        # within.
 
4001
                        for current_entry in current_block[1]:
 
4002
                            # entry referring to file not present on disk.
 
4003
                            # advance the entry only, after processing.
 
4004
                            result, changed = _process_entry(current_entry, None)
 
4005
                            if changed is not None:
 
4006
                                if changed:
 
4007
                                    self._gather_result_for_consistency(result)
 
4008
                                if changed or self.include_unchanged:
 
4009
                                    yield result
 
4010
                        block_index +=1
 
4011
                        if (block_index < len(self.state._dirblocks) and
 
4012
                            osutils.is_inside(current_root,
 
4013
                                              self.state._dirblocks[block_index][0])):
 
4014
                            current_block = self.state._dirblocks[block_index]
 
4015
                        else:
 
4016
                            current_block = None
 
4017
                    continue
 
4018
                entry_index = 0
 
4019
                if current_block and entry_index < len(current_block[1]):
 
4020
                    current_entry = current_block[1][entry_index]
 
4021
                else:
 
4022
                    current_entry = None
 
4023
                advance_entry = True
 
4024
                path_index = 0
 
4025
                if current_dir_info and path_index < len(current_dir_info[1]):
 
4026
                    current_path_info = current_dir_info[1][path_index]
 
4027
                    if current_path_info[2] == 'directory':
 
4028
                        if self.tree._directory_is_tree_reference(
 
4029
                            current_path_info[0].decode('utf8')):
 
4030
                            current_path_info = current_path_info[:2] + \
 
4031
                                ('tree-reference',) + current_path_info[3:]
 
4032
                else:
 
4033
                    current_path_info = None
 
4034
                advance_path = True
 
4035
                path_handled = False
 
4036
                while (current_entry is not None or
 
4037
                    current_path_info is not None):
 
4038
                    if current_entry is None:
 
4039
                        # the check for path_handled when the path is advanced
 
4040
                        # will yield this path if needed.
 
4041
                        pass
 
4042
                    elif current_path_info is None:
 
4043
                        # no path is fine: the per entry code will handle it.
 
4044
                        result, changed = _process_entry(current_entry, current_path_info)
 
4045
                        if changed is not None:
 
4046
                            if changed:
 
4047
                                self._gather_result_for_consistency(result)
 
4048
                            if changed or self.include_unchanged:
 
4049
                                yield result
 
4050
                    elif (current_entry[0][1] != current_path_info[1]
 
4051
                          or current_entry[1][self.target_index][0] in 'ar'):
 
4052
                        # The current path on disk doesn't match the dirblock
 
4053
                        # record. Either the dirblock is marked as absent, or
 
4054
                        # the file on disk is not present at all in the
 
4055
                        # dirblock. Either way, report about the dirblock
 
4056
                        # entry, and let other code handle the filesystem one.
 
4057
 
 
4058
                        # Compare the basename for these files to determine
 
4059
                        # which comes first
 
4060
                        if current_path_info[1] < current_entry[0][1]:
 
4061
                            # extra file on disk: pass for now, but only
 
4062
                            # increment the path, not the entry
 
4063
                            advance_entry = False
 
4064
                        else:
 
4065
                            # entry referring to file not present on disk.
 
4066
                            # advance the entry only, after processing.
 
4067
                            result, changed = _process_entry(current_entry, None)
 
4068
                            if changed is not None:
 
4069
                                if changed:
 
4070
                                    self._gather_result_for_consistency(result)
 
4071
                                if changed or self.include_unchanged:
 
4072
                                    yield result
 
4073
                            advance_path = False
 
4074
                    else:
 
4075
                        result, changed = _process_entry(current_entry, current_path_info)
 
4076
                        if changed is not None:
 
4077
                            path_handled = True
 
4078
                            if changed:
 
4079
                                self._gather_result_for_consistency(result)
 
4080
                            if changed or self.include_unchanged:
 
4081
                                yield result
 
4082
                    if advance_entry and current_entry is not None:
 
4083
                        entry_index += 1
 
4084
                        if entry_index < len(current_block[1]):
 
4085
                            current_entry = current_block[1][entry_index]
 
4086
                        else:
 
4087
                            current_entry = None
 
4088
                    else:
 
4089
                        advance_entry = True # reset the advance flaga
 
4090
                    if advance_path and current_path_info is not None:
 
4091
                        if not path_handled:
 
4092
                            # unversioned in all regards
 
4093
                            if self.want_unversioned:
 
4094
                                new_executable = bool(
 
4095
                                    stat.S_ISREG(current_path_info[3].st_mode)
 
4096
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
 
4097
                                try:
 
4098
                                    relpath_unicode = utf8_decode(current_path_info[0])[0]
 
4099
                                except UnicodeDecodeError:
 
4100
                                    raise errors.BadFilenameEncoding(
 
4101
                                        current_path_info[0], osutils._fs_enc)
 
4102
                                yield (None,
 
4103
                                    (None, relpath_unicode),
 
4104
                                    True,
 
4105
                                    (False, False),
 
4106
                                    (None, None),
 
4107
                                    (None, utf8_decode(current_path_info[1])[0]),
 
4108
                                    (None, current_path_info[2]),
 
4109
                                    (None, new_executable))
 
4110
                            # dont descend into this unversioned path if it is
 
4111
                            # a dir
 
4112
                            if current_path_info[2] in ('directory'):
 
4113
                                del current_dir_info[1][path_index]
 
4114
                                path_index -= 1
 
4115
                        # dont descend the disk iterator into any tree
 
4116
                        # paths.
 
4117
                        if current_path_info[2] == 'tree-reference':
 
4118
                            del current_dir_info[1][path_index]
 
4119
                            path_index -= 1
 
4120
                        path_index += 1
 
4121
                        if path_index < len(current_dir_info[1]):
 
4122
                            current_path_info = current_dir_info[1][path_index]
 
4123
                            if current_path_info[2] == 'directory':
 
4124
                                if self.tree._directory_is_tree_reference(
 
4125
                                    current_path_info[0].decode('utf8')):
 
4126
                                    current_path_info = current_path_info[:2] + \
 
4127
                                        ('tree-reference',) + current_path_info[3:]
 
4128
                        else:
 
4129
                            current_path_info = None
 
4130
                        path_handled = False
 
4131
                    else:
 
4132
                        advance_path = True # reset the advance flagg.
 
4133
                if current_block is not None:
 
4134
                    block_index += 1
 
4135
                    if (block_index < len(self.state._dirblocks) and
 
4136
                        osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
 
4137
                        current_block = self.state._dirblocks[block_index]
 
4138
                    else:
 
4139
                        current_block = None
 
4140
                if current_dir_info is not None:
 
4141
                    try:
 
4142
                        current_dir_info = next(dir_iterator)
 
4143
                    except StopIteration:
 
4144
                        current_dir_info = None
 
4145
        for result in self._iter_specific_file_parents():
 
4146
            yield result
 
4147
 
 
4148
    def _iter_specific_file_parents(self):
 
4149
        """Iter over the specific file parents."""
 
4150
        while self.search_specific_file_parents:
 
4151
            # Process the parent directories for the paths we were iterating.
 
4152
            # Even in extremely large trees this should be modest, so currently
 
4153
            # no attempt is made to optimise.
 
4154
            path_utf8 = self.search_specific_file_parents.pop()
 
4155
            if osutils.is_inside_any(self.searched_specific_files, path_utf8):
 
4156
                # We've examined this path.
 
4157
                continue
 
4158
            if path_utf8 in self.searched_exact_paths:
 
4159
                # We've examined this path.
 
4160
                continue
 
4161
            path_entries = self.state._entries_for_path(path_utf8)
 
4162
            # We need either one or two entries. If the path in
 
4163
            # self.target_index has moved (so the entry in source_index is in
 
4164
            # 'ar') then we need to also look for the entry for this path in
 
4165
            # self.source_index, to output the appropriate delete-or-rename.
 
4166
            selected_entries = []
 
4167
            found_item = False
 
4168
            for candidate_entry in path_entries:
 
4169
                # Find entries present in target at this path:
 
4170
                if candidate_entry[1][self.target_index][0] not in 'ar':
 
4171
                    found_item = True
 
4172
                    selected_entries.append(candidate_entry)
 
4173
                # Find entries present in source at this path:
 
4174
                elif (self.source_index is not None and
 
4175
                    candidate_entry[1][self.source_index][0] not in 'ar'):
 
4176
                    found_item = True
 
4177
                    if candidate_entry[1][self.target_index][0] == 'a':
 
4178
                        # Deleted, emit it here.
 
4179
                        selected_entries.append(candidate_entry)
 
4180
                    else:
 
4181
                        # renamed, emit it when we process the directory it
 
4182
                        # ended up at.
 
4183
                        self.search_specific_file_parents.add(
 
4184
                            candidate_entry[1][self.target_index][1])
 
4185
            if not found_item:
 
4186
                raise AssertionError(
 
4187
                    "Missing entry for specific path parent %r, %r" % (
 
4188
                    path_utf8, path_entries))
 
4189
            path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
 
4190
            for entry in selected_entries:
 
4191
                if entry[0][2] in self.seen_ids:
 
4192
                    continue
 
4193
                result, changed = self._process_entry(entry, path_info)
 
4194
                if changed is None:
 
4195
                    raise AssertionError(
 
4196
                        "Got entry<->path mismatch for specific path "
 
4197
                        "%r entry %r path_info %r " % (
 
4198
                        path_utf8, entry, path_info))
 
4199
                # Only include changes - we're outside the users requested
 
4200
                # expansion.
 
4201
                if changed:
 
4202
                    self._gather_result_for_consistency(result)
 
4203
                    if (result[6][0] == 'directory' and
 
4204
                        result[6][1] != 'directory'):
 
4205
                        # This stopped being a directory, the old children have
 
4206
                        # to be included.
 
4207
                        if entry[1][self.source_index][0] == 'r':
 
4208
                            # renamed, take the source path
 
4209
                            entry_path_utf8 = entry[1][self.source_index][1]
 
4210
                        else:
 
4211
                            entry_path_utf8 = path_utf8
 
4212
                        initial_key = (entry_path_utf8, '', '')
 
4213
                        block_index, _ = self.state._find_block_index_from_key(
 
4214
                            initial_key)
 
4215
                        if block_index == 0:
 
4216
                            # The children of the root are in block index 1.
 
4217
                            block_index +=1
 
4218
                        current_block = None
 
4219
                        if block_index < len(self.state._dirblocks):
 
4220
                            current_block = self.state._dirblocks[block_index]
 
4221
                            if not osutils.is_inside(
 
4222
                                entry_path_utf8, current_block[0]):
 
4223
                                # No entries for this directory at all.
 
4224
                                current_block = None
 
4225
                        if current_block is not None:
 
4226
                            for entry in current_block[1]:
 
4227
                                if entry[1][self.source_index][0] in 'ar':
 
4228
                                    # Not in the source tree, so doesn't have to be
 
4229
                                    # included.
 
4230
                                    continue
 
4231
                                # Path of the entry itself.
 
4232
 
 
4233
                                self.search_specific_file_parents.add(
 
4234
                                    osutils.pathjoin(*entry[0][:2]))
 
4235
                if changed or self.include_unchanged:
 
4236
                    yield result
 
4237
            self.searched_exact_paths.add(path_utf8)
 
4238
 
 
4239
    def _path_info(self, utf8_path, unicode_path):
 
4240
        """Generate path_info for unicode_path.
 
4241
 
 
4242
        :return: None if unicode_path does not exist, or a path_info tuple.
 
4243
        """
 
4244
        abspath = self.tree.abspath(unicode_path)
 
4245
        try:
 
4246
            stat = os.lstat(abspath)
 
4247
        except OSError as e:
 
4248
            if e.errno == errno.ENOENT:
 
4249
                # the path does not exist.
 
4250
                return None
 
4251
            else:
 
4252
                raise
 
4253
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
 
4254
        dir_info = (utf8_path, utf8_basename,
 
4255
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
 
4256
            abspath)
 
4257
        if dir_info[2] == 'directory':
 
4258
            if self.tree._directory_is_tree_reference(
 
4259
                unicode_path):
 
4260
                self.root_dir_info = self.root_dir_info[:2] + \
 
4261
                    ('tree-reference',) + self.root_dir_info[3:]
 
4262
        return dir_info
 
4263
 
 
4264
 
 
4265
# Try to load the compiled form if possible
 
4266
try:
 
4267
    from breezy._dirstate_helpers_pyx import (
 
4268
        _read_dirblocks,
 
4269
        bisect_dirblock,
 
4270
        _bisect_path_left,
 
4271
        _bisect_path_right,
 
4272
        lt_by_dirs,
 
4273
        pack_stat,
 
4274
        ProcessEntryC as _process_entry,
 
4275
        update_entry as update_entry,
 
4276
        )
 
4277
except ImportError as e:
 
4278
    osutils.failed_to_load_extension(e)
 
4279
    from breezy._dirstate_helpers_py import (
 
4280
        _read_dirblocks,
 
4281
        bisect_dirblock,
 
4282
        _bisect_path_left,
 
4283
        _bisect_path_right,
 
4284
        lt_by_dirs,
 
4285
        pack_stat,
 
4286
        )
 
4287
    # FIXME: It would be nice to be able to track moved lines so that the
 
4288
    # corresponding python code can be moved to the _dirstate_helpers_py
 
4289
    # module. I don't want to break the history for this important piece of
 
4290
    # code so I left the code here -- vila 20090622
 
4291
    update_entry = py_update_entry
 
4292
    _process_entry = ProcessEntryPython