~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: Robert Collins
  • Date: 2007-10-23 22:14:32 UTC
  • mto: (2592.6.3 repository)
  • mto: This revision was merged to the branch mainline in revision 2967.
  • Revision ID: robertc@robertcollins.net-20071023221432-j8zndh1oiegql3cu
* Commit updates the state of the working tree via a delta rather than
  supplying entirely new basis trees. For commit of a single specified file
  this reduces the wall clock time for commit by roughly a 30%.
  (Robert Collins, Martin Pool)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2011 Canonical Ltd
 
1
# Copyright (C) 2006, 2007 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
"""DirState objects record the state of a directory and its bzr metadata.
18
18
 
82
82
'a' is an absent entry: In that tree the id is not present at this path.
83
83
'd' is a directory entry: This path in this tree is a directory with the
84
84
    current file id. There is no fingerprint for directories.
85
 
'f' is a file entry: As for directory, but it's a file. The fingerprint is the
86
 
    sha1 value of the file's canonical form, i.e. after any read filters have
87
 
    been applied to the convenience form stored in the working tree.
 
85
'f' is a file entry: As for directory, but its a file. The fingerprint is a
 
86
    sha1 value.
88
87
'l' is a symlink entry: As for directory, but a symlink. The fingerprint is the
89
88
    link target.
90
89
't' is a reference to a nested subtree; the fingerprint is the referenced
100
99
 
101
100
--- Format 1 had the following different definition: ---
102
101
rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
103
 
    WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
 
102
    WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target, 
104
103
    {PARENT ROW}
105
104
PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
106
105
    basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
131
130
operations for the less common but still occurs a lot status/diff/commit
132
131
on specific files). Operations on specific files involve a scan for all
133
132
the children of a path, *in every involved tree*, which the current
134
 
format did not accommodate.
 
133
format did not accommodate. 
135
134
----
136
135
 
137
136
Design priorities:
149
148
 
150
149
Memory representation:
151
150
 vector of all directories, and vector of the childen ?
152
 
   i.e.
153
 
     root_entrie = (direntry for root, [parent_direntries_for_root]),
 
151
   i.e. 
 
152
     root_entrie = (direntry for root, [parent_direntries_for_root]), 
154
153
     dirblocks = [
155
154
     ('', ['data for achild', 'data for bchild', 'data for cchild'])
156
155
     ('dir', ['achild', 'cchild', 'echild'])
159
158
    - in-order for serialisation - this is 'dirblock' grouping.
160
159
    - insertion of a file '/a' affects only the '/' child-vector, that is, to
161
160
      insert 10K elements from scratch does not generates O(N^2) memoves of a
162
 
      single vector, rather each individual, which tends to be limited to a
163
 
      manageable number. Will scale badly on trees with 10K entries in a
 
161
      single vector, rather each individual, which tends to be limited to a 
 
162
      manageable number. Will scale badly on trees with 10K entries in a 
164
163
      single directory. compare with Inventory.InventoryDirectory which has
165
164
      a dictionary for the children. No bisect capability, can only probe for
166
165
      exact matches, or grab all elements and sort.
167
166
    - What's the risk of error here? Once we have the base format being processed
168
 
      we should have a net win regardless of optimality. So we are going to
 
167
      we should have a net win regardless of optimality. So we are going to 
169
168
      go with what seems reasonable.
170
169
open questions:
171
170
 
187
186
the file on disk, and then immediately discard, the overhead of object creation
188
187
becomes a significant cost.
189
188
 
190
 
Figures: Creating a tuple from 3 elements was profiled at 0.0625
 
189
Figures: Creating a tuple from from 3 elements was profiled at 0.0625
191
190
microseconds, whereas creating a object which is subclassed from tuple was
192
191
0.500 microseconds, and creating an object with 3 elements and slots was 3
193
192
microseconds long. 0.1 milliseconds is 100 microseconds, and ideally we'll get
204
203
import bisect
205
204
import binascii
206
205
import errno
207
 
import operator
208
206
import os
209
207
from stat import S_IEXEC
210
208
import stat
220
218
    inventory,
221
219
    lock,
222
220
    osutils,
223
 
    static_tuple,
224
221
    trace,
225
222
    )
226
223
 
227
224
 
228
 
# This is the Windows equivalent of ENOTDIR
229
 
# It is defined in pywin32.winerror, but we don't want a strong dependency for
230
 
# just an error code.
231
 
ERROR_PATH_NOT_FOUND = 3
232
 
ERROR_DIRECTORY = 267
233
 
 
234
 
 
235
 
if not getattr(struct, '_compile', None):
236
 
    # Cannot pre-compile the dirstate pack_stat
237
 
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
238
 
        """Convert stat values into a packed representation."""
239
 
        return _encode(_pack('>LLLLLL', st.st_size, int(st.st_mtime),
240
 
            int(st.st_ctime), st.st_dev, st.st_ino & 0xFFFFFFFF,
241
 
            st.st_mode))[:-1]
242
 
else:
243
 
    # compile the struct compiler we need, so as to only do it once
244
 
    from _struct import Struct
245
 
    _compiled_pack = Struct('>LLLLLL').pack
246
 
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=_compiled_pack):
247
 
        """Convert stat values into a packed representation."""
248
 
        # jam 20060614 it isn't really worth removing more entries if we
249
 
        # are going to leave it in packed form.
250
 
        # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
251
 
        # With all entries, filesize is 5.9M and read time is maybe 280ms
252
 
        # well within the noise margin
253
 
 
254
 
        # base64 encoding always adds a final newline, so strip it off
255
 
        # The current version
256
 
        return _encode(_pack(st.st_size, int(st.st_mtime), int(st.st_ctime),
257
 
            st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
258
 
        # This is 0.060s / 1.520s faster by not encoding as much information
259
 
        # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
260
 
        # This is not strictly faster than _encode(_pack())[:-1]
261
 
        # return '%X.%X.%X.%X.%X.%X' % (
262
 
        #      st.st_size, int(st.st_mtime), int(st.st_ctime),
263
 
        #      st.st_dev, st.st_ino, st.st_mode)
264
 
        # Similar to the _encode(_pack('>LL'))
265
 
        # return '%X.%X' % (int(st.st_mtime), st.st_mode)
266
 
 
267
 
 
268
 
def _unpack_stat(packed_stat):
269
 
    """Turn a packed_stat back into the stat fields.
270
 
 
271
 
    This is meant as a debugging tool, should not be used in real code.
272
 
    """
273
 
    (st_size, st_mtime, st_ctime, st_dev, st_ino,
274
 
     st_mode) = struct.unpack('>LLLLLL', binascii.a2b_base64(packed_stat))
275
 
    return dict(st_size=st_size, st_mtime=st_mtime, st_ctime=st_ctime,
276
 
                st_dev=st_dev, st_ino=st_ino, st_mode=st_mode)
277
 
 
278
 
 
279
 
class SHA1Provider(object):
280
 
    """An interface for getting sha1s of a file."""
281
 
 
282
 
    def sha1(self, abspath):
283
 
        """Return the sha1 of a file given its absolute path.
284
 
 
285
 
        :param abspath:  May be a filesystem encoded absolute path
286
 
             or a unicode path.
287
 
        """
288
 
        raise NotImplementedError(self.sha1)
289
 
 
290
 
    def stat_and_sha1(self, abspath):
291
 
        """Return the stat and sha1 of a file given its absolute path.
292
 
        
293
 
        :param abspath:  May be a filesystem encoded absolute path
294
 
             or a unicode path.
295
 
 
296
 
        Note: the stat should be the stat of the physical file
297
 
        while the sha may be the sha of its canonical content.
298
 
        """
299
 
        raise NotImplementedError(self.stat_and_sha1)
300
 
 
301
 
 
302
 
class DefaultSHA1Provider(SHA1Provider):
303
 
    """A SHA1Provider that reads directly from the filesystem."""
304
 
 
305
 
    def sha1(self, abspath):
306
 
        """Return the sha1 of a file given its absolute path."""
307
 
        return osutils.sha_file_by_name(abspath)
308
 
 
309
 
    def stat_and_sha1(self, abspath):
310
 
        """Return the stat and sha1 of a file given its absolute path."""
311
 
        file_obj = file(abspath, 'rb')
312
 
        try:
313
 
            statvalue = os.fstat(file_obj.fileno())
314
 
            sha1 = osutils.sha_file(file_obj)
315
 
        finally:
316
 
            file_obj.close()
317
 
        return statvalue, sha1
 
225
def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
 
226
    """Convert stat values into a packed representation."""
 
227
    # jam 20060614 it isn't really worth removing more entries if we
 
228
    # are going to leave it in packed form.
 
229
    # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
 
230
    # With all entries, filesize is 5.9M and read time is maybe 280ms
 
231
    # well within the noise margin
 
232
 
 
233
    # base64 encoding always adds a final newline, so strip it off
 
234
    # The current version
 
235
    return _encode(_pack('>LLLLLL'
 
236
        , st.st_size, int(st.st_mtime), int(st.st_ctime)
 
237
        , st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
 
238
    # This is 0.060s / 1.520s faster by not encoding as much information
 
239
    # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
 
240
    # This is not strictly faster than _encode(_pack())[:-1]
 
241
    # return '%X.%X.%X.%X.%X.%X' % (
 
242
    #      st.st_size, int(st.st_mtime), int(st.st_ctime),
 
243
    #      st.st_dev, st.st_ino, st.st_mode)
 
244
    # Similar to the _encode(_pack('>LL'))
 
245
    # return '%X.%X' % (int(st.st_mtime), st.st_mode)
318
246
 
319
247
 
320
248
class DirState(object):
322
250
 
323
251
    A dirstate is a specialised data structure for managing local working
324
252
    tree state information. Its not yet well defined whether it is platform
325
 
    specific, and if it is how we detect/parameterize that.
 
253
    specific, and if it is how we detect/parameterise that.
326
254
 
327
255
    Dirstates use the usual lock_write, lock_read and unlock mechanisms.
328
256
    Unlike most bzr disk formats, DirStates must be locked for reading, using
375
303
    HEADER_FORMAT_2 = '#bazaar dirstate flat format 2\n'
376
304
    HEADER_FORMAT_3 = '#bazaar dirstate flat format 3\n'
377
305
 
378
 
    def __init__(self, path, sha1_provider):
 
306
    def __init__(self, path):
379
307
        """Create a  DirState object.
380
308
 
381
309
        :param path: The path at which the dirstate file on disk should live.
382
 
        :param sha1_provider: an object meeting the SHA1Provider interface.
383
310
        """
384
311
        # _header_state and _dirblock_state represent the current state
385
312
        # of the dirstate metadata and the per-row data respectiely.
387
314
        # IN_MEMORY_UNMODIFIED indicates that what we have in memory
388
315
        #   is the same as is on disk
389
316
        # IN_MEMORY_MODIFIED indicates that we have a modified version
390
 
        #   of what is on disk.
 
317
        #   of what is on disk. 
391
318
        # In future we will add more granularity, for instance _dirblock_state
392
319
        # will probably support partially-in-memory as a separate variable,
393
320
        # allowing for partially-in-memory unmodified and partially-in-memory
394
321
        # modified states.
395
322
        self._header_state = DirState.NOT_IN_MEMORY
396
323
        self._dirblock_state = DirState.NOT_IN_MEMORY
397
 
        # If true, an error has been detected while updating the dirstate, and
398
 
        # for safety we're not going to commit to disk.
399
 
        self._changes_aborted = False
400
324
        self._dirblocks = []
401
325
        self._ghosts = []
402
326
        self._parents = []
405
329
        self._lock_token = None
406
330
        self._lock_state = None
407
331
        self._id_index = None
408
 
        # a map from packed_stat to sha's.
409
 
        self._packed_stat_index = None
410
332
        self._end_of_header = None
411
333
        self._cutoff_time = None
412
334
        self._split_path_cache = {}
413
335
        self._bisect_page_size = DirState.BISECT_PAGE_SIZE
414
 
        self._sha1_provider = sha1_provider
415
336
        if 'hashcache' in debug.debug_flags:
416
337
            self._sha1_file = self._sha1_file_and_mutter
417
338
        else:
418
 
            self._sha1_file = self._sha1_provider.sha1
 
339
            self._sha1_file = osutils.sha_file_by_name
419
340
        # These two attributes provide a simple cache for lookups into the
420
341
        # dirstate in-memory vectors. By probing respectively for the last
421
342
        # block, and for the next entry, we save nearly 2 bisections per path
431
352
        """Add a path to be tracked.
432
353
 
433
354
        :param path: The path within the dirstate - '' is the root, 'foo' is the
434
 
            path foo within the root, 'foo/bar' is the path bar within foo
 
355
            path foo within the root, 'foo/bar' is the path bar within foo 
435
356
            within the root.
436
357
        :param file_id: The file id of the path being added.
437
 
        :param kind: The kind of the path, as a string like 'file',
 
358
        :param kind: The kind of the path, as a string like 'file', 
438
359
            'directory', etc.
439
360
        :param stat: The output of os.lstat for the path.
440
 
        :param fingerprint: The sha value of the file's canonical form (i.e.
441
 
            after any read filters have been applied),
 
361
        :param fingerprint: The sha value of the file,
442
362
            or the target of a symlink,
443
363
            or the referenced revision id for tree-references,
444
364
            or '' for directories.
445
365
        """
446
366
        # adding a file:
447
 
        # find the block its in.
 
367
        # find the block its in. 
448
368
        # find the location in the block.
449
369
        # check its not there
450
370
        # add it.
463
383
        # in the parent, or according to the special treatment for the root
464
384
        if basename == '.' or basename == '..':
465
385
            raise errors.InvalidEntryName(path)
466
 
        # now that we've normalised, we need the correct utf8 path and
 
386
        # now that we've normalised, we need the correct utf8 path and 
467
387
        # dirname and basename elements. This single encode and split should be
468
388
        # faster than three separate encodes.
469
389
        utf8path = (dirname + '/' + basename).strip('/').encode('utf8')
470
390
        dirname, basename = osutils.split(utf8path)
471
 
        # uses __class__ for speed; the check is needed for safety
472
 
        if file_id.__class__ is not str:
473
 
            raise AssertionError(
474
 
                "must be a utf8 file_id not %s" % (type(file_id), ))
 
391
        assert file_id.__class__ == str, \
 
392
            "must be a utf8 file_id not %s" % (type(file_id))
475
393
        # Make sure the file_id does not exist in this tree
476
 
        rename_from = None
477
 
        file_id_entry = self._get_entry(0, fileid_utf8=file_id, include_deleted=True)
 
394
        file_id_entry = self._get_entry(0, fileid_utf8=file_id)
478
395
        if file_id_entry != (None, None):
479
 
            if file_id_entry[1][0][0] == 'a':
480
 
                if file_id_entry[0] != (dirname, basename, file_id):
481
 
                    # set the old name's current operation to rename
482
 
                    self.update_minimal(file_id_entry[0],
483
 
                        'r',
484
 
                        path_utf8='',
485
 
                        packed_stat='',
486
 
                        fingerprint=utf8path
487
 
                    )
488
 
                    rename_from = file_id_entry[0][0:2]
489
 
            else:
490
 
                path = osutils.pathjoin(file_id_entry[0][0], file_id_entry[0][1])
491
 
                kind = DirState._minikind_to_kind[file_id_entry[1][0][0]]
492
 
                info = '%s:%s' % (kind, path)
493
 
                raise errors.DuplicateFileId(file_id, info)
 
396
            path = osutils.pathjoin(file_id_entry[0][0], file_id_entry[0][1])
 
397
            kind = DirState._minikind_to_kind[file_id_entry[1][0][0]]
 
398
            info = '%s:%s' % (kind, path)
 
399
            raise errors.DuplicateFileId(file_id, info)
494
400
        first_key = (dirname, basename, '')
495
401
        block_index, present = self._find_block_index_from_key(first_key)
496
402
        if present:
497
403
            # check the path is not in the tree
498
404
            block = self._dirblocks[block_index][1]
499
405
            entry_index, _ = self._find_entry_index(first_key, block)
500
 
            while (entry_index < len(block) and
 
406
            while (entry_index < len(block) and 
501
407
                block[entry_index][0][0:2] == first_key[0:2]):
502
408
                if block[entry_index][1][0][0] not in 'ar':
503
409
                    # this path is in the dirstate in the current tree.
523
429
            packed_stat = pack_stat(stat)
524
430
        parent_info = self._empty_parent_info()
525
431
        minikind = DirState._kind_to_minikind[kind]
526
 
        if rename_from is not None:
527
 
            if rename_from[0]:
528
 
                old_path_utf8 = '%s/%s' % rename_from
529
 
            else:
530
 
                old_path_utf8 = rename_from[1]
531
 
            parent_info[0] = ('r', old_path_utf8, 0, False, '')
532
432
        if kind == 'file':
533
433
            entry_data = entry_key, [
534
434
                (minikind, fingerprint, size, False, packed_stat),
551
451
        if not present:
552
452
            block.insert(entry_index, entry_data)
553
453
        else:
554
 
            if block[entry_index][1][0][0] != 'a':
555
 
                raise AssertionError(" %r(%r) already added" % (basename, file_id))
 
454
            assert block[entry_index][1][0][0] == 'a', " %r(%r) already added" % (basename, file_id)
556
455
            block[entry_index][1][0] = entry_data[1][0]
557
456
 
558
457
        if kind == 'directory':
560
459
           self._ensure_block(block_index, entry_index, utf8path)
561
460
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
562
461
        if self._id_index:
563
 
            self._add_to_id_index(self._id_index, entry_key)
 
462
            self._id_index.setdefault(entry_key[2], set()).add(entry_key)
564
463
 
565
464
    def _bisect(self, paths):
566
465
        """Bisect through the disk structure for specific rows.
577
476
        # If _dirblock_state was in memory, we should just return info from
578
477
        # there, this function is only meant to handle when we want to read
579
478
        # part of the disk.
580
 
        if self._dirblock_state != DirState.NOT_IN_MEMORY:
581
 
            raise AssertionError("bad dirblock state %r" % self._dirblock_state)
 
479
        assert self._dirblock_state == DirState.NOT_IN_MEMORY
582
480
 
583
481
        # The disk representation is generally info + '\0\n\0' at the end. But
584
482
        # for bisecting, it is easier to treat this as '\0' + info + '\0\n'
770
668
        # If _dirblock_state was in memory, we should just return info from
771
669
        # there, this function is only meant to handle when we want to read
772
670
        # part of the disk.
773
 
        if self._dirblock_state != DirState.NOT_IN_MEMORY:
774
 
            raise AssertionError("bad dirblock state %r" % self._dirblock_state)
 
671
        assert self._dirblock_state == DirState.NOT_IN_MEMORY
 
672
 
775
673
        # The disk representation is generally info + '\0\n\0' at the end. But
776
674
        # for bisecting, it is easier to treat this as '\0' + info + '\0\n'
777
675
        # Because it means we can sync on the '\n'
993
891
 
994
892
    def _discard_merge_parents(self):
995
893
        """Discard any parents trees beyond the first.
996
 
 
 
894
        
997
895
        Note that if this fails the dirstate is corrupted.
998
896
 
999
897
        After this function returns the dirstate contains 2 trees, neither of
1014
912
                    if (entry[1][0][0], entry[1][1][0]) in dead_patterns:
1015
913
                        deleted_positions.append(pos)
1016
914
                if deleted_positions:
1017
 
                    if len(deleted_positions) == len(block[1]):
 
915
                    if len(deleted_positions) == len(block):
1018
916
                        del block[1][:]
1019
917
                    else:
1020
918
                        for pos in reversed(deleted_positions):
1064
962
        # the basename of the directory must be the end of its full name.
1065
963
        if not (parent_block_index == -1 and
1066
964
            parent_block_index == -1 and dirname == ''):
1067
 
            if not dirname.endswith(
1068
 
                    self._dirblocks[parent_block_index][1][parent_row_index][0][1]):
1069
 
                raise AssertionError("bad dirname %r" % dirname)
 
965
            assert dirname.endswith(
 
966
                self._dirblocks[parent_block_index][1][parent_row_index][0][1])
1070
967
        block_index, present = self._find_block_index_from_key((dirname, '', ''))
1071
968
        if not present:
1072
 
            ## In future, when doing partial parsing, this should load and
 
969
            ## In future, when doing partial parsing, this should load and 
1073
970
            # populate the entire block.
1074
971
            self._dirblocks.insert(block_index, (dirname, []))
1075
972
        return block_index
1084
981
            to prevent unneeded overhead when callers have a sorted list already.
1085
982
        :return: Nothing.
1086
983
        """
1087
 
        if new_entries[0][0][0:2] != ('', ''):
1088
 
            raise AssertionError(
1089
 
                "Missing root row %r" % (new_entries[0][0],))
1090
 
        # The two blocks here are deliberate: the root block and the
 
984
        assert new_entries[0][0][0:2] == ('', ''), \
 
985
            "Missing root row %r" % (new_entries[0][0],)
 
986
        # The two blocks here are deliberate: the root block and the 
1091
987
        # contents-of-root block.
1092
988
        self._dirblocks = [('', []), ('', [])]
1093
989
        current_block = self._dirblocks[0][1]
1114
1010
        # The above loop leaves the "root block" entries mixed with the
1115
1011
        # "contents-of-root block". But we don't want an if check on
1116
1012
        # all entries, so instead we just fix it up here.
1117
 
        if self._dirblocks[1] != ('', []):
1118
 
            raise ValueError("bad dirblock start %r" % (self._dirblocks[1],))
 
1013
        assert self._dirblocks[1] == ('', [])
1119
1014
        root_block = []
1120
1015
        contents_of_root_block = []
1121
1016
        for entry in self._dirblocks[0][1]:
1126
1021
        self._dirblocks[0] = ('', root_block)
1127
1022
        self._dirblocks[1] = ('', contents_of_root_block)
1128
1023
 
1129
 
    def _entries_for_path(self, path):
1130
 
        """Return a list with all the entries that match path for all ids."""
1131
 
        dirname, basename = os.path.split(path)
1132
 
        key = (dirname, basename, '')
1133
 
        block_index, present = self._find_block_index_from_key(key)
1134
 
        if not present:
1135
 
            # the block which should contain path is absent.
1136
 
            return []
1137
 
        result = []
1138
 
        block = self._dirblocks[block_index][1]
1139
 
        entry_index, _ = self._find_entry_index(key, block)
1140
 
        # we may need to look at multiple entries at this path: walk while the specific_files match.
1141
 
        while (entry_index < len(block) and
1142
 
            block[entry_index][0][0:2] == key[0:2]):
1143
 
            result.append(block[entry_index])
1144
 
            entry_index += 1
1145
 
        return result
1146
 
 
1147
1024
    def _entry_to_line(self, entry):
1148
1025
        """Serialize entry to a NULL delimited line ready for _get_output_lines.
1149
1026
 
1217
1094
        # one to use it. we use _right here because there are two
1218
1095
        # '' blocks - the root, and the contents of root
1219
1096
        # we always have a minimum of 2 in self._dirblocks: root and
1220
 
        # root-contents, and for '', we get 2 back, so this is
 
1097
        # root-contents, and for '', we get 2 back, so this is 
1221
1098
        # simple and correct:
1222
1099
        present = (block_index < len(self._dirblocks) and
1223
1100
            self._dirblocks[block_index][0] == key[0])
1252
1129
        return entry_index, present
1253
1130
 
1254
1131
    @staticmethod
1255
 
    def from_tree(tree, dir_state_filename, sha1_provider=None):
 
1132
    def from_tree(tree, dir_state_filename):
1256
1133
        """Create a dirstate from a bzr Tree.
1257
1134
 
1258
1135
        :param tree: The tree which should provide parent information and
1259
1136
            inventory ids.
1260
 
        :param sha1_provider: an object meeting the SHA1Provider interface.
1261
 
            If None, a DefaultSHA1Provider is used.
1262
1137
        :return: a DirState object which is currently locked for writing.
1263
1138
            (it was locked by DirState.initialize)
1264
1139
        """
1265
 
        result = DirState.initialize(dir_state_filename,
1266
 
            sha1_provider=sha1_provider)
 
1140
        result = DirState.initialize(dir_state_filename)
1267
1141
        try:
1268
1142
            tree.lock_read()
1269
1143
            try:
1287
1161
            raise
1288
1162
        return result
1289
1163
 
1290
 
    def update_by_delta(self, delta):
1291
 
        """Apply an inventory delta to the dirstate for tree 0
1292
 
 
1293
 
        This is the workhorse for apply_inventory_delta in dirstate based
1294
 
        trees.
1295
 
 
1296
 
        :param delta: An inventory delta.  See Inventory.apply_delta for
1297
 
            details.
1298
 
        """
1299
 
        self._read_dirblocks_if_needed()
1300
 
        encode = cache_utf8.encode
1301
 
        insertions = {}
1302
 
        removals = {}
1303
 
        # Accumulate parent references (path_utf8, id), to check for parentless
1304
 
        # items or items placed under files/links/tree-references. We get
1305
 
        # references from every item in the delta that is not a deletion and
1306
 
        # is not itself the root.
1307
 
        parents = set()
1308
 
        # Added ids must not be in the dirstate already. This set holds those
1309
 
        # ids.
1310
 
        new_ids = set()
1311
 
        # This loop transforms the delta to single atomic operations that can
1312
 
        # be executed and validated.
1313
 
        for old_path, new_path, file_id, inv_entry in sorted(
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
 
            reverse=True):
1320
 
            if (file_id in insertions) or (file_id in removals):
1321
 
                raise errors.InconsistentDelta(old_path or new_path, file_id,
1322
 
                    "repeated file_id")
1323
 
            if old_path is not None:
1324
 
                old_path = old_path.encode('utf-8')
1325
 
                removals[file_id] = old_path
1326
 
            else:
1327
 
                new_ids.add(file_id)
1328
 
            if new_path is not None:
1329
 
                if inv_entry is None:
1330
 
                    raise errors.InconsistentDelta(new_path, file_id,
1331
 
                        "new_path with no entry")
1332
 
                new_path = new_path.encode('utf-8')
1333
 
                dirname_utf8, basename = osutils.split(new_path)
1334
 
                if basename:
1335
 
                    parents.add((dirname_utf8, inv_entry.parent_id))
1336
 
                key = (dirname_utf8, basename, file_id)
1337
 
                minikind = DirState._kind_to_minikind[inv_entry.kind]
1338
 
                if minikind == 't':
1339
 
                    fingerprint = inv_entry.reference_revision or ''
1340
 
                else:
1341
 
                    fingerprint = ''
1342
 
                insertions[file_id] = (key, minikind, inv_entry.executable,
1343
 
                                       fingerprint, new_path)
1344
 
            # Transform moves into delete+add pairs
1345
 
            if None not in (old_path, new_path):
1346
 
                for child in self._iter_child_entries(0, old_path):
1347
 
                    if child[0][2] in insertions or child[0][2] in removals:
1348
 
                        continue
1349
 
                    child_dirname = child[0][0]
1350
 
                    child_basename = child[0][1]
1351
 
                    minikind = child[1][0][0]
1352
 
                    fingerprint = child[1][0][4]
1353
 
                    executable = child[1][0][3]
1354
 
                    old_child_path = osutils.pathjoin(child_dirname,
1355
 
                                                      child_basename)
1356
 
                    removals[child[0][2]] = old_child_path
1357
 
                    child_suffix = child_dirname[len(old_path):]
1358
 
                    new_child_dirname = (new_path + child_suffix)
1359
 
                    key = (new_child_dirname, child_basename, child[0][2])
1360
 
                    new_child_path = osutils.pathjoin(new_child_dirname,
1361
 
                                                      child_basename)
1362
 
                    insertions[child[0][2]] = (key, minikind, executable,
1363
 
                                               fingerprint, new_child_path)
1364
 
        self._check_delta_ids_absent(new_ids, delta, 0)
1365
 
        try:
1366
 
            self._apply_removals(removals.iteritems())
1367
 
            self._apply_insertions(insertions.values())
1368
 
            # Validate parents
1369
 
            self._after_delta_check_parents(parents, 0)
1370
 
        except errors.BzrError, e:
1371
 
            self._changes_aborted = True
1372
 
            if 'integrity error' not in str(e):
1373
 
                raise
1374
 
            # _get_entry raises BzrError when a request is inconsistent; we
1375
 
            # want such errors to be shown as InconsistentDelta - and that 
1376
 
            # fits the behaviour we trigger.
1377
 
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1378
 
 
1379
 
    def _apply_removals(self, removals):
1380
 
        for file_id, path in sorted(removals, reverse=True,
1381
 
            key=operator.itemgetter(1)):
1382
 
            dirname, basename = osutils.split(path)
1383
 
            block_i, entry_i, d_present, f_present = \
1384
 
                self._get_block_entry_index(dirname, basename, 0)
1385
 
            try:
1386
 
                entry = self._dirblocks[block_i][1][entry_i]
1387
 
            except IndexError:
1388
 
                self._changes_aborted = True
1389
 
                raise errors.InconsistentDelta(path, file_id,
1390
 
                    "Wrong path for old path.")
1391
 
            if not f_present or entry[1][0][0] in 'ar':
1392
 
                self._changes_aborted = True
1393
 
                raise errors.InconsistentDelta(path, file_id,
1394
 
                    "Wrong path for old path.")
1395
 
            if file_id != entry[0][2]:
1396
 
                self._changes_aborted = True
1397
 
                raise errors.InconsistentDelta(path, file_id,
1398
 
                    "Attempt to remove path has wrong id - found %r."
1399
 
                    % entry[0][2])
1400
 
            self._make_absent(entry)
1401
 
            # See if we have a malformed delta: deleting a directory must not
1402
 
            # leave crud behind. This increases the number of bisects needed
1403
 
            # substantially, but deletion or renames of large numbers of paths
1404
 
            # is rare enough it shouldn't be an issue (famous last words?) RBC
1405
 
            # 20080730.
1406
 
            block_i, entry_i, d_present, f_present = \
1407
 
                self._get_block_entry_index(path, '', 0)
1408
 
            if d_present:
1409
 
                # The dir block is still present in the dirstate; this could
1410
 
                # be due to it being in a parent tree, or a corrupt delta.
1411
 
                for child_entry in self._dirblocks[block_i][1]:
1412
 
                    if child_entry[1][0][0] not in ('r', 'a'):
1413
 
                        self._changes_aborted = True
1414
 
                        raise errors.InconsistentDelta(path, entry[0][2],
1415
 
                            "The file id was deleted but its children were "
1416
 
                            "not deleted.")
1417
 
 
1418
 
    def _apply_insertions(self, adds):
1419
 
        try:
1420
 
            for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1421
 
                self.update_minimal(key, minikind, executable, fingerprint,
1422
 
                                    path_utf8=path_utf8)
1423
 
        except errors.NotVersionedError:
1424
 
            self._changes_aborted = True
1425
 
            raise errors.InconsistentDelta(path_utf8.decode('utf8'), key[2],
1426
 
                "Missing parent")
1427
 
 
1428
1164
    def update_basis_by_delta(self, delta, new_revid):
1429
1165
        """Update the parents of this tree after a commit.
1430
1166
 
1472
1208
        # expanding them recursively as needed.
1473
1209
        # At the same time, to reduce interface friction we convert the input
1474
1210
        # inventory entries to dirstate.
1475
 
        root_only = ('', '')
1476
 
        # Accumulate parent references (path_utf8, id), to check for parentless
1477
 
        # items or items placed under files/links/tree-references. We get
1478
 
        # references from every item in the delta that is not a deletion and
1479
 
        # is not itself the root.
1480
 
        parents = set()
1481
 
        # Added ids must not be in the dirstate already. This set holds those
1482
 
        # ids.
1483
 
        new_ids = set()
 
1211
        
1484
1212
        for old_path, new_path, file_id, inv_entry in delta:
1485
 
            if inv_entry is not None and file_id != inv_entry.file_id:
1486
 
                raise errors.InconsistentDelta(new_path, file_id,
1487
 
                    "mismatched entry file_id %r" % inv_entry)
1488
 
            if new_path is not None:
1489
 
                if inv_entry is None:
1490
 
                    raise errors.InconsistentDelta(new_path, file_id,
1491
 
                        "new_path with no entry")
1492
 
                new_path_utf8 = encode(new_path)
1493
 
                # note the parent for validation
1494
 
                dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
1495
 
                if basename_utf8:
1496
 
                    parents.add((dirname_utf8, inv_entry.parent_id))
1497
1213
            if old_path is None:
1498
1214
                adds.append((None, encode(new_path), file_id,
1499
 
                    inv_to_entry(inv_entry), True))
1500
 
                new_ids.add(file_id)
 
1215
                    inv_to_entry(inv_entry)))
1501
1216
            elif new_path is None:
1502
 
                deletes.append((encode(old_path), None, file_id, None, True))
1503
 
            elif (old_path, new_path) != root_only:
 
1217
                deletes.append((encode(old_path), None, file_id, None))
 
1218
            elif old_path != new_path:
1504
1219
                # Renames:
1505
1220
                # Because renames must preserve their children we must have
1506
 
                # processed all relocations and removes before hand. The sort
 
1221
                # processed all reloations and removes before hand. The sort
1507
1222
                # order ensures we've examined the child paths, but we also
1508
1223
                # have to execute the removals, or the split to an add/delete
1509
1224
                # pair will result in the deleted item being reinserted, or
1515
1230
                # for 'r' items on every pass.
1516
1231
                self._update_basis_apply_deletes(deletes)
1517
1232
                deletes = []
 
1233
                new_path_utf8 = encode(new_path)
1518
1234
                # Split into an add/delete pair recursively.
1519
1235
                adds.append((None, new_path_utf8, file_id,
1520
 
                    inv_to_entry(inv_entry), False))
1521
 
                # Expunge deletes that we've seen so that deleted/renamed
1522
 
                # children of a rename directory are handled correctly.
1523
 
                new_deletes = reversed(list(self._iter_child_entries(1,
1524
 
                    encode(old_path))))
 
1236
                    inv_to_entry(inv_entry)))
1525
1237
                # Remove the current contents of the tree at orig_path, and
1526
1238
                # reinsert at the correct new path.
 
1239
                new_deletes = list(reversed(list(self._iter_child_entries(1,
 
1240
                    encode(old_path)))))
1527
1241
                for entry in new_deletes:
1528
 
                    if entry[0][0]:
1529
 
                        source_path = entry[0][0] + '/' + entry[0][1]
1530
 
                    else:
1531
 
                        source_path = entry[0][1]
1532
 
                    if new_path_utf8:
1533
 
                        target_path = new_path_utf8 + source_path[len(old_path):]
1534
 
                    else:
1535
 
                        if old_path == '':
1536
 
                            raise AssertionError("cannot rename directory to"
1537
 
                                " itself")
1538
 
                        target_path = source_path[len(old_path) + 1:]
1539
 
                    adds.append((None, target_path, entry[0][2], entry[1][1], False))
1540
 
                    deletes.append(
1541
 
                        (source_path, target_path, entry[0][2], None, False))
1542
 
                deletes.append(
1543
 
                    (encode(old_path), new_path, file_id, None, False))
 
1242
                    source_path = '/'.join(entry[0][0:2])
 
1243
                    target_path = new_path_utf8 + source_path[len(old_path):]
 
1244
                    adds.append((None, target_path, entry[0][2], entry[1][1]))
 
1245
                    deletes.append((source_path,  None, entry[0][2], None))
 
1246
                deletes.append((encode(old_path), None, file_id, None))
1544
1247
            else:
1545
 
                # changes to just the root should not require remove/insertion
1546
 
                # of everything.
1547
1248
                changes.append((encode(old_path), encode(new_path), file_id,
1548
1249
                    inv_to_entry(inv_entry)))
1549
 
        self._check_delta_ids_absent(new_ids, delta, 1)
1550
 
        try:
1551
 
            # Finish expunging deletes/first half of renames.
1552
 
            self._update_basis_apply_deletes(deletes)
1553
 
            # Reinstate second half of renames and new paths.
1554
 
            self._update_basis_apply_adds(adds)
1555
 
            # Apply in-situ changes.
1556
 
            self._update_basis_apply_changes(changes)
1557
 
            # Validate parents
1558
 
            self._after_delta_check_parents(parents, 1)
1559
 
        except errors.BzrError, e:
1560
 
            self._changes_aborted = True
1561
 
            if 'integrity error' not in str(e):
1562
 
                raise
1563
 
            # _get_entry raises BzrError when a request is inconsistent; we
1564
 
            # want such errors to be shown as InconsistentDelta - and that 
1565
 
            # fits the behaviour we trigger. Partof this is driven by dirstate
1566
 
            # only supporting deltas that turn the basis into a closer fit to
1567
 
            # the active tree.
1568
 
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1569
 
 
 
1250
 
 
1251
        self._update_basis_apply_deletes(deletes)
 
1252
        self._update_basis_apply_changes(changes)
 
1253
        self._update_basis_apply_adds(adds)
 
1254
 
 
1255
        # remove all deletes
1570
1256
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1571
1257
        self._header_state = DirState.IN_MEMORY_MODIFIED
1572
 
        self._id_index = None
1573
1258
        return
1574
1259
 
1575
 
    def _check_delta_ids_absent(self, new_ids, delta, tree_index):
1576
 
        """Check that none of the file_ids in new_ids are present in a tree."""
1577
 
        if not new_ids:
1578
 
            return
1579
 
        id_index = self._get_id_index()
1580
 
        for file_id in new_ids:
1581
 
            for key in id_index.get(file_id, ()):
1582
 
                block_i, entry_i, d_present, f_present = \
1583
 
                    self._get_block_entry_index(key[0], key[1], tree_index)
1584
 
                if not f_present:
1585
 
                    # In a different tree
1586
 
                    continue
1587
 
                entry = self._dirblocks[block_i][1][entry_i]
1588
 
                if entry[0][2] != file_id:
1589
 
                    # Different file_id, so not what we want.
1590
 
                    continue
1591
 
                # NB: No changes made before this helper is called, so no need
1592
 
                # to set the _changes_aborted flag.
1593
 
                raise errors.InconsistentDelta(
1594
 
                    ("%s/%s" % key[0:2]).decode('utf8'), file_id,
1595
 
                    "This file_id is new in the delta but already present in "
1596
 
                    "the target")
1597
 
 
1598
1260
    def _update_basis_apply_adds(self, adds):
1599
1261
        """Apply a sequence of adds to tree 1 during update_basis_by_delta.
1600
1262
 
1602
1264
        pairs.
1603
1265
 
1604
1266
        :param adds: A sequence of adds. Each add is a tuple:
1605
 
            (None, new_path_utf8, file_id, (entry_details), real_add). real_add
1606
 
            is False when the add is the second half of a remove-and-reinsert
1607
 
            pair created to handle renames and deletes.
 
1267
            (None, new_path_utf8, file_id, (entry_details))
1608
1268
        """
1609
1269
        # Adds are accumulated partly from renames, so can be in any input
1610
1270
        # order - sort it.
1611
1271
        adds.sort()
1612
1272
        # adds is now in lexographic order, which places all parents before
1613
1273
        # their children, so we can process it linearly.
1614
 
        absent = 'ar'
1615
 
        for old_path, new_path, file_id, new_details, real_add in adds:
 
1274
        absent = ('r', 'a')
 
1275
        for old_path, new_path, file_id, new_details in adds:
 
1276
            assert old_path is None
1616
1277
            # the entry for this file_id must be in tree 0.
1617
1278
            entry = self._get_entry(0, file_id, new_path)
1618
 
            if entry[0] is None or entry[0][2] != file_id:
1619
 
                self._changes_aborted = True
1620
 
                raise errors.InconsistentDelta(new_path, file_id,
1621
 
                    'working tree does not contain new entry')
1622
 
            if real_add and entry[1][1][0] not in absent:
1623
 
                self._changes_aborted = True
1624
 
                raise errors.InconsistentDelta(new_path, file_id,
1625
 
                    'The entry was considered to be a genuinely new record,'
1626
 
                    ' but there was already an old record for it.')
 
1279
            if entry[0][2] != file_id:
 
1280
                raise errors.BzrError('dirstate: cannot apply delta, working'
 
1281
                    ' tree does not contain new entry %r %r' %
 
1282
                    (new_path, file_id))
 
1283
            if entry[1][1][0] not in absent:
 
1284
                raise errors.BzrError('dirstate: inconsistent delta, with '
 
1285
                    'tree 0. %r %r' % (new_path, file_id))
1627
1286
            # We don't need to update the target of an 'r' because the handling
1628
1287
            # of renames turns all 'r' situations into a delete at the original
1629
1288
            # location.
1635
1294
        :param adds: A sequence of changes. Each change is a tuple:
1636
1295
            (path_utf8, path_utf8, file_id, (entry_details))
1637
1296
        """
1638
 
        absent = 'ar'
 
1297
        absent = ('a', 'r')
1639
1298
        for old_path, new_path, file_id, new_details in changes:
 
1299
            assert old_path == new_path
1640
1300
            # the entry for this file_id must be in tree 0.
1641
1301
            entry = self._get_entry(0, file_id, new_path)
1642
 
            if entry[0] is None or entry[0][2] != file_id:
1643
 
                self._changes_aborted = True
1644
 
                raise errors.InconsistentDelta(new_path, file_id,
1645
 
                    'working tree does not contain new entry')
 
1302
            if entry[0][2] != file_id:
 
1303
                raise errors.BzrError('dirstate: cannot apply delta, working'
 
1304
                    ' tree does not contain new entry %r %r' %
 
1305
                    (new_path, file_id))
1646
1306
            if (entry[1][0][0] in absent or
1647
1307
                entry[1][1][0] in absent):
1648
 
                self._changes_aborted = True
1649
 
                raise errors.InconsistentDelta(new_path, file_id,
1650
 
                    'changed considered absent')
 
1308
                raise errors.BzrError('dirstate: inconsistent delta, with '
 
1309
                    'tree 0. %r %r' % (new_path, file_id))
1651
1310
            entry[1][1] = new_details
1652
1311
 
1653
1312
    def _update_basis_apply_deletes(self, deletes):
1656
1315
        They may be deletes, or renames that have been split into add/delete
1657
1316
        pairs.
1658
1317
 
1659
 
        :param deletes: A sequence of deletes. Each delete is a tuple:
1660
 
            (old_path_utf8, new_path_utf8, file_id, None, real_delete).
1661
 
            real_delete is True when the desired outcome is an actual deletion
1662
 
            rather than the rename handling logic temporarily deleting a path
1663
 
            during the replacement of a parent.
 
1318
        :param adds: A sequence of deletes. Each delete is a tuple:
 
1319
            (old_path_utf8, None, file_id, None)
1664
1320
        """
1665
 
        null = DirState.NULL_PARENT_DETAILS
1666
 
        for old_path, new_path, file_id, _, real_delete in deletes:
1667
 
            if real_delete != (new_path is None):
1668
 
                self._changes_aborted = True
1669
 
                raise AssertionError("bad delete delta")
 
1321
        absent = ('r', 'a')
 
1322
        # Deletes are accumulated in lexographical order.
 
1323
        for old_path, new_path, file_id, _ in deletes:
 
1324
            assert new_path is None
1670
1325
            # the entry for this file_id must be in tree 1.
1671
1326
            dirname, basename = osutils.split(old_path)
1672
1327
            block_index, entry_index, dir_present, file_present = \
1673
1328
                self._get_block_entry_index(dirname, basename, 1)
1674
1329
            if not file_present:
1675
 
                self._changes_aborted = True
1676
 
                raise errors.InconsistentDelta(old_path, file_id,
1677
 
                    'basis tree does not contain removed entry')
 
1330
                raise errors.BzrError('dirstate: cannot apply delta, basis'
 
1331
                    ' tree does not contain new entry %r %r' %
 
1332
                    (old_path, file_id))
1678
1333
            entry = self._dirblocks[block_index][1][entry_index]
1679
1334
            if entry[0][2] != file_id:
1680
 
                self._changes_aborted = True
1681
 
                raise errors.InconsistentDelta(old_path, file_id,
1682
 
                    'mismatched file_id in tree 1')
1683
 
            if real_delete:
1684
 
                if entry[1][0][0] != 'a':
1685
 
                    self._changes_aborted = True
1686
 
                    raise errors.InconsistentDelta(old_path, file_id,
1687
 
                            'This was marked as a real delete, but the WT state'
1688
 
                            ' claims that it still exists and is versioned.')
1689
 
                del self._dirblocks[block_index][1][entry_index]
1690
 
            else:
1691
 
                if entry[1][0][0] == 'a':
1692
 
                    self._changes_aborted = True
1693
 
                    raise errors.InconsistentDelta(old_path, file_id,
1694
 
                        'The entry was considered a rename, but the source path'
1695
 
                        ' is marked as absent.')
1696
 
                    # For whatever reason, we were asked to rename an entry
1697
 
                    # that was originally marked as deleted. This could be
1698
 
                    # because we are renaming the parent directory, and the WT
1699
 
                    # current state has the file marked as deleted.
1700
 
                elif entry[1][0][0] == 'r':
1701
 
                    # implement the rename
1702
 
                    del self._dirblocks[block_index][1][entry_index]
1703
 
                else:
1704
 
                    # it is being resurrected here, so blank it out temporarily.
1705
 
                    self._dirblocks[block_index][1][entry_index][1][1] = null
1706
 
 
1707
 
    def _after_delta_check_parents(self, parents, index):
1708
 
        """Check that parents required by the delta are all intact.
1709
 
        
1710
 
        :param parents: An iterable of (path_utf8, file_id) tuples which are
1711
 
            required to be present in tree 'index' at path_utf8 with id file_id
1712
 
            and be a directory.
1713
 
        :param index: The column in the dirstate to check for parents in.
1714
 
        """
1715
 
        for dirname_utf8, file_id in parents:
1716
 
            # Get the entry - the ensures that file_id, dirname_utf8 exists and
1717
 
            # has the right file id.
1718
 
            entry = self._get_entry(index, file_id, dirname_utf8)
1719
 
            if entry[1] is None:
1720
 
                self._changes_aborted = True
1721
 
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1722
 
                    file_id, "This parent is not present.")
1723
 
            # Parents of things must be directories
1724
 
            if entry[1][index][0] != 'd':
1725
 
                self._changes_aborted = True
1726
 
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1727
 
                    file_id, "This parent is not a directory.")
1728
 
 
1729
 
    def _observed_sha1(self, entry, sha1, stat_value,
1730
 
        _stat_to_minikind=_stat_to_minikind, _pack_stat=pack_stat):
1731
 
        """Note the sha1 of a file.
1732
 
 
1733
 
        :param entry: The entry the sha1 is for.
1734
 
        :param sha1: The observed sha1.
1735
 
        :param stat_value: The os.lstat for the file.
 
1335
                raise errors.BzrError('mismatched file_id in tree 1 %r %r' %
 
1336
                    (old_path, file_id))
 
1337
            if entry[1][0][0] not in absent:
 
1338
                raise errors.BzrError('dirstate: inconsistent delta, with '
 
1339
                    'tree 0. %r %r' % (old_path, file_id))
 
1340
            del self._dirblocks[block_index][1][entry_index]
 
1341
 
 
1342
    def update_entry(self, entry, abspath, stat_value,
 
1343
                     _stat_to_minikind=_stat_to_minikind,
 
1344
                     _pack_stat=pack_stat):
 
1345
        """Update the entry based on what is actually on disk.
 
1346
 
 
1347
        :param entry: This is the dirblock entry for the file in question.
 
1348
        :param abspath: The path on disk for this file.
 
1349
        :param stat_value: (optional) if we already have done a stat on the
 
1350
            file, re-use it.
 
1351
        :return: The sha1 hexdigest of the file (40 bytes) or link target of a
 
1352
                symlink.
1736
1353
        """
1737
1354
        try:
1738
1355
            minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
1740
1357
            # Unhandled kind
1741
1358
            return None
1742
1359
        packed_stat = _pack_stat(stat_value)
 
1360
        (saved_minikind, saved_link_or_sha1, saved_file_size,
 
1361
         saved_executable, saved_packed_stat) = entry[1][0]
 
1362
 
 
1363
        if (minikind == saved_minikind
 
1364
            and packed_stat == saved_packed_stat):
 
1365
            # The stat hasn't changed since we saved, so we can re-use the
 
1366
            # saved sha hash.
 
1367
            if minikind == 'd':
 
1368
                return None
 
1369
 
 
1370
            # size should also be in packed_stat
 
1371
            if saved_file_size == stat_value.st_size:
 
1372
                return saved_link_or_sha1
 
1373
 
 
1374
        # If we have gotten this far, that means that we need to actually
 
1375
        # process this entry.
 
1376
        link_or_sha1 = None
1743
1377
        if minikind == 'f':
1744
 
            if self._cutoff_time is None:
1745
 
                self._sha_cutoff_time()
1746
 
            if (stat_value.st_mtime < self._cutoff_time
1747
 
                and stat_value.st_ctime < self._cutoff_time):
1748
 
                entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
1749
 
                               packed_stat)
1750
 
                self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1378
            link_or_sha1 = self._sha1_file(abspath)
 
1379
            executable = self._is_executable(stat_value.st_mode,
 
1380
                                             saved_executable)
 
1381
            if self._cutoff_time is None:
 
1382
                self._sha_cutoff_time()
 
1383
            if (stat_value.st_mtime < self._cutoff_time
 
1384
                and stat_value.st_ctime < self._cutoff_time):
 
1385
                entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
 
1386
                               executable, packed_stat)
 
1387
            else:
 
1388
                entry[1][0] = ('f', '', stat_value.st_size,
 
1389
                               executable, DirState.NULLSTAT)
 
1390
        elif minikind == 'd':
 
1391
            link_or_sha1 = None
 
1392
            entry[1][0] = ('d', '', 0, False, packed_stat)
 
1393
            if saved_minikind != 'd':
 
1394
                # This changed from something into a directory. Make sure we
 
1395
                # have a directory block for it. This doesn't happen very
 
1396
                # often, so this doesn't have to be super fast.
 
1397
                block_index, entry_index, dir_present, file_present = \
 
1398
                    self._get_block_entry_index(entry[0][0], entry[0][1], 0)
 
1399
                self._ensure_block(block_index, entry_index,
 
1400
                                   osutils.pathjoin(entry[0][0], entry[0][1]))
 
1401
        elif minikind == 'l':
 
1402
            link_or_sha1 = self._read_link(abspath, saved_link_or_sha1)
 
1403
            if self._cutoff_time is None:
 
1404
                self._sha_cutoff_time()
 
1405
            if (stat_value.st_mtime < self._cutoff_time
 
1406
                and stat_value.st_ctime < self._cutoff_time):
 
1407
                entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
 
1408
                               False, packed_stat)
 
1409
            else:
 
1410
                entry[1][0] = ('l', '', stat_value.st_size,
 
1411
                               False, DirState.NULLSTAT)
 
1412
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1413
        return link_or_sha1
1751
1414
 
1752
1415
    def _sha_cutoff_time(self):
1753
1416
        """Return cutoff time.
1770
1433
        # when -Dhashcache is turned on, this is monkey-patched in to log
1771
1434
        # file reads
1772
1435
        trace.mutter("dirstate sha1 " + abspath)
1773
 
        return self._sha1_provider.sha1(abspath)
 
1436
        return osutils.sha_file_by_name(abspath)
1774
1437
 
1775
1438
    def _is_executable(self, mode, old_executable):
1776
1439
        """Is this file executable?"""
1789
1452
        #       already in memory. However, this really needs to be done at a
1790
1453
        #       higher level, because there either won't be anything on disk,
1791
1454
        #       or the thing on disk will be a file.
1792
 
        fs_encoding = osutils._fs_enc
1793
 
        if isinstance(abspath, unicode):
1794
 
            # abspath is defined as the path to pass to lstat. readlink is
1795
 
            # buggy in python < 2.6 (it doesn't encode unicode path into FS
1796
 
            # encoding), so we need to encode ourselves knowing that unicode
1797
 
            # paths are produced by UnicodeDirReader on purpose.
1798
 
            abspath = abspath.encode(fs_encoding)
1799
 
        target = os.readlink(abspath)
1800
 
        if fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968'):
1801
 
            # Change encoding if needed
1802
 
            target = target.decode(fs_encoding).encode('UTF-8')
1803
 
        return target
 
1455
        return os.readlink(abspath)
1804
1456
 
1805
1457
    def get_ghosts(self):
1806
1458
        """Return a list of the parent tree revision ids that are ghosts."""
1946
1598
        # linear search through entries at this path to find the one
1947
1599
        # requested.
1948
1600
        while entry_index < len(block) and block[entry_index][0][1] == basename:
1949
 
            if block[entry_index][1][tree_index][0] not in 'ar':
1950
 
                # neither absent or relocated
 
1601
            if block[entry_index][1][tree_index][0] not in \
 
1602
                       ('a', 'r'): # absent, relocated
1951
1603
                return block_index, entry_index, True, True
1952
1604
            entry_index += 1
1953
1605
        return block_index, entry_index, True, False
1954
1606
 
1955
 
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None, include_deleted=False):
 
1607
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None):
1956
1608
        """Get the dirstate entry for path in tree tree_index.
1957
1609
 
1958
1610
        If either file_id or path is supplied, it is used as the key to lookup.
1966
1618
            trees.
1967
1619
        :param fileid_utf8: A utf8 file_id to look up.
1968
1620
        :param path_utf8: An utf8 path to be looked up.
1969
 
        :param include_deleted: If True, and performing a lookup via
1970
 
            fileid_utf8 rather than path_utf8, return an entry for deleted
1971
 
            (absent) paths.
1972
1621
        :return: The dirstate entry tuple for path, or (None, None)
1973
1622
        """
1974
1623
        self._read_dirblocks_if_needed()
1975
1624
        if path_utf8 is not None:
1976
 
            if type(path_utf8) is not str:
1977
 
                raise errors.BzrError('path_utf8 is not a str: %s %r'
1978
 
                    % (type(path_utf8), path_utf8))
 
1625
            assert path_utf8.__class__ == str, ('path_utf8 is not a str: %s %s'
 
1626
                % (type(path_utf8), path_utf8))
1979
1627
            # path lookups are faster
1980
1628
            dirname, basename = osutils.split(path_utf8)
1981
1629
            block_index, entry_index, dir_present, file_present = \
1983
1631
            if not file_present:
1984
1632
                return None, None
1985
1633
            entry = self._dirblocks[block_index][1][entry_index]
1986
 
            if not (entry[0][2] and entry[1][tree_index][0] not in ('a', 'r')):
1987
 
                raise AssertionError('unversioned entry?')
 
1634
            assert entry[0][2] and entry[1][tree_index][0] not in ('a', 'r'), 'unversioned entry?!?!'
1988
1635
            if fileid_utf8:
1989
1636
                if entry[0][2] != fileid_utf8:
1990
 
                    self._changes_aborted = True
1991
1637
                    raise errors.BzrError('integrity error ? : mismatching'
1992
1638
                                          ' tree_index, file_id and path')
1993
1639
            return entry
1994
1640
        else:
1995
 
            possible_keys = self._get_id_index().get(fileid_utf8, ())
 
1641
            assert fileid_utf8 is not None
 
1642
            possible_keys = self._get_id_index().get(fileid_utf8, None)
1996
1643
            if not possible_keys:
1997
1644
                return None, None
1998
1645
            for key in possible_keys:
2009
1656
                entry_index, present = self._find_entry_index(key, block)
2010
1657
                if present:
2011
1658
                    entry = self._dirblocks[block_index][1][entry_index]
2012
 
                    # TODO: We might want to assert that entry[0][2] ==
2013
 
                    #       fileid_utf8.
2014
1659
                    if entry[1][tree_index][0] in 'fdlt':
2015
 
                        # this is the result we are looking for: the
 
1660
                        # this is the result we are looking for: the  
2016
1661
                        # real home of this file_id in this tree.
2017
1662
                        return entry
2018
1663
                    if entry[1][tree_index][0] == 'a':
2019
1664
                        # there is no home for this entry in this tree
2020
 
                        if include_deleted:
2021
 
                            return entry
2022
1665
                        return None, None
2023
 
                    if entry[1][tree_index][0] != 'r':
2024
 
                        raise AssertionError(
2025
 
                            "entry %r has invalid minikind %r for tree %r" \
2026
 
                            % (entry,
2027
 
                               entry[1][tree_index][0],
2028
 
                               tree_index))
 
1666
                    assert entry[1][tree_index][0] == 'r', \
 
1667
                        "entry %r has invalid minikind %r for tree %r" \
 
1668
                        % (entry,
 
1669
                           entry[1][tree_index][0],
 
1670
                           tree_index)
2029
1671
                    real_path = entry[1][tree_index][1]
2030
1672
                    return self._get_entry(tree_index, fileid_utf8=fileid_utf8,
2031
1673
                        path_utf8=real_path)
2032
1674
            return None, None
2033
1675
 
2034
1676
    @classmethod
2035
 
    def initialize(cls, path, sha1_provider=None):
 
1677
    def initialize(cls, path):
2036
1678
        """Create a new dirstate on path.
2037
1679
 
2038
1680
        The new dirstate will be an empty tree - that is it has no parents,
2039
1681
        and only a root node - which has id ROOT_ID.
2040
1682
 
2041
1683
        :param path: The name of the file for the dirstate.
2042
 
        :param sha1_provider: an object meeting the SHA1Provider interface.
2043
 
            If None, a DefaultSHA1Provider is used.
2044
1684
        :return: A write-locked DirState object.
2045
1685
        """
2046
1686
        # This constructs a new DirState object on a path, sets the _state_file
2048
1688
        # stock empty dirstate information - a root with ROOT_ID, no children,
2049
1689
        # and no parents. Finally it calls save() to ensure that this data will
2050
1690
        # persist.
2051
 
        if sha1_provider is None:
2052
 
            sha1_provider = DefaultSHA1Provider()
2053
 
        result = cls(path, sha1_provider)
 
1691
        result = cls(path)
2054
1692
        # root dir and root dir contents with no children.
2055
1693
        empty_tree_dirblocks = [('', []), ('', [])]
2056
1694
        # a new root directory, with a NULLSTAT.
2067
1705
            raise
2068
1706
        return result
2069
1707
 
2070
 
    @staticmethod
2071
 
    def _inv_entry_to_details(inv_entry):
 
1708
    def _inv_entry_to_details(self, inv_entry):
2072
1709
        """Convert an inventory entry (from a revision tree) to state details.
2073
1710
 
2074
1711
        :param inv_entry: An inventory entry whose sha1 and link targets can be
2079
1716
        kind = inv_entry.kind
2080
1717
        minikind = DirState._kind_to_minikind[kind]
2081
1718
        tree_data = inv_entry.revision
 
1719
        assert tree_data, 'empty revision for the inv_entry %s.' % \
 
1720
            inv_entry.file_id
2082
1721
        if kind == 'directory':
2083
1722
            fingerprint = ''
2084
1723
            size = 0
2085
1724
            executable = False
2086
1725
        elif kind == 'symlink':
2087
 
            if inv_entry.symlink_target is None:
2088
 
                fingerprint = ''
2089
 
            else:
2090
 
                fingerprint = inv_entry.symlink_target.encode('utf8')
 
1726
            fingerprint = inv_entry.symlink_target or ''
2091
1727
            size = 0
2092
1728
            executable = False
2093
1729
        elif kind == 'file':
2105
1741
    def _iter_child_entries(self, tree_index, path_utf8):
2106
1742
        """Iterate over all the entries that are children of path_utf.
2107
1743
 
2108
 
        This only returns entries that are present (not in 'a', 'r') in
 
1744
        This only returns entries that are present (not in 'a', 'r') in 
2109
1745
        tree_index. tree_index data is not refreshed, so if tree 0 is used,
2110
1746
        results may differ from that obtained if paths were statted to
2111
1747
        determine what ones were directories.
2115
1751
        """
2116
1752
        pending_dirs = []
2117
1753
        next_pending_dirs = [path_utf8]
2118
 
        absent = 'ar'
 
1754
        absent = ('r', 'a')
2119
1755
        while next_pending_dirs:
2120
1756
            pending_dirs = next_pending_dirs
2121
1757
            next_pending_dirs = []
2122
1758
            for path in pending_dirs:
2123
1759
                block_index, present = self._find_block_index_from_key(
2124
1760
                    (path, '', ''))
2125
 
                if block_index == 0:
2126
 
                    block_index = 1
2127
 
                    if len(self._dirblocks) == 1:
2128
 
                        # asked for the children of the root with no other
2129
 
                        # contents.
2130
 
                        return
2131
1761
                if not present:
2132
1762
                    # children of a non-directory asked for.
2133
1763
                    continue
2137
1767
                    if kind not in absent:
2138
1768
                        yield entry
2139
1769
                    if kind == 'd':
2140
 
                        if entry[0][0]:
2141
 
                            path = entry[0][0] + '/' + entry[0][1]
2142
 
                        else:
2143
 
                            path = entry[0][1]
2144
 
                        next_pending_dirs.append(path)
2145
 
 
 
1770
                        next_pending_dirs.append('/'.join(entry[0][0:2]))
 
1771
    
2146
1772
    def _iter_entries(self):
2147
1773
        """Iterate over all the entries in the dirstate.
2148
1774
 
2155
1781
                yield entry
2156
1782
 
2157
1783
    def _get_id_index(self):
2158
 
        """Get an id index of self._dirblocks.
2159
 
        
2160
 
        This maps from file_id => [(directory, name, file_id)] entries where
2161
 
        that file_id appears in one of the trees.
2162
 
        """
 
1784
        """Get an id index of self._dirblocks."""
2163
1785
        if self._id_index is None:
2164
1786
            id_index = {}
2165
1787
            for key, tree_details in self._iter_entries():
2166
 
                self._add_to_id_index(id_index, key)
 
1788
                id_index.setdefault(key[2], set()).add(key)
2167
1789
            self._id_index = id_index
2168
1790
        return self._id_index
2169
1791
 
2170
 
    def _add_to_id_index(self, id_index, entry_key):
2171
 
        """Add this entry to the _id_index mapping."""
2172
 
        # This code used to use a set for every entry in the id_index. However,
2173
 
        # it is *rare* to have more than one entry. So a set is a large
2174
 
        # overkill. And even when we do, we won't ever have more than the
2175
 
        # number of parent trees. Which is still a small number (rarely >2). As
2176
 
        # such, we use a simple tuple, and do our own uniqueness checks. While
2177
 
        # the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2178
 
        # cause quadratic failure.
2179
 
        # TODO: This should use StaticTuple
2180
 
        file_id = entry_key[2]
2181
 
        entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2182
 
        if file_id not in id_index:
2183
 
            id_index[file_id] = static_tuple.StaticTuple(entry_key,)
2184
 
        else:
2185
 
            entry_keys = id_index[file_id]
2186
 
            if entry_key not in entry_keys:
2187
 
                id_index[file_id] = entry_keys + (entry_key,)
2188
 
 
2189
 
    def _remove_from_id_index(self, id_index, entry_key):
2190
 
        """Remove this entry from the _id_index mapping.
2191
 
 
2192
 
        It is an programming error to call this when the entry_key is not
2193
 
        already present.
2194
 
        """
2195
 
        file_id = entry_key[2]
2196
 
        entry_keys = list(id_index[file_id])
2197
 
        entry_keys.remove(entry_key)
2198
 
        id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
2199
 
 
2200
1792
    def _get_output_lines(self, lines):
2201
1793
        """Format lines for final output.
2202
1794
 
2223
1815
        return len(self._parents) - len(self._ghosts)
2224
1816
 
2225
1817
    @staticmethod
2226
 
    def on_file(path, sha1_provider=None):
2227
 
        """Construct a DirState on the file at path "path".
 
1818
    def on_file(path):
 
1819
        """Construct a DirState on the file at path path.
2228
1820
 
2229
 
        :param path: The path at which the dirstate file on disk should live.
2230
 
        :param sha1_provider: an object meeting the SHA1Provider interface.
2231
 
            If None, a DefaultSHA1Provider is used.
2232
1821
        :return: An unlocked DirState object, associated with the given path.
2233
1822
        """
2234
 
        if sha1_provider is None:
2235
 
            sha1_provider = DefaultSHA1Provider()
2236
 
        result = DirState(path, sha1_provider)
 
1823
        result = DirState(path)
2237
1824
        return result
2238
1825
 
2239
1826
    def _read_dirblocks_if_needed(self):
2240
1827
        """Read in all the dirblocks from the file if they are not in memory.
2241
 
 
 
1828
        
2242
1829
        This populates self._dirblocks, and sets self._dirblock_state to
2243
1830
        IN_MEMORY_UNMODIFIED. It is not currently ready for incremental block
2244
1831
        loading.
2259
1846
        parent_line = self._state_file.readline()
2260
1847
        info = parent_line.split('\0')
2261
1848
        num_parents = int(info[0])
 
1849
        assert num_parents == len(info)-2, 'incorrect parent info line'
2262
1850
        self._parents = info[1:-1]
 
1851
 
2263
1852
        ghost_line = self._state_file.readline()
2264
1853
        info = ghost_line.split('\0')
2265
1854
        num_ghosts = int(info[1])
 
1855
        assert num_ghosts == len(info)-3, 'incorrect ghost info line'
2266
1856
        self._ghosts = info[2:-1]
2267
1857
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
2268
1858
        self._end_of_header = self._state_file.tell()
2285
1875
        and their ids. Followed by a newline.
2286
1876
        """
2287
1877
        header = self._state_file.readline()
2288
 
        if header != DirState.HEADER_FORMAT_3:
2289
 
            raise errors.BzrError(
2290
 
                'invalid header line: %r' % (header,))
 
1878
        assert header == DirState.HEADER_FORMAT_3, \
 
1879
            'invalid header line: %r' % (header,)
2291
1880
        crc_line = self._state_file.readline()
2292
 
        if not crc_line.startswith('crc32: '):
2293
 
            raise errors.BzrError('missing crc32 checksum: %r' % crc_line)
 
1881
        assert crc_line.startswith('crc32: '), 'missing crc32 checksum'
2294
1882
        self.crc_expected = int(crc_line[len('crc32: '):-1])
2295
1883
        num_entries_line = self._state_file.readline()
2296
 
        if not num_entries_line.startswith('num_entries: '):
2297
 
            raise errors.BzrError('missing num_entries line')
 
1884
        assert num_entries_line.startswith('num_entries: '), 'missing num_entries line'
2298
1885
        self._num_entries = int(num_entries_line[len('num_entries: '):-1])
2299
1886
 
2300
 
    def sha1_from_stat(self, path, stat_result, _pack_stat=pack_stat):
2301
 
        """Find a sha1 given a stat lookup."""
2302
 
        return self._get_packed_stat_index().get(_pack_stat(stat_result), None)
2303
 
 
2304
 
    def _get_packed_stat_index(self):
2305
 
        """Get a packed_stat index of self._dirblocks."""
2306
 
        if self._packed_stat_index is None:
2307
 
            index = {}
2308
 
            for key, tree_details in self._iter_entries():
2309
 
                if tree_details[0][0] == 'f':
2310
 
                    index[tree_details[0][4]] = tree_details[0][1]
2311
 
            self._packed_stat_index = index
2312
 
        return self._packed_stat_index
2313
 
 
2314
1887
    def save(self):
2315
1888
        """Save any pending changes created during this session.
2316
1889
 
2325
1898
        start over, to allow for fine grained read lock duration, so 'status'
2326
1899
        wont block 'commit' - for example.
2327
1900
        """
2328
 
        if self._changes_aborted:
2329
 
            # Should this be a warning? For now, I'm expecting that places that
2330
 
            # mark it inconsistent will warn, making a warning here redundant.
2331
 
            trace.mutter('Not saving DirState because '
2332
 
                    '_changes_aborted is set.')
2333
 
            return
2334
1901
        if (self._header_state == DirState.IN_MEMORY_MODIFIED or
2335
1902
            self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
2336
1903
 
2369
1936
 
2370
1937
        :param parent_ids: A list of parent tree revision ids.
2371
1938
        :param dirblocks: A list containing one tuple for each directory in the
2372
 
            tree. Each tuple contains the directory path and a list of entries
 
1939
            tree. Each tuple contains the directory path and a list of entries 
2373
1940
            found in that directory.
2374
1941
        """
2375
1942
        # our memory copy is now authoritative.
2378
1945
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2379
1946
        self._parents = list(parent_ids)
2380
1947
        self._id_index = None
2381
 
        self._packed_stat_index = None
2382
1948
 
2383
1949
    def set_path_id(self, path, new_id):
2384
1950
        """Change the id of path to new_id in the current working tree.
2388
1954
        :param new_id: The new id to assign to the path. This must be a utf8
2389
1955
            file id (not unicode, and not None).
2390
1956
        """
 
1957
        assert new_id.__class__ == str, \
 
1958
            "path_id %r is not a plain string" % (new_id,)
2391
1959
        self._read_dirblocks_if_needed()
2392
1960
        if len(path):
2393
1961
            # TODO: logic not written
2402
1970
        self.update_minimal(('', '', new_id), 'd',
2403
1971
            path_utf8='', packed_stat=entry[1][0][4])
2404
1972
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1973
        if self._id_index is not None:
 
1974
            self._id_index.setdefault(new_id, set()).add(entry[0])
2405
1975
 
2406
1976
    def set_parent_trees(self, trees, ghosts):
2407
1977
        """Set the parent trees for the dirstate.
2408
1978
 
2409
1979
        :param trees: A list of revision_id, tree tuples. tree must be provided
2410
 
            even if the revision_id refers to a ghost: supply an empty tree in
 
1980
            even if the revision_id refers to a ghost: supply an empty tree in 
2411
1981
            this case.
2412
1982
        :param ghosts: A list of the revision_ids that are ghosts at the time
2413
1983
            of setting.
2414
 
        """
2415
 
        # TODO: generate a list of parent indexes to preserve to save
 
1984
        """ 
 
1985
        # TODO: generate a list of parent indexes to preserve to save 
2416
1986
        # processing specific parent trees. In the common case one tree will
2417
1987
        # be preserved - the left most parent.
2418
1988
        # TODO: if the parent tree is a dirstate, we might want to walk them
2423
1993
        # map and then walk the new parent trees only, mapping them into the
2424
1994
        # dirstate. Walk the dirstate at the same time to remove unreferenced
2425
1995
        # entries.
2426
 
        # for now:
2427
 
        # sketch: loop over all entries in the dirstate, cherry picking
 
1996
        # for now: 
 
1997
        # sketch: loop over all entries in the dirstate, cherry picking 
2428
1998
        # entries from the parent trees, if they are not ghost trees.
2429
1999
        # after we finish walking the dirstate, all entries not in the dirstate
2430
2000
        # are deletes, so we want to append them to the end as per the design
2435
2005
        #   links. We dont't trivially use the inventory from other trees
2436
2006
        #   because this leads to either double touching, or to accessing
2437
2007
        #   missing keys,
2438
 
        # - find other keys containing a path
2439
 
        # We accumulate each entry via this dictionary, including the root
 
2008
        # - find other keys containing a path 
 
2009
        # We accumulate each entry via this dictionary, including the root 
2440
2010
        by_path = {}
2441
2011
        id_index = {}
2442
2012
        # we could do parallel iterators, but because file id data may be
2446
2016
        # parent, but for now the common cases are adding a new parent (merge),
2447
2017
        # and replacing completely (commit), and commit is more common: so
2448
2018
        # optimise merge later.
2449
 
 
 
2019
        
2450
2020
        # ---- start generation of full tree mapping data
2451
2021
        # what trees should we use?
2452
2022
        parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
2453
 
        # how many trees do we end up with
 
2023
        # how many trees do we end up with 
2454
2024
        parent_count = len(parent_trees)
2455
2025
 
2456
2026
        # one: the current tree
2457
2027
        for entry in self._iter_entries():
2458
2028
            # skip entries not in the current tree
2459
 
            if entry[1][0][0] in 'ar': # absent, relocated
 
2029
            if entry[1][0][0] in ('a', 'r'): # absent, relocated
2460
2030
                continue
2461
2031
            by_path[entry[0]] = [entry[1][0]] + \
2462
2032
                [DirState.NULL_PARENT_DETAILS] * parent_count
2463
 
            # TODO: Possibly inline this, since we know it isn't present yet
2464
 
            #       id_index[entry[0][2]] = (entry[0],)
2465
 
            self._add_to_id_index(id_index, entry[0])
2466
 
 
 
2033
            id_index[entry[0][2]] = set([entry[0]])
 
2034
        
2467
2035
        # now the parent trees:
2468
2036
        for tree_index, tree in enumerate(parent_trees):
2469
2037
            # the index is off by one, adjust it.
2483
2051
                # avoid checking all known paths for the id when generating a
2484
2052
                # new entry at this path: by adding the id->path mapping last,
2485
2053
                # all the mappings are valid and have correct relocation
2486
 
                # records where needed.
 
2054
                # records where needed. 
2487
2055
                file_id = entry.file_id
2488
2056
                path_utf8 = path.encode('utf8')
2489
2057
                dirname, basename = osutils.split(path_utf8)
2490
2058
                new_entry_key = (dirname, basename, file_id)
2491
2059
                # tree index consistency: All other paths for this id in this tree
2492
2060
                # index must point to the correct path.
2493
 
                for entry_key in id_index.get(file_id, ()):
 
2061
                for entry_key in id_index.setdefault(file_id, set()):
2494
2062
                    # TODO:PROFILING: It might be faster to just update
2495
2063
                    # rather than checking if we need to, and then overwrite
2496
2064
                    # the one we are located at.
2500
2068
                        # This is the vertical axis in the matrix, all pointing
2501
2069
                        # to the real path.
2502
2070
                        by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2503
 
                # by path consistency: Insert into an existing path record (trivial), or
 
2071
                # by path consistency: Insert into an existing path record (trivial), or 
2504
2072
                # add a new one with relocation pointers for the other tree indexes.
2505
 
                entry_keys = id_index.get(file_id, ())
2506
 
                if new_entry_key in entry_keys:
 
2073
                if new_entry_key in id_index[file_id]:
2507
2074
                    # there is already an entry where this data belongs, just insert it.
2508
2075
                    by_path[new_entry_key][tree_index] = \
2509
2076
                        self._inv_entry_to_details(entry)
2514
2081
                    new_details = []
2515
2082
                    for lookup_index in xrange(tree_index):
2516
2083
                        # boundary case: this is the first occurence of file_id
2517
 
                        # so there are no id_indexes, possibly take this out of
 
2084
                        # so there are no id_indexs, possibly take this out of
2518
2085
                        # the loop?
2519
 
                        if not len(entry_keys):
 
2086
                        if not len(id_index[file_id]):
2520
2087
                            new_details.append(DirState.NULL_PARENT_DETAILS)
2521
2088
                        else:
2522
2089
                            # grab any one entry, use it to find the right path.
2523
 
                            # TODO: optimise this to reduce memory use in highly
 
2090
                            # TODO: optimise this to reduce memory use in highly 
2524
2091
                            # fragmented situations by reusing the relocation
2525
2092
                            # records.
2526
 
                            a_key = iter(entry_keys).next()
 
2093
                            a_key = iter(id_index[file_id]).next()
2527
2094
                            if by_path[a_key][lookup_index][0] in ('r', 'a'):
2528
2095
                                # its a pointer or missing statement, use it as is.
2529
2096
                                new_details.append(by_path[a_key][lookup_index])
2534
2101
                    new_details.append(self._inv_entry_to_details(entry))
2535
2102
                    new_details.extend(new_location_suffix)
2536
2103
                    by_path[new_entry_key] = new_details
2537
 
                    self._add_to_id_index(id_index, new_entry_key)
 
2104
                    id_index[file_id].add(new_entry_key)
2538
2105
        # --- end generation of full tree mappings
2539
2106
 
2540
2107
        # sort and output all the entries
2559
2126
        return sorted(entry_list, key=_key)
2560
2127
 
2561
2128
    def set_state_from_inventory(self, new_inv):
2562
 
        """Set new_inv as the current state.
 
2129
        """Set new_inv as the current state. 
2563
2130
 
2564
2131
        This API is called by tree transform, and will usually occur with
2565
2132
        existing parent trees.
2569
2136
        if 'evil' in debug.debug_flags:
2570
2137
            trace.mutter_callsite(1,
2571
2138
                "set_state_from_inventory called; please mutate the tree instead")
2572
 
        tracing = 'dirstate' in debug.debug_flags
2573
 
        if tracing:
2574
 
            trace.mutter("set_state_from_inventory trace:")
2575
2139
        self._read_dirblocks_if_needed()
2576
2140
        # sketch:
2577
 
        # Two iterators: current data and new data, both in dirblock order.
 
2141
        # Two iterators: current data and new data, both in dirblock order. 
2578
2142
        # We zip them together, which tells about entries that are new in the
2579
2143
        # inventory, or removed in the inventory, or present in both and
2580
 
        # possibly changed.
 
2144
        # possibly changed.  
2581
2145
        #
2582
2146
        # You might think we could just synthesize a new dirstate directly
2583
2147
        # since we're processing it in the right order.  However, we need to
2586
2150
        new_iterator = new_inv.iter_entries_by_dir()
2587
2151
        # we will be modifying the dirstate, so we need a stable iterator. In
2588
2152
        # future we might write one, for now we just clone the state into a
2589
 
        # list using a copy so that we see every original item and don't have
2590
 
        # to adjust the position when items are inserted or deleted in the
2591
 
        # underlying dirstate.
 
2153
        # list - which is a shallow copy.
2592
2154
        old_iterator = iter(list(self._iter_entries()))
2593
2155
        # both must have roots so this is safe:
2594
2156
        current_new = new_iterator.next()
2600
2162
                return None
2601
2163
        while current_new or current_old:
2602
2164
            # skip entries in old that are not really there
2603
 
            if current_old and current_old[1][0][0] in 'ar':
 
2165
            if current_old and current_old[1][0][0] in ('r', 'a'):
2604
2166
                # relocated or absent
2605
2167
                current_old = advance(old_iterator)
2606
2168
                continue
2628
2190
            # we make both end conditions explicit
2629
2191
            if not current_old:
2630
2192
                # old is finished: insert current_new into the state.
2631
 
                if tracing:
2632
 
                    trace.mutter("Appending from new '%s'.",
2633
 
                        new_path_utf8.decode('utf8'))
2634
2193
                self.update_minimal(new_entry_key, current_new_minikind,
2635
2194
                    executable=current_new[1].executable,
2636
 
                    path_utf8=new_path_utf8, fingerprint=fingerprint,
2637
 
                    fullscan=True)
 
2195
                    path_utf8=new_path_utf8, fingerprint=fingerprint)
2638
2196
                current_new = advance(new_iterator)
2639
2197
            elif not current_new:
2640
2198
                # new is finished
2641
 
                if tracing:
2642
 
                    trace.mutter("Truncating from old '%s/%s'.",
2643
 
                        current_old[0][0].decode('utf8'),
2644
 
                        current_old[0][1].decode('utf8'))
2645
2199
                self._make_absent(current_old)
2646
2200
                current_old = advance(old_iterator)
2647
2201
            elif new_entry_key == current_old[0]:
2654
2208
                # kind has changed.
2655
2209
                if (current_old[1][0][3] != current_new[1].executable or
2656
2210
                    current_old[1][0][0] != current_new_minikind):
2657
 
                    if tracing:
2658
 
                        trace.mutter("Updating in-place change '%s'.",
2659
 
                            new_path_utf8.decode('utf8'))
2660
2211
                    self.update_minimal(current_old[0], current_new_minikind,
2661
2212
                        executable=current_new[1].executable,
2662
 
                        path_utf8=new_path_utf8, fingerprint=fingerprint,
2663
 
                        fullscan=True)
 
2213
                        path_utf8=new_path_utf8, fingerprint=fingerprint)
2664
2214
                # both sides are dealt with, move on
2665
2215
                current_old = advance(old_iterator)
2666
2216
                current_new = advance(new_iterator)
2669
2219
                      and new_entry_key[1:] < current_old[0][1:])):
2670
2220
                # new comes before:
2671
2221
                # add a entry for this and advance new
2672
 
                if tracing:
2673
 
                    trace.mutter("Inserting from new '%s'.",
2674
 
                        new_path_utf8.decode('utf8'))
2675
2222
                self.update_minimal(new_entry_key, current_new_minikind,
2676
2223
                    executable=current_new[1].executable,
2677
 
                    path_utf8=new_path_utf8, fingerprint=fingerprint,
2678
 
                    fullscan=True)
 
2224
                    path_utf8=new_path_utf8, fingerprint=fingerprint)
2679
2225
                current_new = advance(new_iterator)
2680
2226
            else:
2681
2227
                # we've advanced past the place where the old key would be,
2682
2228
                # without seeing it in the new list.  so it must be gone.
2683
 
                if tracing:
2684
 
                    trace.mutter("Deleting from old '%s/%s'.",
2685
 
                        current_old[0][0].decode('utf8'),
2686
 
                        current_old[0][1].decode('utf8'))
2687
2229
                self._make_absent(current_old)
2688
2230
                current_old = advance(old_iterator)
2689
2231
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2690
2232
        self._id_index = None
2691
 
        self._packed_stat_index = None
2692
 
        if tracing:
2693
 
            trace.mutter("set_state_from_inventory complete.")
2694
 
 
2695
 
    def set_state_from_scratch(self, working_inv, parent_trees, parent_ghosts):
2696
 
        """Wipe the currently stored state and set it to something new.
2697
 
 
2698
 
        This is a hard-reset for the data we are working with.
2699
 
        """
2700
 
        # Technically, we really want a write lock, but until we write, we
2701
 
        # don't really need it.
2702
 
        self._requires_lock()
2703
 
        # root dir and root dir contents with no children. We have to have a
2704
 
        # root for set_state_from_inventory to work correctly.
2705
 
        empty_root = (('', '', inventory.ROOT_ID),
2706
 
                      [('d', '', 0, False, DirState.NULLSTAT)])
2707
 
        empty_tree_dirblocks = [('', [empty_root]), ('', [])]
2708
 
        self._set_data([], empty_tree_dirblocks)
2709
 
        self.set_state_from_inventory(working_inv)
2710
 
        self.set_parent_trees(parent_trees, parent_ghosts)
2711
2233
 
2712
2234
    def _make_absent(self, current_old):
2713
2235
        """Mark current_old - an entry - as absent for tree 0.
2721
2243
        all_remaining_keys = set()
2722
2244
        # Dont check the working tree, because it's going.
2723
2245
        for details in current_old[1][1:]:
2724
 
            if details[0] not in 'ar': # absent, relocated
 
2246
            if details[0] not in ('a', 'r'): # absent, relocated
2725
2247
                all_remaining_keys.add(current_old[0])
2726
2248
            elif details[0] == 'r': # relocated
2727
2249
                # record the key for the real path.
2734
2256
            # Remove it, its meaningless.
2735
2257
            block = self._find_block(current_old[0])
2736
2258
            entry_index, present = self._find_entry_index(current_old[0], block[1])
2737
 
            if not present:
2738
 
                raise AssertionError('could not find entry for %s' % (current_old,))
 
2259
            assert present, 'could not find entry for %s' % (current_old,)
2739
2260
            block[1].pop(entry_index)
2740
2261
            # if we have an id_index in use, remove this key from it for this id.
2741
2262
            if self._id_index is not None:
2742
 
                self._remove_from_id_index(self._id_index, current_old[0])
 
2263
                self._id_index[current_old[0][2]].remove(current_old[0])
2743
2264
        # update all remaining keys for this id to record it as absent. The
2744
 
        # existing details may either be the record we are marking as deleted
 
2265
        # existing details may either be the record we are making as deleted
2745
2266
        # (if there were other trees with the id present at this path), or may
2746
2267
        # be relocations.
2747
2268
        for update_key in all_remaining_keys:
2748
2269
            update_block_index, present = \
2749
2270
                self._find_block_index_from_key(update_key)
2750
 
            if not present:
2751
 
                raise AssertionError('could not find block for %s' % (update_key,))
 
2271
            assert present, 'could not find block for %s' % (update_key,)
2752
2272
            update_entry_index, present = \
2753
2273
                self._find_entry_index(update_key, self._dirblocks[update_block_index][1])
2754
 
            if not present:
2755
 
                raise AssertionError('could not find entry for %s' % (update_key,))
 
2274
            assert present, 'could not find entry for %s' % (update_key,)
2756
2275
            update_tree_details = self._dirblocks[update_block_index][1][update_entry_index][1]
2757
2276
            # it must not be absent at the moment
2758
 
            if update_tree_details[0][0] == 'a': # absent
2759
 
                raise AssertionError('bad row %r' % (update_tree_details,))
 
2277
            assert update_tree_details[0][0] != 'a' # absent
2760
2278
            update_tree_details[0] = DirState.NULL_PARENT_DETAILS
2761
2279
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2762
2280
        return last_reference
2763
2281
 
2764
2282
    def update_minimal(self, key, minikind, executable=False, fingerprint='',
2765
 
        packed_stat=None, size=0, path_utf8=None, fullscan=False):
 
2283
                       packed_stat=None, size=0, path_utf8=None):
2766
2284
        """Update an entry to the state in tree 0.
2767
2285
 
2768
2286
        This will either create a new entry at 'key' or update an existing one.
2773
2291
        :param minikind: The type for the entry ('f' == 'file', 'd' ==
2774
2292
                'directory'), etc.
2775
2293
        :param executable: Should the executable bit be set?
2776
 
        :param fingerprint: Simple fingerprint for new entry: canonical-form
2777
 
            sha1 for files, referenced revision id for subtrees, etc.
 
2294
        :param fingerprint: Simple fingerprint for new entry: sha1 for files, 
 
2295
            referenced revision id for subtrees, etc.
2778
2296
        :param packed_stat: Packed stat value for new entry.
2779
2297
        :param size: Size information for new entry
2780
2298
        :param path_utf8: key[0] + '/' + key[1], just passed in to avoid doing
2781
2299
                extra computation.
2782
 
        :param fullscan: If True then a complete scan of the dirstate is being
2783
 
            done and checking for duplicate rows should not be done. This
2784
 
            should only be set by set_state_from_inventory and similar methods.
2785
2300
 
2786
2301
        If packed_stat and fingerprint are not given, they're invalidated in
2787
2302
        the entry.
2796
2311
        new_details = (minikind, fingerprint, size, executable, packed_stat)
2797
2312
        id_index = self._get_id_index()
2798
2313
        if not present:
2799
 
            # New record. Check there isn't a entry at this path already.
2800
 
            if not fullscan:
2801
 
                low_index, _ = self._find_entry_index(key[0:2] + ('',), block)
2802
 
                while low_index < len(block):
2803
 
                    entry = block[low_index]
2804
 
                    if entry[0][0:2] == key[0:2]:
2805
 
                        if entry[1][0][0] not in 'ar':
2806
 
                            # This entry has the same path (but a different id) as
2807
 
                            # the new entry we're adding, and is present in ths
2808
 
                            # tree.
2809
 
                            raise errors.InconsistentDelta(
2810
 
                                ("%s/%s" % key[0:2]).decode('utf8'), key[2],
2811
 
                                "Attempt to add item at path already occupied by "
2812
 
                                "id %r" % entry[0][2])
2813
 
                        low_index += 1
2814
 
                    else:
2815
 
                        break
2816
2314
            # new entry, synthesis cross reference here,
2817
 
            existing_keys = id_index.get(key[2], ())
 
2315
            existing_keys = id_index.setdefault(key[2], set())
2818
2316
            if not existing_keys:
2819
2317
                # not currently in the state, simplest case
2820
2318
                new_entry = key, [new_details] + self._empty_parent_info()
2823
2321
                # grab one of them and use it to generate parent
2824
2322
                # relocation/absent entries.
2825
2323
                new_entry = key, [new_details]
2826
 
                # existing_keys can be changed as we iterate.
2827
 
                for other_key in tuple(existing_keys):
 
2324
                for other_key in existing_keys:
2828
2325
                    # change the record at other to be a pointer to this new
2829
2326
                    # record. The loop looks similar to the change to
2830
2327
                    # relocations when updating an existing record but its not:
2831
2328
                    # the test for existing kinds is different: this can be
2832
2329
                    # factored out to a helper though.
2833
 
                    other_block_index, present = self._find_block_index_from_key(
2834
 
                        other_key)
2835
 
                    if not present:
2836
 
                        raise AssertionError('could not find block for %s' % (
2837
 
                            other_key,))
2838
 
                    other_block = self._dirblocks[other_block_index][1]
2839
 
                    other_entry_index, present = self._find_entry_index(
2840
 
                        other_key, other_block)
2841
 
                    if not present:
2842
 
                        raise AssertionError(
2843
 
                            'update_minimal: could not find other entry for %s'
2844
 
                            % (other_key,))
2845
 
                    if path_utf8 is None:
2846
 
                        raise AssertionError('no path')
2847
 
                    # Turn this other location into a reference to the new
2848
 
                    # location. This also updates the aliased iterator
2849
 
                    # (current_old in set_state_from_inventory) so that the old
2850
 
                    # entry, if not already examined, is skipped over by that
2851
 
                    # loop.
2852
 
                    other_entry = other_block[other_entry_index]
2853
 
                    other_entry[1][0] = ('r', path_utf8, 0, False, '')
2854
 
                    if self._maybe_remove_row(other_block, other_entry_index,
2855
 
                                              id_index):
2856
 
                        # If the row holding this was removed, we need to
2857
 
                        # recompute where this entry goes
2858
 
                        entry_index, _ = self._find_entry_index(key, block)
 
2330
                    other_block_index, present = self._find_block_index_from_key(other_key)
 
2331
                    assert present, 'could not find block for %s' % (other_key,)
 
2332
                    other_entry_index, present = self._find_entry_index(other_key,
 
2333
                                            self._dirblocks[other_block_index][1])
 
2334
                    assert present, 'could not find entry for %s' % (other_key,)
 
2335
                    assert path_utf8 is not None
 
2336
                    self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
 
2337
                        ('r', path_utf8, 0, False, '')
2859
2338
 
2860
 
                # This loop:
2861
 
                # adds a tuple to the new details for each column
2862
 
                #  - either by copying an existing relocation pointer inside that column
2863
 
                #  - or by creating a new pointer to the right row inside that column
2864
2339
                num_present_parents = self._num_present_parents()
2865
 
                if num_present_parents:
2866
 
                    # TODO: This re-evaluates the existing_keys set, do we need
2867
 
                    #       to do that ourselves?
2868
 
                    other_key = list(existing_keys)[0]
2869
2340
                for lookup_index in xrange(1, num_present_parents + 1):
2870
2341
                    # grab any one entry, use it to find the right path.
2871
 
                    # TODO: optimise this to reduce memory use in highly
 
2342
                    # TODO: optimise this to reduce memory use in highly 
2872
2343
                    # fragmented situations by reusing the relocation
2873
2344
                    # records.
2874
2345
                    update_block_index, present = \
2875
2346
                        self._find_block_index_from_key(other_key)
2876
 
                    if not present:
2877
 
                        raise AssertionError('could not find block for %s' % (other_key,))
 
2347
                    assert present, 'could not find block for %s' % (other_key,)
2878
2348
                    update_entry_index, present = \
2879
2349
                        self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
2880
 
                    if not present:
2881
 
                        raise AssertionError('update_minimal: could not find entry for %s' % (other_key,))
 
2350
                    assert present, 'could not find entry for %s' % (other_key,)
2882
2351
                    update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
2883
 
                    if update_details[0] in 'ar': # relocated, absent
 
2352
                    if update_details[0] in ('r', 'a'): # relocated, absent
2884
2353
                        # its a pointer or absent in lookup_index's tree, use
2885
2354
                        # it as is.
2886
2355
                        new_entry[1].append(update_details)
2889
2358
                        pointer_path = osutils.pathjoin(*other_key[0:2])
2890
2359
                        new_entry[1].append(('r', pointer_path, 0, False, ''))
2891
2360
            block.insert(entry_index, new_entry)
2892
 
            self._add_to_id_index(id_index, key)
 
2361
            existing_keys.add(key)
2893
2362
        else:
2894
 
            # Does the new state matter?
 
2363
            # Does the new state matter? 
2895
2364
            block[entry_index][1][0] = new_details
2896
2365
            # parents cannot be affected by what we do.
2897
 
            # other occurences of this id can be found
 
2366
            # other occurences of this id can be found 
2898
2367
            # from the id index.
2899
2368
            # ---
2900
2369
            # tree index consistency: All other paths for this id in this tree
2902
2371
            # we may have passed entries in the state with this file id already
2903
2372
            # that were absent - where parent entries are - and they need to be
2904
2373
            # converted to relocated.
2905
 
            if path_utf8 is None:
2906
 
                raise AssertionError('no path')
2907
 
            existing_keys = id_index.get(key[2], ())
2908
 
            if key not in existing_keys:
2909
 
                raise AssertionError('We found the entry in the blocks, but'
2910
 
                    ' the key is not in the id_index.'
2911
 
                    ' key: %s, existing_keys: %s' % (key, existing_keys))
2912
 
            for entry_key in existing_keys:
 
2374
            assert path_utf8 is not None
 
2375
            for entry_key in id_index.setdefault(key[2], set()):
2913
2376
                # TODO:PROFILING: It might be faster to just update
2914
2377
                # rather than checking if we need to, and then overwrite
2915
2378
                # the one we are located at.
2919
2382
                    # This is the vertical axis in the matrix, all pointing
2920
2383
                    # to the real path.
2921
2384
                    block_index, present = self._find_block_index_from_key(entry_key)
2922
 
                    if not present:
2923
 
                        raise AssertionError('not present: %r', entry_key)
 
2385
                    assert present
2924
2386
                    entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
2925
 
                    if not present:
2926
 
                        raise AssertionError('not present: %r', entry_key)
 
2387
                    assert present
2927
2388
                    self._dirblocks[block_index][1][entry_index][1][0] = \
2928
2389
                        ('r', path_utf8, 0, False, '')
2929
2390
        # add a containing dirblock if needed.
2935
2396
 
2936
2397
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2937
2398
 
2938
 
    def _maybe_remove_row(self, block, index, id_index):
2939
 
        """Remove index if it is absent or relocated across the row.
2940
 
        
2941
 
        id_index is updated accordingly.
2942
 
        :return: True if we removed the row, False otherwise
2943
 
        """
2944
 
        present_in_row = False
2945
 
        entry = block[index]
2946
 
        for column in entry[1]:
2947
 
            if column[0] not in 'ar':
2948
 
                present_in_row = True
2949
 
                break
2950
 
        if not present_in_row:
2951
 
            block.pop(index)
2952
 
            self._remove_from_id_index(id_index, entry[0])
2953
 
            return True
2954
 
        return False
2955
 
 
2956
2399
    def _validate(self):
2957
2400
        """Check that invariants on the dirblock are correct.
2958
2401
 
2959
 
        This can be useful in debugging; it shouldn't be necessary in
 
2402
        This can be useful in debugging; it shouldn't be necessary in 
2960
2403
        normal code.
2961
2404
 
2962
2405
        This must be called with a lock held.
2978
2421
            if not self._dirblocks[0][0] == '':
2979
2422
                raise AssertionError(
2980
2423
                    "dirblocks don't start with root block:\n" + \
2981
 
                    pformat(self._dirblocks))
 
2424
                    pformat(dirblocks))
2982
2425
        if len(self._dirblocks) > 1:
2983
2426
            if not self._dirblocks[1][0] == '':
2984
2427
                raise AssertionError(
2985
2428
                    "dirblocks missing root directory:\n" + \
2986
 
                    pformat(self._dirblocks))
 
2429
                    pformat(dirblocks))
2987
2430
        # the dirblocks are sorted by their path components, name, and dir id
2988
2431
        dir_names = [d[0].split('/')
2989
2432
                for d in self._dirblocks[1:]]
3007
2450
                    "dirblock for %r is not sorted:\n%s" % \
3008
2451
                    (dirblock[0], pformat(dirblock)))
3009
2452
 
 
2453
 
3010
2454
        def check_valid_parent():
3011
2455
            """Check that the current entry has a valid parent.
3012
2456
 
3031
2475
        # For each file id, for each tree: either
3032
2476
        # the file id is not present at all; all rows with that id in the
3033
2477
        # key have it marked as 'absent'
3034
 
        # OR the file id is present under exactly one name; any other entries
 
2478
        # OR the file id is present under exactly one name; any other entries 
3035
2479
        # that mention that id point to the correct name.
3036
2480
        #
3037
2481
        # We check this with a dict per tree pointing either to the present
3047
2491
                "wrong number of entry details for row\n%s" \
3048
2492
                ",\nexpected %d" % \
3049
2493
                (pformat(entry), tree_count))
3050
 
            absent_positions = 0
3051
2494
            for tree_index, tree_state in enumerate(entry[1]):
3052
2495
                this_tree_map = id_path_maps[tree_index]
3053
2496
                minikind = tree_state[0]
3054
 
                if minikind in 'ar':
3055
 
                    absent_positions += 1
3056
2497
                # have we seen this id before in this column?
3057
2498
                if file_id in this_tree_map:
3058
 
                    previous_path, previous_loc = this_tree_map[file_id]
 
2499
                    previous_path = this_tree_map[file_id]
3059
2500
                    # any later mention of this file must be consistent with
3060
2501
                    # what was said before
3061
2502
                    if minikind == 'a':
3075
2516
                        # pointed to by a relocation, which must point here
3076
2517
                        if previous_path != this_path:
3077
2518
                            raise AssertionError(
3078
 
                                "entry %r inconsistent with previous path %r "
3079
 
                                "seen at %r" %
3080
 
                                (entry, previous_path, previous_loc))
 
2519
                            "entry %r inconsistent with previous path %r" % \
 
2520
                            (entry, previous_path))
3081
2521
                        check_valid_parent()
3082
2522
                else:
3083
2523
                    if minikind == 'a':
3084
2524
                        # absent; should not occur anywhere else
3085
 
                        this_tree_map[file_id] = None, this_path
 
2525
                        this_tree_map[file_id] = None
3086
2526
                    elif minikind == 'r':
3087
 
                        # relocation, must occur at expected location
3088
 
                        this_tree_map[file_id] = tree_state[1], this_path
 
2527
                        # relocation, must occur at expected location 
 
2528
                        this_tree_map[file_id] = tree_state[1]
3089
2529
                    else:
3090
 
                        this_tree_map[file_id] = this_path, this_path
 
2530
                        this_tree_map[file_id] = this_path
3091
2531
                        check_valid_parent()
3092
 
            if absent_positions == tree_count:
3093
 
                raise AssertionError(
3094
 
                    "entry %r has no data for any tree." % (entry,))
3095
 
        if self._id_index is not None:
3096
 
            for file_id, entry_keys in self._id_index.iteritems():
3097
 
                for entry_key in entry_keys:
3098
 
                    if entry_key[2] != file_id:
3099
 
                        raise AssertionError(
3100
 
                            'file_id %r did not match entry key %s'
3101
 
                            % (file_id, entry_key))
3102
 
                if len(entry_keys) != len(set(entry_keys)):
3103
 
                    raise AssertionError(
3104
 
                        'id_index contained non-unique data for %s'
3105
 
                        % (entry_keys,))
3106
2532
 
3107
2533
    def _wipe_state(self):
3108
2534
        """Forget all state information about the dirstate."""
3109
2535
        self._header_state = DirState.NOT_IN_MEMORY
3110
2536
        self._dirblock_state = DirState.NOT_IN_MEMORY
3111
 
        self._changes_aborted = False
3112
2537
        self._parents = []
3113
2538
        self._ghosts = []
3114
2539
        self._dirblocks = []
3115
2540
        self._id_index = None
3116
 
        self._packed_stat_index = None
3117
2541
        self._end_of_header = None
3118
2542
        self._cutoff_time = None
3119
2543
        self._split_path_cache = {}
3164
2588
            raise errors.ObjectNotLocked(self)
3165
2589
 
3166
2590
 
3167
 
def py_update_entry(state, entry, abspath, stat_value,
3168
 
                 _stat_to_minikind=DirState._stat_to_minikind,
3169
 
                 _pack_stat=pack_stat):
3170
 
    """Update the entry based on what is actually on disk.
3171
 
 
3172
 
    This function only calculates the sha if it needs to - if the entry is
3173
 
    uncachable, or clearly different to the first parent's entry, no sha
3174
 
    is calculated, and None is returned.
3175
 
 
3176
 
    :param state: The dirstate this entry is in.
3177
 
    :param entry: This is the dirblock entry for the file in question.
3178
 
    :param abspath: The path on disk for this file.
3179
 
    :param stat_value: The stat value done on the path.
3180
 
    :return: None, or The sha1 hexdigest of the file (40 bytes) or link
3181
 
        target of a symlink.
3182
 
    """
3183
 
    try:
3184
 
        minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
3185
 
    except KeyError:
3186
 
        # Unhandled kind
3187
 
        return None
3188
 
    packed_stat = _pack_stat(stat_value)
3189
 
    (saved_minikind, saved_link_or_sha1, saved_file_size,
3190
 
     saved_executable, saved_packed_stat) = entry[1][0]
3191
 
 
3192
 
    if minikind == 'd' and saved_minikind == 't':
3193
 
        minikind = 't'
3194
 
    if (minikind == saved_minikind
3195
 
        and packed_stat == saved_packed_stat):
3196
 
        # The stat hasn't changed since we saved, so we can re-use the
3197
 
        # saved sha hash.
3198
 
        if minikind == 'd':
3199
 
            return None
3200
 
 
3201
 
        # size should also be in packed_stat
3202
 
        if saved_file_size == stat_value.st_size:
3203
 
            return saved_link_or_sha1
3204
 
 
3205
 
    # If we have gotten this far, that means that we need to actually
3206
 
    # process this entry.
3207
 
    link_or_sha1 = None
3208
 
    worth_saving = True
3209
 
    if minikind == 'f':
3210
 
        executable = state._is_executable(stat_value.st_mode,
3211
 
                                         saved_executable)
3212
 
        if state._cutoff_time is None:
3213
 
            state._sha_cutoff_time()
3214
 
        if (stat_value.st_mtime < state._cutoff_time
3215
 
            and stat_value.st_ctime < state._cutoff_time
3216
 
            and len(entry[1]) > 1
3217
 
            and entry[1][1][0] != 'a'):
3218
 
            # Could check for size changes for further optimised
3219
 
            # avoidance of sha1's. However the most prominent case of
3220
 
            # over-shaing is during initial add, which this catches.
3221
 
            # Besides, if content filtering happens, size and sha
3222
 
            # are calculated at the same time, so checking just the size
3223
 
            # gains nothing w.r.t. performance.
3224
 
            link_or_sha1 = state._sha1_file(abspath)
3225
 
            entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
3226
 
                           executable, packed_stat)
3227
 
        else:
3228
 
            entry[1][0] = ('f', '', stat_value.st_size,
3229
 
                           executable, DirState.NULLSTAT)
3230
 
            worth_saving = False
3231
 
    elif minikind == 'd':
3232
 
        link_or_sha1 = None
3233
 
        entry[1][0] = ('d', '', 0, False, packed_stat)
3234
 
        if saved_minikind != 'd':
3235
 
            # This changed from something into a directory. Make sure we
3236
 
            # have a directory block for it. This doesn't happen very
3237
 
            # often, so this doesn't have to be super fast.
3238
 
            block_index, entry_index, dir_present, file_present = \
3239
 
                state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3240
 
            state._ensure_block(block_index, entry_index,
3241
 
                               osutils.pathjoin(entry[0][0], entry[0][1]))
3242
 
        else:
3243
 
            worth_saving = False
3244
 
    elif minikind == 'l':
3245
 
        link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3246
 
        if state._cutoff_time is None:
3247
 
            state._sha_cutoff_time()
3248
 
        if (stat_value.st_mtime < state._cutoff_time
3249
 
            and stat_value.st_ctime < state._cutoff_time):
3250
 
            entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
3251
 
                           False, packed_stat)
3252
 
        else:
3253
 
            entry[1][0] = ('l', '', stat_value.st_size,
3254
 
                           False, DirState.NULLSTAT)
3255
 
    if worth_saving:
3256
 
        state._dirblock_state = DirState.IN_MEMORY_MODIFIED
3257
 
    return link_or_sha1
3258
 
 
3259
 
 
3260
 
class ProcessEntryPython(object):
3261
 
 
3262
 
    __slots__ = ["old_dirname_to_file_id", "new_dirname_to_file_id",
3263
 
        "last_source_parent", "last_target_parent", "include_unchanged",
3264
 
        "partial", "use_filesystem_for_exec", "utf8_decode",
3265
 
        "searched_specific_files", "search_specific_files",
3266
 
        "searched_exact_paths", "search_specific_file_parents", "seen_ids",
3267
 
        "state", "source_index", "target_index", "want_unversioned", "tree"]
3268
 
 
3269
 
    def __init__(self, include_unchanged, use_filesystem_for_exec,
3270
 
        search_specific_files, state, source_index, target_index,
3271
 
        want_unversioned, tree):
3272
 
        self.old_dirname_to_file_id = {}
3273
 
        self.new_dirname_to_file_id = {}
3274
 
        # Are we doing a partial iter_changes?
3275
 
        self.partial = search_specific_files != set([''])
3276
 
        # Using a list so that we can access the values and change them in
3277
 
        # nested scope. Each one is [path, file_id, entry]
3278
 
        self.last_source_parent = [None, None]
3279
 
        self.last_target_parent = [None, None]
3280
 
        self.include_unchanged = include_unchanged
3281
 
        self.use_filesystem_for_exec = use_filesystem_for_exec
3282
 
        self.utf8_decode = cache_utf8._utf8_decode
3283
 
        # for all search_indexs in each path at or under each element of
3284
 
        # search_specific_files, if the detail is relocated: add the id, and
3285
 
        # add the relocated path as one to search if its not searched already.
3286
 
        # If the detail is not relocated, add the id.
3287
 
        self.searched_specific_files = set()
3288
 
        # When we search exact paths without expanding downwards, we record
3289
 
        # that here.
3290
 
        self.searched_exact_paths = set()
3291
 
        self.search_specific_files = search_specific_files
3292
 
        # The parents up to the root of the paths we are searching.
3293
 
        # After all normal paths are returned, these specific items are returned.
3294
 
        self.search_specific_file_parents = set()
3295
 
        # The ids we've sent out in the delta.
3296
 
        self.seen_ids = set()
3297
 
        self.state = state
3298
 
        self.source_index = source_index
3299
 
        self.target_index = target_index
3300
 
        if target_index != 0:
3301
 
            # A lot of code in here depends on target_index == 0
3302
 
            raise errors.BzrError('unsupported target index')
3303
 
        self.want_unversioned = want_unversioned
3304
 
        self.tree = tree
3305
 
 
3306
 
    def _process_entry(self, entry, path_info, pathjoin=osutils.pathjoin):
3307
 
        """Compare an entry and real disk to generate delta information.
3308
 
 
3309
 
        :param path_info: top_relpath, basename, kind, lstat, abspath for
3310
 
            the path of entry. If None, then the path is considered absent in 
3311
 
            the target (Perhaps we should pass in a concrete entry for this ?)
3312
 
            Basename is returned as a utf8 string because we expect this
3313
 
            tuple will be ignored, and don't want to take the time to
3314
 
            decode.
3315
 
        :return: (iter_changes_result, changed). If the entry has not been
3316
 
            handled then changed is None. Otherwise it is False if no content
3317
 
            or metadata changes have occurred, and True if any content or
3318
 
            metadata change has occurred. If self.include_unchanged is True then
3319
 
            if changed is not None, iter_changes_result will always be a result
3320
 
            tuple. Otherwise, iter_changes_result is None unless changed is
3321
 
            True.
3322
 
        """
3323
 
        if self.source_index is None:
3324
 
            source_details = DirState.NULL_PARENT_DETAILS
3325
 
        else:
3326
 
            source_details = entry[1][self.source_index]
3327
 
        target_details = entry[1][self.target_index]
3328
 
        target_minikind = target_details[0]
3329
 
        if path_info is not None and target_minikind in 'fdlt':
3330
 
            if not (self.target_index == 0):
3331
 
                raise AssertionError()
3332
 
            link_or_sha1 = update_entry(self.state, entry,
3333
 
                abspath=path_info[4], stat_value=path_info[3])
3334
 
            # The entry may have been modified by update_entry
3335
 
            target_details = entry[1][self.target_index]
3336
 
            target_minikind = target_details[0]
3337
 
        else:
3338
 
            link_or_sha1 = None
3339
 
        file_id = entry[0][2]
3340
 
        source_minikind = source_details[0]
3341
 
        if source_minikind in 'fdltr' and target_minikind in 'fdlt':
3342
 
            # claimed content in both: diff
3343
 
            #   r    | fdlt   |      | add source to search, add id path move and perform
3344
 
            #        |        |      | diff check on source-target
3345
 
            #   r    | fdlt   |  a   | dangling file that was present in the basis.
3346
 
            #        |        |      | ???
3347
 
            if source_minikind in 'r':
3348
 
                # add the source to the search path to find any children it
3349
 
                # has.  TODO ? : only add if it is a container ?
3350
 
                if not osutils.is_inside_any(self.searched_specific_files,
3351
 
                                             source_details[1]):
3352
 
                    self.search_specific_files.add(source_details[1])
3353
 
                # generate the old path; this is needed for stating later
3354
 
                # as well.
3355
 
                old_path = source_details[1]
3356
 
                old_dirname, old_basename = os.path.split(old_path)
3357
 
                path = pathjoin(entry[0][0], entry[0][1])
3358
 
                old_entry = self.state._get_entry(self.source_index,
3359
 
                                             path_utf8=old_path)
3360
 
                # update the source details variable to be the real
3361
 
                # location.
3362
 
                if old_entry == (None, None):
3363
 
                    raise errors.CorruptDirstate(self.state._filename,
3364
 
                        "entry '%s/%s' is considered renamed from %r"
3365
 
                        " but source does not exist\n"
3366
 
                        "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3367
 
                source_details = old_entry[1][self.source_index]
3368
 
                source_minikind = source_details[0]
3369
 
            else:
3370
 
                old_dirname = entry[0][0]
3371
 
                old_basename = entry[0][1]
3372
 
                old_path = path = None
3373
 
            if path_info is None:
3374
 
                # the file is missing on disk, show as removed.
3375
 
                content_change = True
3376
 
                target_kind = None
3377
 
                target_exec = False
3378
 
            else:
3379
 
                # source and target are both versioned and disk file is present.
3380
 
                target_kind = path_info[2]
3381
 
                if target_kind == 'directory':
3382
 
                    if path is None:
3383
 
                        old_path = path = pathjoin(old_dirname, old_basename)
3384
 
                    self.new_dirname_to_file_id[path] = file_id
3385
 
                    if source_minikind != 'd':
3386
 
                        content_change = True
3387
 
                    else:
3388
 
                        # directories have no fingerprint
3389
 
                        content_change = False
3390
 
                    target_exec = False
3391
 
                elif target_kind == 'file':
3392
 
                    if source_minikind != 'f':
3393
 
                        content_change = True
3394
 
                    else:
3395
 
                        # Check the sha. We can't just rely on the size as
3396
 
                        # content filtering may mean differ sizes actually
3397
 
                        # map to the same content
3398
 
                        if link_or_sha1 is None:
3399
 
                            # Stat cache miss:
3400
 
                            statvalue, link_or_sha1 = \
3401
 
                                self.state._sha1_provider.stat_and_sha1(
3402
 
                                path_info[4])
3403
 
                            self.state._observed_sha1(entry, link_or_sha1,
3404
 
                                statvalue)
3405
 
                        content_change = (link_or_sha1 != source_details[1])
3406
 
                    # Target details is updated at update_entry time
3407
 
                    if self.use_filesystem_for_exec:
3408
 
                        # We don't need S_ISREG here, because we are sure
3409
 
                        # we are dealing with a file.
3410
 
                        target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
3411
 
                    else:
3412
 
                        target_exec = target_details[3]
3413
 
                elif target_kind == 'symlink':
3414
 
                    if source_minikind != 'l':
3415
 
                        content_change = True
3416
 
                    else:
3417
 
                        content_change = (link_or_sha1 != source_details[1])
3418
 
                    target_exec = False
3419
 
                elif target_kind == 'tree-reference':
3420
 
                    if source_minikind != 't':
3421
 
                        content_change = True
3422
 
                    else:
3423
 
                        content_change = False
3424
 
                    target_exec = False
3425
 
                else:
3426
 
                    if path is None:
3427
 
                        path = pathjoin(old_dirname, old_basename)
3428
 
                    raise errors.BadFileKindError(path, path_info[2])
3429
 
            if source_minikind == 'd':
3430
 
                if path is None:
3431
 
                    old_path = path = pathjoin(old_dirname, old_basename)
3432
 
                self.old_dirname_to_file_id[old_path] = file_id
3433
 
            # parent id is the entry for the path in the target tree
3434
 
            if old_basename and old_dirname == self.last_source_parent[0]:
3435
 
                source_parent_id = self.last_source_parent[1]
3436
 
            else:
3437
 
                try:
3438
 
                    source_parent_id = self.old_dirname_to_file_id[old_dirname]
3439
 
                except KeyError:
3440
 
                    source_parent_entry = self.state._get_entry(self.source_index,
3441
 
                                                           path_utf8=old_dirname)
3442
 
                    source_parent_id = source_parent_entry[0][2]
3443
 
                if source_parent_id == entry[0][2]:
3444
 
                    # This is the root, so the parent is None
3445
 
                    source_parent_id = None
3446
 
                else:
3447
 
                    self.last_source_parent[0] = old_dirname
3448
 
                    self.last_source_parent[1] = source_parent_id
3449
 
            new_dirname = entry[0][0]
3450
 
            if entry[0][1] and new_dirname == self.last_target_parent[0]:
3451
 
                target_parent_id = self.last_target_parent[1]
3452
 
            else:
3453
 
                try:
3454
 
                    target_parent_id = self.new_dirname_to_file_id[new_dirname]
3455
 
                except KeyError:
3456
 
                    # TODO: We don't always need to do the lookup, because the
3457
 
                    #       parent entry will be the same as the source entry.
3458
 
                    target_parent_entry = self.state._get_entry(self.target_index,
3459
 
                                                           path_utf8=new_dirname)
3460
 
                    if target_parent_entry == (None, None):
3461
 
                        raise AssertionError(
3462
 
                            "Could not find target parent in wt: %s\nparent of: %s"
3463
 
                            % (new_dirname, entry))
3464
 
                    target_parent_id = target_parent_entry[0][2]
3465
 
                if target_parent_id == entry[0][2]:
3466
 
                    # This is the root, so the parent is None
3467
 
                    target_parent_id = None
3468
 
                else:
3469
 
                    self.last_target_parent[0] = new_dirname
3470
 
                    self.last_target_parent[1] = target_parent_id
3471
 
 
3472
 
            source_exec = source_details[3]
3473
 
            changed = (content_change
3474
 
                or source_parent_id != target_parent_id
3475
 
                or old_basename != entry[0][1]
3476
 
                or source_exec != target_exec
3477
 
                )
3478
 
            if not changed and not self.include_unchanged:
3479
 
                return None, False
3480
 
            else:
3481
 
                if old_path is None:
3482
 
                    old_path = path = pathjoin(old_dirname, old_basename)
3483
 
                    old_path_u = self.utf8_decode(old_path)[0]
3484
 
                    path_u = old_path_u
3485
 
                else:
3486
 
                    old_path_u = self.utf8_decode(old_path)[0]
3487
 
                    if old_path == path:
3488
 
                        path_u = old_path_u
3489
 
                    else:
3490
 
                        path_u = self.utf8_decode(path)[0]
3491
 
                source_kind = DirState._minikind_to_kind[source_minikind]
3492
 
                return (entry[0][2],
3493
 
                       (old_path_u, path_u),
3494
 
                       content_change,
3495
 
                       (True, True),
3496
 
                       (source_parent_id, target_parent_id),
3497
 
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3498
 
                       (source_kind, target_kind),
3499
 
                       (source_exec, target_exec)), changed
3500
 
        elif source_minikind in 'a' and target_minikind in 'fdlt':
3501
 
            # looks like a new file
3502
 
            path = pathjoin(entry[0][0], entry[0][1])
3503
 
            # parent id is the entry for the path in the target tree
3504
 
            # TODO: these are the same for an entire directory: cache em.
3505
 
            parent_id = self.state._get_entry(self.target_index,
3506
 
                                         path_utf8=entry[0][0])[0][2]
3507
 
            if parent_id == entry[0][2]:
3508
 
                parent_id = None
3509
 
            if path_info is not None:
3510
 
                # Present on disk:
3511
 
                if self.use_filesystem_for_exec:
3512
 
                    # We need S_ISREG here, because we aren't sure if this
3513
 
                    # is a file or not.
3514
 
                    target_exec = bool(
3515
 
                        stat.S_ISREG(path_info[3].st_mode)
3516
 
                        and stat.S_IEXEC & path_info[3].st_mode)
3517
 
                else:
3518
 
                    target_exec = target_details[3]
3519
 
                return (entry[0][2],
3520
 
                       (None, self.utf8_decode(path)[0]),
3521
 
                       True,
3522
 
                       (False, True),
3523
 
                       (None, parent_id),
3524
 
                       (None, self.utf8_decode(entry[0][1])[0]),
3525
 
                       (None, path_info[2]),
3526
 
                       (None, target_exec)), True
3527
 
            else:
3528
 
                # Its a missing file, report it as such.
3529
 
                return (entry[0][2],
3530
 
                       (None, self.utf8_decode(path)[0]),
3531
 
                       False,
3532
 
                       (False, True),
3533
 
                       (None, parent_id),
3534
 
                       (None, self.utf8_decode(entry[0][1])[0]),
3535
 
                       (None, None),
3536
 
                       (None, False)), True
3537
 
        elif source_minikind in 'fdlt' and target_minikind in 'a':
3538
 
            # unversioned, possibly, or possibly not deleted: we dont care.
3539
 
            # if its still on disk, *and* theres no other entry at this
3540
 
            # path [we dont know this in this routine at the moment -
3541
 
            # perhaps we should change this - then it would be an unknown.
3542
 
            old_path = pathjoin(entry[0][0], entry[0][1])
3543
 
            # parent id is the entry for the path in the target tree
3544
 
            parent_id = self.state._get_entry(self.source_index, path_utf8=entry[0][0])[0][2]
3545
 
            if parent_id == entry[0][2]:
3546
 
                parent_id = None
3547
 
            return (entry[0][2],
3548
 
                   (self.utf8_decode(old_path)[0], None),
3549
 
                   True,
3550
 
                   (True, False),
3551
 
                   (parent_id, None),
3552
 
                   (self.utf8_decode(entry[0][1])[0], None),
3553
 
                   (DirState._minikind_to_kind[source_minikind], None),
3554
 
                   (source_details[3], None)), True
3555
 
        elif source_minikind in 'fdlt' and target_minikind in 'r':
3556
 
            # a rename; could be a true rename, or a rename inherited from
3557
 
            # a renamed parent. TODO: handle this efficiently. Its not
3558
 
            # common case to rename dirs though, so a correct but slow
3559
 
            # implementation will do.
3560
 
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3561
 
                self.search_specific_files.add(target_details[1])
3562
 
        elif source_minikind in 'ra' and target_minikind in 'ra':
3563
 
            # neither of the selected trees contain this file,
3564
 
            # so skip over it. This is not currently directly tested, but
3565
 
            # is indirectly via test_too_much.TestCommands.test_conflicts.
3566
 
            pass
3567
 
        else:
3568
 
            raise AssertionError("don't know how to compare "
3569
 
                "source_minikind=%r, target_minikind=%r"
3570
 
                % (source_minikind, target_minikind))
3571
 
            ## import pdb;pdb.set_trace()
3572
 
        return None, None
3573
 
 
3574
 
    def __iter__(self):
3575
 
        return self
3576
 
 
3577
 
    def _gather_result_for_consistency(self, result):
3578
 
        """Check a result we will yield to make sure we are consistent later.
3579
 
        
3580
 
        This gathers result's parents into a set to output later.
3581
 
 
3582
 
        :param result: A result tuple.
3583
 
        """
3584
 
        if not self.partial or not result[0]:
3585
 
            return
3586
 
        self.seen_ids.add(result[0])
3587
 
        new_path = result[1][1]
3588
 
        if new_path:
3589
 
            # Not the root and not a delete: queue up the parents of the path.
3590
 
            self.search_specific_file_parents.update(
3591
 
                osutils.parent_directories(new_path.encode('utf8')))
3592
 
            # Add the root directory which parent_directories does not
3593
 
            # provide.
3594
 
            self.search_specific_file_parents.add('')
3595
 
 
3596
 
    def iter_changes(self):
3597
 
        """Iterate over the changes."""
3598
 
        utf8_decode = cache_utf8._utf8_decode
3599
 
        _cmp_by_dirs = cmp_by_dirs
3600
 
        _process_entry = self._process_entry
3601
 
        search_specific_files = self.search_specific_files
3602
 
        searched_specific_files = self.searched_specific_files
3603
 
        splitpath = osutils.splitpath
3604
 
        # sketch:
3605
 
        # compare source_index and target_index at or under each element of search_specific_files.
3606
 
        # follow the following comparison table. Note that we only want to do diff operations when
3607
 
        # the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
3608
 
        # for the target.
3609
 
        # cases:
3610
 
        #
3611
 
        # Source | Target | disk | action
3612
 
        #   r    | fdlt   |      | add source to search, add id path move and perform
3613
 
        #        |        |      | diff check on source-target
3614
 
        #   r    | fdlt   |  a   | dangling file that was present in the basis.
3615
 
        #        |        |      | ???
3616
 
        #   r    |  a     |      | add source to search
3617
 
        #   r    |  a     |  a   |
3618
 
        #   r    |  r     |      | this path is present in a non-examined tree, skip.
3619
 
        #   r    |  r     |  a   | this path is present in a non-examined tree, skip.
3620
 
        #   a    | fdlt   |      | add new id
3621
 
        #   a    | fdlt   |  a   | dangling locally added file, skip
3622
 
        #   a    |  a     |      | not present in either tree, skip
3623
 
        #   a    |  a     |  a   | not present in any tree, skip
3624
 
        #   a    |  r     |      | not present in either tree at this path, skip as it
3625
 
        #        |        |      | may not be selected by the users list of paths.
3626
 
        #   a    |  r     |  a   | not present in either tree at this path, skip as it
3627
 
        #        |        |      | may not be selected by the users list of paths.
3628
 
        #  fdlt  | fdlt   |      | content in both: diff them
3629
 
        #  fdlt  | fdlt   |  a   | deleted locally, but not unversioned - show as deleted ?
3630
 
        #  fdlt  |  a     |      | unversioned: output deleted id for now
3631
 
        #  fdlt  |  a     |  a   | unversioned and deleted: output deleted id
3632
 
        #  fdlt  |  r     |      | relocated in this tree, so add target to search.
3633
 
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
3634
 
        #        |        |      | this id at the other path.
3635
 
        #  fdlt  |  r     |  a   | relocated in this tree, so add target to search.
3636
 
        #        |        |      | Dont diff, we will see an r,fd; pair when we reach
3637
 
        #        |        |      | this id at the other path.
3638
 
 
3639
 
        # TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
3640
 
        #       keeping a cache of directories that we have seen.
3641
 
 
3642
 
        while search_specific_files:
3643
 
            # TODO: the pending list should be lexically sorted?  the
3644
 
            # interface doesn't require it.
3645
 
            current_root = search_specific_files.pop()
3646
 
            current_root_unicode = current_root.decode('utf8')
3647
 
            searched_specific_files.add(current_root)
3648
 
            # process the entries for this containing directory: the rest will be
3649
 
            # found by their parents recursively.
3650
 
            root_entries = self.state._entries_for_path(current_root)
3651
 
            root_abspath = self.tree.abspath(current_root_unicode)
3652
 
            try:
3653
 
                root_stat = os.lstat(root_abspath)
3654
 
            except OSError, e:
3655
 
                if e.errno == errno.ENOENT:
3656
 
                    # the path does not exist: let _process_entry know that.
3657
 
                    root_dir_info = None
3658
 
                else:
3659
 
                    # some other random error: hand it up.
3660
 
                    raise
3661
 
            else:
3662
 
                root_dir_info = ('', current_root,
3663
 
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3664
 
                    root_abspath)
3665
 
                if root_dir_info[2] == 'directory':
3666
 
                    if self.tree._directory_is_tree_reference(
3667
 
                        current_root.decode('utf8')):
3668
 
                        root_dir_info = root_dir_info[:2] + \
3669
 
                            ('tree-reference',) + root_dir_info[3:]
3670
 
 
3671
 
            if not root_entries and not root_dir_info:
3672
 
                # this specified path is not present at all, skip it.
3673
 
                continue
3674
 
            path_handled = False
3675
 
            for entry in root_entries:
3676
 
                result, changed = _process_entry(entry, root_dir_info)
3677
 
                if changed is not None:
3678
 
                    path_handled = True
3679
 
                    if changed:
3680
 
                        self._gather_result_for_consistency(result)
3681
 
                    if changed or self.include_unchanged:
3682
 
                        yield result
3683
 
            if self.want_unversioned and not path_handled and root_dir_info:
3684
 
                new_executable = bool(
3685
 
                    stat.S_ISREG(root_dir_info[3].st_mode)
3686
 
                    and stat.S_IEXEC & root_dir_info[3].st_mode)
3687
 
                yield (None,
3688
 
                       (None, current_root_unicode),
3689
 
                       True,
3690
 
                       (False, False),
3691
 
                       (None, None),
3692
 
                       (None, splitpath(current_root_unicode)[-1]),
3693
 
                       (None, root_dir_info[2]),
3694
 
                       (None, new_executable)
3695
 
                      )
3696
 
            initial_key = (current_root, '', '')
3697
 
            block_index, _ = self.state._find_block_index_from_key(initial_key)
3698
 
            if block_index == 0:
3699
 
                # we have processed the total root already, but because the
3700
 
                # initial key matched it we should skip it here.
3701
 
                block_index +=1
3702
 
            if root_dir_info and root_dir_info[2] == 'tree-reference':
3703
 
                current_dir_info = None
3704
 
            else:
3705
 
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3706
 
                try:
3707
 
                    current_dir_info = dir_iterator.next()
3708
 
                except OSError, e:
3709
 
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3710
 
                    # python 2.5 has e.errno == EINVAL,
3711
 
                    #            and e.winerror == ERROR_DIRECTORY
3712
 
                    e_winerror = getattr(e, 'winerror', None)
3713
 
                    win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
3714
 
                    # there may be directories in the inventory even though
3715
 
                    # this path is not a file on disk: so mark it as end of
3716
 
                    # iterator
3717
 
                    if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
3718
 
                        current_dir_info = None
3719
 
                    elif (sys.platform == 'win32'
3720
 
                          and (e.errno in win_errors
3721
 
                               or e_winerror in win_errors)):
3722
 
                        current_dir_info = None
3723
 
                    else:
3724
 
                        raise
3725
 
                else:
3726
 
                    if current_dir_info[0][0] == '':
3727
 
                        # remove .bzr from iteration
3728
 
                        bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3729
 
                        if current_dir_info[1][bzr_index][0] != '.bzr':
3730
 
                            raise AssertionError()
3731
 
                        del current_dir_info[1][bzr_index]
3732
 
            # walk until both the directory listing and the versioned metadata
3733
 
            # are exhausted.
3734
 
            if (block_index < len(self.state._dirblocks) and
3735
 
                osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3736
 
                current_block = self.state._dirblocks[block_index]
3737
 
            else:
3738
 
                current_block = None
3739
 
            while (current_dir_info is not None or
3740
 
                   current_block is not None):
3741
 
                if (current_dir_info and current_block
3742
 
                    and current_dir_info[0][0] != current_block[0]):
3743
 
                    if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
3744
 
                        # filesystem data refers to paths not covered by the dirblock.
3745
 
                        # this has two possibilities:
3746
 
                        # A) it is versioned but empty, so there is no block for it
3747
 
                        # B) it is not versioned.
3748
 
 
3749
 
                        # if (A) then we need to recurse into it to check for
3750
 
                        # new unknown files or directories.
3751
 
                        # if (B) then we should ignore it, because we don't
3752
 
                        # recurse into unknown directories.
3753
 
                        path_index = 0
3754
 
                        while path_index < len(current_dir_info[1]):
3755
 
                                current_path_info = current_dir_info[1][path_index]
3756
 
                                if self.want_unversioned:
3757
 
                                    if current_path_info[2] == 'directory':
3758
 
                                        if self.tree._directory_is_tree_reference(
3759
 
                                            current_path_info[0].decode('utf8')):
3760
 
                                            current_path_info = current_path_info[:2] + \
3761
 
                                                ('tree-reference',) + current_path_info[3:]
3762
 
                                    new_executable = bool(
3763
 
                                        stat.S_ISREG(current_path_info[3].st_mode)
3764
 
                                        and stat.S_IEXEC & current_path_info[3].st_mode)
3765
 
                                    yield (None,
3766
 
                                        (None, utf8_decode(current_path_info[0])[0]),
3767
 
                                        True,
3768
 
                                        (False, False),
3769
 
                                        (None, None),
3770
 
                                        (None, utf8_decode(current_path_info[1])[0]),
3771
 
                                        (None, current_path_info[2]),
3772
 
                                        (None, new_executable))
3773
 
                                # dont descend into this unversioned path if it is
3774
 
                                # a dir
3775
 
                                if current_path_info[2] in ('directory',
3776
 
                                                            'tree-reference'):
3777
 
                                    del current_dir_info[1][path_index]
3778
 
                                    path_index -= 1
3779
 
                                path_index += 1
3780
 
 
3781
 
                        # This dir info has been handled, go to the next
3782
 
                        try:
3783
 
                            current_dir_info = dir_iterator.next()
3784
 
                        except StopIteration:
3785
 
                            current_dir_info = None
3786
 
                    else:
3787
 
                        # We have a dirblock entry for this location, but there
3788
 
                        # is no filesystem path for this. This is most likely
3789
 
                        # because a directory was removed from the disk.
3790
 
                        # We don't have to report the missing directory,
3791
 
                        # because that should have already been handled, but we
3792
 
                        # need to handle all of the files that are contained
3793
 
                        # within.
3794
 
                        for current_entry in current_block[1]:
3795
 
                            # entry referring to file not present on disk.
3796
 
                            # advance the entry only, after processing.
3797
 
                            result, changed = _process_entry(current_entry, None)
3798
 
                            if changed is not None:
3799
 
                                if changed:
3800
 
                                    self._gather_result_for_consistency(result)
3801
 
                                if changed or self.include_unchanged:
3802
 
                                    yield result
3803
 
                        block_index +=1
3804
 
                        if (block_index < len(self.state._dirblocks) and
3805
 
                            osutils.is_inside(current_root,
3806
 
                                              self.state._dirblocks[block_index][0])):
3807
 
                            current_block = self.state._dirblocks[block_index]
3808
 
                        else:
3809
 
                            current_block = None
3810
 
                    continue
3811
 
                entry_index = 0
3812
 
                if current_block and entry_index < len(current_block[1]):
3813
 
                    current_entry = current_block[1][entry_index]
3814
 
                else:
3815
 
                    current_entry = None
3816
 
                advance_entry = True
3817
 
                path_index = 0
3818
 
                if current_dir_info and path_index < len(current_dir_info[1]):
3819
 
                    current_path_info = current_dir_info[1][path_index]
3820
 
                    if current_path_info[2] == 'directory':
3821
 
                        if self.tree._directory_is_tree_reference(
3822
 
                            current_path_info[0].decode('utf8')):
3823
 
                            current_path_info = current_path_info[:2] + \
3824
 
                                ('tree-reference',) + current_path_info[3:]
3825
 
                else:
3826
 
                    current_path_info = None
3827
 
                advance_path = True
3828
 
                path_handled = False
3829
 
                while (current_entry is not None or
3830
 
                    current_path_info is not None):
3831
 
                    if current_entry is None:
3832
 
                        # the check for path_handled when the path is advanced
3833
 
                        # will yield this path if needed.
3834
 
                        pass
3835
 
                    elif current_path_info is None:
3836
 
                        # no path is fine: the per entry code will handle it.
3837
 
                        result, changed = _process_entry(current_entry, current_path_info)
3838
 
                        if changed is not None:
3839
 
                            if changed:
3840
 
                                self._gather_result_for_consistency(result)
3841
 
                            if changed or self.include_unchanged:
3842
 
                                yield result
3843
 
                    elif (current_entry[0][1] != current_path_info[1]
3844
 
                          or current_entry[1][self.target_index][0] in 'ar'):
3845
 
                        # The current path on disk doesn't match the dirblock
3846
 
                        # record. Either the dirblock is marked as absent, or
3847
 
                        # the file on disk is not present at all in the
3848
 
                        # dirblock. Either way, report about the dirblock
3849
 
                        # entry, and let other code handle the filesystem one.
3850
 
 
3851
 
                        # Compare the basename for these files to determine
3852
 
                        # which comes first
3853
 
                        if current_path_info[1] < current_entry[0][1]:
3854
 
                            # extra file on disk: pass for now, but only
3855
 
                            # increment the path, not the entry
3856
 
                            advance_entry = False
3857
 
                        else:
3858
 
                            # entry referring to file not present on disk.
3859
 
                            # advance the entry only, after processing.
3860
 
                            result, changed = _process_entry(current_entry, None)
3861
 
                            if changed is not None:
3862
 
                                if changed:
3863
 
                                    self._gather_result_for_consistency(result)
3864
 
                                if changed or self.include_unchanged:
3865
 
                                    yield result
3866
 
                            advance_path = False
3867
 
                    else:
3868
 
                        result, changed = _process_entry(current_entry, current_path_info)
3869
 
                        if changed is not None:
3870
 
                            path_handled = True
3871
 
                            if changed:
3872
 
                                self._gather_result_for_consistency(result)
3873
 
                            if changed or self.include_unchanged:
3874
 
                                yield result
3875
 
                    if advance_entry and current_entry is not None:
3876
 
                        entry_index += 1
3877
 
                        if entry_index < len(current_block[1]):
3878
 
                            current_entry = current_block[1][entry_index]
3879
 
                        else:
3880
 
                            current_entry = None
3881
 
                    else:
3882
 
                        advance_entry = True # reset the advance flaga
3883
 
                    if advance_path and current_path_info is not None:
3884
 
                        if not path_handled:
3885
 
                            # unversioned in all regards
3886
 
                            if self.want_unversioned:
3887
 
                                new_executable = bool(
3888
 
                                    stat.S_ISREG(current_path_info[3].st_mode)
3889
 
                                    and stat.S_IEXEC & current_path_info[3].st_mode)
3890
 
                                try:
3891
 
                                    relpath_unicode = utf8_decode(current_path_info[0])[0]
3892
 
                                except UnicodeDecodeError:
3893
 
                                    raise errors.BadFilenameEncoding(
3894
 
                                        current_path_info[0], osutils._fs_enc)
3895
 
                                yield (None,
3896
 
                                    (None, relpath_unicode),
3897
 
                                    True,
3898
 
                                    (False, False),
3899
 
                                    (None, None),
3900
 
                                    (None, utf8_decode(current_path_info[1])[0]),
3901
 
                                    (None, current_path_info[2]),
3902
 
                                    (None, new_executable))
3903
 
                            # dont descend into this unversioned path if it is
3904
 
                            # a dir
3905
 
                            if current_path_info[2] in ('directory'):
3906
 
                                del current_dir_info[1][path_index]
3907
 
                                path_index -= 1
3908
 
                        # dont descend the disk iterator into any tree
3909
 
                        # paths.
3910
 
                        if current_path_info[2] == 'tree-reference':
3911
 
                            del current_dir_info[1][path_index]
3912
 
                            path_index -= 1
3913
 
                        path_index += 1
3914
 
                        if path_index < len(current_dir_info[1]):
3915
 
                            current_path_info = current_dir_info[1][path_index]
3916
 
                            if current_path_info[2] == 'directory':
3917
 
                                if self.tree._directory_is_tree_reference(
3918
 
                                    current_path_info[0].decode('utf8')):
3919
 
                                    current_path_info = current_path_info[:2] + \
3920
 
                                        ('tree-reference',) + current_path_info[3:]
3921
 
                        else:
3922
 
                            current_path_info = None
3923
 
                        path_handled = False
3924
 
                    else:
3925
 
                        advance_path = True # reset the advance flagg.
3926
 
                if current_block is not None:
3927
 
                    block_index += 1
3928
 
                    if (block_index < len(self.state._dirblocks) and
3929
 
                        osutils.is_inside(current_root, self.state._dirblocks[block_index][0])):
3930
 
                        current_block = self.state._dirblocks[block_index]
3931
 
                    else:
3932
 
                        current_block = None
3933
 
                if current_dir_info is not None:
3934
 
                    try:
3935
 
                        current_dir_info = dir_iterator.next()
3936
 
                    except StopIteration:
3937
 
                        current_dir_info = None
3938
 
        for result in self._iter_specific_file_parents():
3939
 
            yield result
3940
 
 
3941
 
    def _iter_specific_file_parents(self):
3942
 
        """Iter over the specific file parents."""
3943
 
        while self.search_specific_file_parents:
3944
 
            # Process the parent directories for the paths we were iterating.
3945
 
            # Even in extremely large trees this should be modest, so currently
3946
 
            # no attempt is made to optimise.
3947
 
            path_utf8 = self.search_specific_file_parents.pop()
3948
 
            if osutils.is_inside_any(self.searched_specific_files, path_utf8):
3949
 
                # We've examined this path.
3950
 
                continue
3951
 
            if path_utf8 in self.searched_exact_paths:
3952
 
                # We've examined this path.
3953
 
                continue
3954
 
            path_entries = self.state._entries_for_path(path_utf8)
3955
 
            # We need either one or two entries. If the path in
3956
 
            # self.target_index has moved (so the entry in source_index is in
3957
 
            # 'ar') then we need to also look for the entry for this path in
3958
 
            # self.source_index, to output the appropriate delete-or-rename.
3959
 
            selected_entries = []
3960
 
            found_item = False
3961
 
            for candidate_entry in path_entries:
3962
 
                # Find entries present in target at this path:
3963
 
                if candidate_entry[1][self.target_index][0] not in 'ar':
3964
 
                    found_item = True
3965
 
                    selected_entries.append(candidate_entry)
3966
 
                # Find entries present in source at this path:
3967
 
                elif (self.source_index is not None and
3968
 
                    candidate_entry[1][self.source_index][0] not in 'ar'):
3969
 
                    found_item = True
3970
 
                    if candidate_entry[1][self.target_index][0] == 'a':
3971
 
                        # Deleted, emit it here.
3972
 
                        selected_entries.append(candidate_entry)
3973
 
                    else:
3974
 
                        # renamed, emit it when we process the directory it
3975
 
                        # ended up at.
3976
 
                        self.search_specific_file_parents.add(
3977
 
                            candidate_entry[1][self.target_index][1])
3978
 
            if not found_item:
3979
 
                raise AssertionError(
3980
 
                    "Missing entry for specific path parent %r, %r" % (
3981
 
                    path_utf8, path_entries))
3982
 
            path_info = self._path_info(path_utf8, path_utf8.decode('utf8'))
3983
 
            for entry in selected_entries:
3984
 
                if entry[0][2] in self.seen_ids:
3985
 
                    continue
3986
 
                result, changed = self._process_entry(entry, path_info)
3987
 
                if changed is None:
3988
 
                    raise AssertionError(
3989
 
                        "Got entry<->path mismatch for specific path "
3990
 
                        "%r entry %r path_info %r " % (
3991
 
                        path_utf8, entry, path_info))
3992
 
                # Only include changes - we're outside the users requested
3993
 
                # expansion.
3994
 
                if changed:
3995
 
                    self._gather_result_for_consistency(result)
3996
 
                    if (result[6][0] == 'directory' and
3997
 
                        result[6][1] != 'directory'):
3998
 
                        # This stopped being a directory, the old children have
3999
 
                        # to be included.
4000
 
                        if entry[1][self.source_index][0] == 'r':
4001
 
                            # renamed, take the source path
4002
 
                            entry_path_utf8 = entry[1][self.source_index][1]
4003
 
                        else:
4004
 
                            entry_path_utf8 = path_utf8
4005
 
                        initial_key = (entry_path_utf8, '', '')
4006
 
                        block_index, _ = self.state._find_block_index_from_key(
4007
 
                            initial_key)
4008
 
                        if block_index == 0:
4009
 
                            # The children of the root are in block index 1.
4010
 
                            block_index +=1
4011
 
                        current_block = None
4012
 
                        if block_index < len(self.state._dirblocks):
4013
 
                            current_block = self.state._dirblocks[block_index]
4014
 
                            if not osutils.is_inside(
4015
 
                                entry_path_utf8, current_block[0]):
4016
 
                                # No entries for this directory at all.
4017
 
                                current_block = None
4018
 
                        if current_block is not None:
4019
 
                            for entry in current_block[1]:
4020
 
                                if entry[1][self.source_index][0] in 'ar':
4021
 
                                    # Not in the source tree, so doesn't have to be
4022
 
                                    # included.
4023
 
                                    continue
4024
 
                                # Path of the entry itself.
4025
 
 
4026
 
                                self.search_specific_file_parents.add(
4027
 
                                    osutils.pathjoin(*entry[0][:2]))
4028
 
                if changed or self.include_unchanged:
4029
 
                    yield result
4030
 
            self.searched_exact_paths.add(path_utf8)
4031
 
 
4032
 
    def _path_info(self, utf8_path, unicode_path):
4033
 
        """Generate path_info for unicode_path.
4034
 
 
4035
 
        :return: None if unicode_path does not exist, or a path_info tuple.
4036
 
        """
4037
 
        abspath = self.tree.abspath(unicode_path)
4038
 
        try:
4039
 
            stat = os.lstat(abspath)
4040
 
        except OSError, e:
4041
 
            if e.errno == errno.ENOENT:
4042
 
                # the path does not exist.
4043
 
                return None
4044
 
            else:
4045
 
                raise
4046
 
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
4047
 
        dir_info = (utf8_path, utf8_basename,
4048
 
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
4049
 
            abspath)
4050
 
        if dir_info[2] == 'directory':
4051
 
            if self.tree._directory_is_tree_reference(
4052
 
                unicode_path):
4053
 
                self.root_dir_info = self.root_dir_info[:2] + \
4054
 
                    ('tree-reference',) + self.root_dir_info[3:]
4055
 
        return dir_info
4056
 
 
4057
 
 
4058
2591
# Try to load the compiled form if possible
4059
2592
try:
4060
 
    from bzrlib._dirstate_helpers_pyx import (
4061
 
        _read_dirblocks,
4062
 
        bisect_dirblock,
4063
 
        _bisect_path_left,
4064
 
        _bisect_path_right,
4065
 
        cmp_by_dirs,
4066
 
        ProcessEntryC as _process_entry,
4067
 
        update_entry as update_entry,
 
2593
    from bzrlib._dirstate_helpers_c import (
 
2594
        _read_dirblocks_c as _read_dirblocks,
 
2595
        bisect_dirblock_c as bisect_dirblock,
 
2596
        _bisect_path_left_c as _bisect_path_left,
 
2597
        _bisect_path_right_c as _bisect_path_right,
 
2598
        cmp_by_dirs_c as cmp_by_dirs,
4068
2599
        )
4069
 
except ImportError, e:
4070
 
    osutils.failed_to_load_extension(e)
 
2600
except ImportError:
4071
2601
    from bzrlib._dirstate_helpers_py import (
4072
 
        _read_dirblocks,
4073
 
        bisect_dirblock,
4074
 
        _bisect_path_left,
4075
 
        _bisect_path_right,
4076
 
        cmp_by_dirs,
 
2602
        _read_dirblocks_py as _read_dirblocks,
 
2603
        bisect_dirblock_py as bisect_dirblock,
 
2604
        _bisect_path_left_py as _bisect_path_left,
 
2605
        _bisect_path_right_py as _bisect_path_right,
 
2606
        cmp_by_dirs_py as cmp_by_dirs,
4077
2607
        )
4078
 
    # FIXME: It would be nice to be able to track moved lines so that the
4079
 
    # corresponding python code can be moved to the _dirstate_helpers_py
4080
 
    # module. I don't want to break the history for this important piece of
4081
 
    # code so I left the code here -- vila 20090622
4082
 
    update_entry = py_update_entry
4083
 
    _process_entry = ProcessEntryPython