/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to breezy/bzr/dirstate.py

  • Committer: Jelmer Vernooij
  • Date: 2017-07-24 01:09:25 UTC
  • mfrom: (6740 trunk)
  • mto: This revision was merged to the branch mainline in revision 6743.
  • Revision ID: jelmer@jelmer.uk-20170724010925-nted35vp0ufbs3p2
Merge trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
20
20
lines by NL. The field delimiters are ommitted in the grammar, line delimiters
21
21
are not - this is done for clarity of reading. All string data is in utf8.
22
22
 
23
 
MINIKIND = "f" | "d" | "l" | "a" | "r" | "t";
24
 
NL = "\n";
25
 
NULL = "\0";
26
 
WHOLE_NUMBER = {digit}, digit;
27
 
BOOLEAN = "y" | "n";
28
 
REVISION_ID = a non-empty utf8 string;
29
 
 
30
 
dirstate format = header line, full checksum, row count, parent details,
31
 
 ghost_details, entries;
32
 
header line = "#bazaar dirstate flat format 3", NL;
33
 
full checksum = "crc32: ", ["-"], WHOLE_NUMBER, NL;
34
 
row count = "num_entries: ", WHOLE_NUMBER, NL;
35
 
parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
36
 
ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
37
 
entries = {entry};
38
 
entry = entry_key, current_entry_details, {parent_entry_details};
39
 
entry_key = dirname,  basename, fileid;
40
 
current_entry_details = common_entry_details, working_entry_details;
41
 
parent_entry_details = common_entry_details, history_entry_details;
42
 
common_entry_details = MINIKIND, fingerprint, size, executable
43
 
working_entry_details = packed_stat
44
 
history_entry_details = REVISION_ID;
45
 
executable = BOOLEAN;
46
 
size = WHOLE_NUMBER;
47
 
fingerprint = a nonempty utf8 sequence with meaning defined by minikind.
48
 
 
49
 
Given this definition, the following is useful to know:
50
 
entry (aka row) - all the data for a given key.
51
 
entry[0]: The key (dirname, basename, fileid)
52
 
entry[0][0]: dirname
53
 
entry[0][1]: basename
54
 
entry[0][2]: fileid
55
 
entry[1]: The tree(s) data for this path and id combination.
56
 
entry[1][0]: The current tree
57
 
entry[1][1]: The second tree
58
 
 
59
 
For an entry for a tree, we have (using tree 0 - current tree) to demonstrate:
60
 
entry[1][0][0]: minikind
61
 
entry[1][0][1]: fingerprint
62
 
entry[1][0][2]: size
63
 
entry[1][0][3]: executable
64
 
entry[1][0][4]: packed_stat
65
 
OR (for non tree-0)
66
 
entry[1][1][4]: revision_id
 
23
::
 
24
 
 
25
    MINIKIND = "f" | "d" | "l" | "a" | "r" | "t";
 
26
    NL = "\\n";
 
27
    NULL = "\\0";
 
28
    WHOLE_NUMBER = {digit}, digit;
 
29
    BOOLEAN = "y" | "n";
 
30
    REVISION_ID = a non-empty utf8 string;
 
31
    
 
32
    dirstate format = header line, full checksum, row count, parent details,
 
33
     ghost_details, entries;
 
34
    header line = "#bazaar dirstate flat format 3", NL;
 
35
    full checksum = "crc32: ", ["-"], WHOLE_NUMBER, NL;
 
36
    row count = "num_entries: ", WHOLE_NUMBER, NL;
 
37
    parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
 
38
    ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
 
39
    entries = {entry};
 
40
    entry = entry_key, current_entry_details, {parent_entry_details};
 
41
    entry_key = dirname,  basename, fileid;
 
42
    current_entry_details = common_entry_details, working_entry_details;
 
43
    parent_entry_details = common_entry_details, history_entry_details;
 
44
    common_entry_details = MINIKIND, fingerprint, size, executable
 
45
    working_entry_details = packed_stat
 
46
    history_entry_details = REVISION_ID;
 
47
    executable = BOOLEAN;
 
48
    size = WHOLE_NUMBER;
 
49
    fingerprint = a nonempty utf8 sequence with meaning defined by minikind.
 
50
 
 
51
Given this definition, the following is useful to know::
 
52
 
 
53
    entry (aka row) - all the data for a given key.
 
54
    entry[0]: The key (dirname, basename, fileid)
 
55
    entry[0][0]: dirname
 
56
    entry[0][1]: basename
 
57
    entry[0][2]: fileid
 
58
    entry[1]: The tree(s) data for this path and id combination.
 
59
    entry[1][0]: The current tree
 
60
    entry[1][1]: The second tree
 
61
 
 
62
For an entry for a tree, we have (using tree 0 - current tree) to demonstrate::
 
63
 
 
64
    entry[1][0][0]: minikind
 
65
    entry[1][0][1]: fingerprint
 
66
    entry[1][0][2]: size
 
67
    entry[1][0][3]: executable
 
68
    entry[1][0][4]: packed_stat
 
69
 
 
70
OR (for non tree-0)::
 
71
 
 
72
    entry[1][1][4]: revision_id
67
73
 
68
74
There may be multiple rows at the root, one per id present in the root, so the
69
 
in memory root row is now:
70
 
self._dirblocks[0] -> ('', [entry ...]),
71
 
and the entries in there are
72
 
entries[0][0]: ''
73
 
entries[0][1]: ''
74
 
entries[0][2]: file_id
75
 
entries[1][0]: The tree data for the current tree for this fileid at /
76
 
etc.
77
 
 
78
 
Kinds:
79
 
'r' is a relocated entry: This path is not present in this tree with this id,
80
 
    but the id can be found at another location. The fingerprint is used to
81
 
    point to the target location.
82
 
'a' is an absent entry: In that tree the id is not present at this path.
83
 
'd' is a directory entry: This path in this tree is a directory with the
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.
88
 
'l' is a symlink entry: As for directory, but a symlink. The fingerprint is the
89
 
    link target.
90
 
't' is a reference to a nested subtree; the fingerprint is the referenced
91
 
    revision.
 
75
in memory root row is now::
 
76
 
 
77
    self._dirblocks[0] -> ('', [entry ...]),
 
78
 
 
79
and the entries in there are::
 
80
 
 
81
    entries[0][0]: ''
 
82
    entries[0][1]: ''
 
83
    entries[0][2]: file_id
 
84
    entries[1][0]: The tree data for the current tree for this fileid at /
 
85
    etc.
 
86
 
 
87
Kinds::
 
88
 
 
89
    'r' is a relocated entry: This path is not present in this tree with this
 
90
        id, but the id can be found at another location. The fingerprint is
 
91
        used to point to the target location.
 
92
    'a' is an absent entry: In that tree the id is not present at this path.
 
93
    'd' is a directory entry: This path in this tree is a directory with the
 
94
        current file id. There is no fingerprint for directories.
 
95
    'f' is a file entry: As for directory, but it's a file. The fingerprint is
 
96
        the sha1 value of the file's canonical form, i.e. after any read
 
97
        filters have been applied to the convenience form stored in the working
 
98
        tree.
 
99
    'l' is a symlink entry: As for directory, but a symlink. The fingerprint is
 
100
        the link target.
 
101
    't' is a reference to a nested subtree; the fingerprint is the referenced
 
102
        revision.
92
103
 
93
104
Ordering:
94
105
 
95
 
The entries on disk and in memory are ordered according to the following keys:
 
106
The entries on disk and in memory are ordered according to the following keys::
96
107
 
97
108
    directory, as a list of components
98
109
    filename
99
110
    file-id
100
111
 
101
112
--- Format 1 had the following different definition: ---
102
 
rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
103
 
    WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
104
 
    {PARENT ROW}
105
 
PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
106
 
    basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
107
 
    SHA1
 
113
 
 
114
::
 
115
 
 
116
    rows = dirname, NULL, basename, NULL, MINIKIND, NULL, fileid_utf8, NULL,
 
117
        WHOLE NUMBER (* size *), NULL, packed stat, NULL, sha1|symlink target,
 
118
        {PARENT ROW}
 
119
    PARENT ROW = NULL, revision_utf8, NULL, MINIKIND, NULL, dirname, NULL,
 
120
        basename, NULL, WHOLE NUMBER (* size *), NULL, "y" | "n", NULL,
 
121
        SHA1
108
122
 
109
123
PARENT ROW's are emitted for every parent that is not in the ghosts details
110
124
line. That is, if the parents are foo, bar, baz, and the ghosts are bar, then
135
149
----
136
150
 
137
151
Design priorities:
138
 
 1) Fast end to end use for bzr's top 5 uses cases. (commmit/diff/status/merge/???)
139
 
 2) fall back current object model as needed.
140
 
 3) scale usably to the largest trees known today - say 50K entries. (mozilla
 
152
 1. Fast end to end use for bzr's top 5 uses cases. (commmit/diff/status/merge/???)
 
153
 2. fall back current object model as needed.
 
154
 3. scale usably to the largest trees known today - say 50K entries. (mozilla
141
155
    is an example of this)
142
156
 
143
157
 
144
158
Locking:
 
159
 
145
160
 Eventually reuse dirstate objects across locks IFF the dirstate file has not
146
161
 been modified, but will require that we flush/ignore cached stat-hit data
147
162
 because we won't want to restat all files on disk just because a lock was
148
163
 acquired, yet we cannot trust the data after the previous lock was released.
149
164
 
150
 
Memory representation:
 
165
Memory representation::
 
166
 
151
167
 vector of all directories, and vector of the childen ?
152
168
   i.e.
153
169
     root_entrie = (direntry for root, [parent_direntries_for_root]),
167
183
    - What's the risk of error here? Once we have the base format being processed
168
184
      we should have a net win regardless of optimality. So we are going to
169
185
      go with what seems reasonable.
 
186
 
170
187
open questions:
171
188
 
172
189
Maybe we should do a test profile of the core structure - 10K simulated
201
218
 
202
219
"""
203
220
 
 
221
from __future__ import absolute_import
 
222
 
204
223
import bisect
205
 
import binascii
206
224
import errno
207
225
import operator
208
226
import os
209
227
from stat import S_IEXEC
210
228
import stat
211
 
import struct
212
229
import sys
213
230
import time
214
231
import zlib
215
232
 
216
 
from bzrlib import (
 
233
from . import (
 
234
    inventory,
 
235
    )
 
236
from .. import (
217
237
    cache_utf8,
 
238
    config,
218
239
    debug,
219
240
    errors,
220
 
    inventory,
221
241
    lock,
222
242
    osutils,
 
243
    static_tuple,
223
244
    trace,
 
245
    urlutils,
 
246
    )
 
247
from ..sixish import (
 
248
    range,
 
249
    viewitems,
 
250
    viewvalues,
224
251
    )
225
252
 
226
253
 
231
258
ERROR_DIRECTORY = 267
232
259
 
233
260
 
234
 
if not getattr(struct, '_compile', None):
235
 
    # Cannot pre-compile the dirstate pack_stat
236
 
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
237
 
        """Convert stat values into a packed representation."""
238
 
        return _encode(_pack('>LLLLLL', st.st_size, int(st.st_mtime),
239
 
            int(st.st_ctime), st.st_dev, st.st_ino & 0xFFFFFFFF,
240
 
            st.st_mode))[:-1]
241
 
else:
242
 
    # compile the struct compiler we need, so as to only do it once
243
 
    from _struct import Struct
244
 
    _compiled_pack = Struct('>LLLLLL').pack
245
 
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=_compiled_pack):
246
 
        """Convert stat values into a packed representation."""
247
 
        # jam 20060614 it isn't really worth removing more entries if we
248
 
        # are going to leave it in packed form.
249
 
        # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
250
 
        # With all entries, filesize is 5.9M and read time is maybe 280ms
251
 
        # well within the noise margin
252
 
 
253
 
        # base64 encoding always adds a final newline, so strip it off
254
 
        # The current version
255
 
        return _encode(_pack(st.st_size, int(st.st_mtime), int(st.st_ctime),
256
 
            st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
257
 
        # This is 0.060s / 1.520s faster by not encoding as much information
258
 
        # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
259
 
        # This is not strictly faster than _encode(_pack())[:-1]
260
 
        # return '%X.%X.%X.%X.%X.%X' % (
261
 
        #      st.st_size, int(st.st_mtime), int(st.st_ctime),
262
 
        #      st.st_dev, st.st_ino, st.st_mode)
263
 
        # Similar to the _encode(_pack('>LL'))
264
 
        # return '%X.%X' % (int(st.st_mtime), st.st_mode)
 
261
class DirstateCorrupt(errors.BzrError):
 
262
 
 
263
    _fmt = "The dirstate file (%(state)s) appears to be corrupt: %(msg)s"
 
264
 
 
265
    def __init__(self, state, msg):
 
266
        errors.BzrError.__init__(self)
 
267
        self.state = state
 
268
        self.msg = msg
265
269
 
266
270
 
267
271
class SHA1Provider(object):
322
326
    """
323
327
 
324
328
    _kind_to_minikind = {
325
 
            'absent': 'a',
326
 
            'file': 'f',
327
 
            'directory': 'd',
328
 
            'relocated': 'r',
329
 
            'symlink': 'l',
330
 
            'tree-reference': 't',
 
329
            'absent': b'a',
 
330
            'file': b'f',
 
331
            'directory': b'd',
 
332
            'relocated': b'r',
 
333
            'symlink': b'l',
 
334
            'tree-reference': b't',
331
335
        }
332
336
    _minikind_to_kind = {
333
 
            'a': 'absent',
334
 
            'f': 'file',
335
 
            'd': 'directory',
336
 
            'l':'symlink',
337
 
            'r': 'relocated',
338
 
            't': 'tree-reference',
 
337
            b'a': 'absent',
 
338
            b'f': 'file',
 
339
            b'd': 'directory',
 
340
            b'l':'symlink',
 
341
            b'r': 'relocated',
 
342
            b't': 'tree-reference',
339
343
        }
340
344
    _stat_to_minikind = {
341
 
        stat.S_IFDIR:'d',
342
 
        stat.S_IFREG:'f',
343
 
        stat.S_IFLNK:'l',
 
345
        stat.S_IFDIR:b'd',
 
346
        stat.S_IFREG:b'f',
 
347
        stat.S_IFLNK:b'l',
344
348
    }
345
 
    _to_yesno = {True:'y', False: 'n'} # TODO profile the performance gain
 
349
    _to_yesno = {True: b'y', False: b'n'} # TODO profile the performance gain
346
350
     # of using int conversion rather than a dict here. AND BLAME ANDREW IF
347
351
     # it is faster.
348
352
 
354
358
    NOT_IN_MEMORY = 0
355
359
    IN_MEMORY_UNMODIFIED = 1
356
360
    IN_MEMORY_MODIFIED = 2
 
361
    IN_MEMORY_HASH_MODIFIED = 3 # Only hash-cache updates
357
362
 
358
363
    # A pack_stat (the x's) that is just noise and will never match the output
359
364
    # of base64 encode.
360
 
    NULLSTAT = 'x' * 32
361
 
    NULL_PARENT_DETAILS = ('a', '', 0, False, '')
362
 
 
363
 
    HEADER_FORMAT_2 = '#bazaar dirstate flat format 2\n'
364
 
    HEADER_FORMAT_3 = '#bazaar dirstate flat format 3\n'
365
 
 
366
 
    def __init__(self, path, sha1_provider):
 
365
    NULLSTAT = b'x' * 32
 
366
    NULL_PARENT_DETAILS = static_tuple.StaticTuple(b'a', b'', 0, False, b'')
 
367
 
 
368
    HEADER_FORMAT_2 = b'#bazaar dirstate flat format 2\n'
 
369
    HEADER_FORMAT_3 = b'#bazaar dirstate flat format 3\n'
 
370
 
 
371
    def __init__(self, path, sha1_provider, worth_saving_limit=0):
367
372
        """Create a  DirState object.
368
373
 
369
374
        :param path: The path at which the dirstate file on disk should live.
370
375
        :param sha1_provider: an object meeting the SHA1Provider interface.
 
376
        :param worth_saving_limit: when the exact number of hash changed
 
377
            entries is known, only bother saving the dirstate if more than
 
378
            this count of entries have changed.
 
379
            -1 means never save hash changes, 0 means always save hash changes.
371
380
        """
372
381
        # _header_state and _dirblock_state represent the current state
373
382
        # of the dirstate metadata and the per-row data respectiely.
410
419
        # during commit.
411
420
        self._last_block_index = None
412
421
        self._last_entry_index = None
 
422
        # The set of known hash changes
 
423
        self._known_hash_changes = set()
 
424
        # How many hash changed entries can we have without saving
 
425
        self._worth_saving_limit = worth_saving_limit
 
426
        self._config_stack = config.LocationStack(urlutils.local_path_to_url(
 
427
            path))
413
428
 
414
429
    def __repr__(self):
415
430
        return "%s(%r)" % \
416
431
            (self.__class__.__name__, self._filename)
417
432
 
 
433
    def _mark_modified(self, hash_changed_entries=None, header_modified=False):
 
434
        """Mark this dirstate as modified.
 
435
 
 
436
        :param hash_changed_entries: if non-None, mark just these entries as
 
437
            having their hash modified.
 
438
        :param header_modified: mark the header modified as well, not just the
 
439
            dirblocks.
 
440
        """
 
441
        #trace.mutter_callsite(3, "modified hash entries: %s", hash_changed_entries)
 
442
        if hash_changed_entries:
 
443
            self._known_hash_changes.update([e[0] for e in hash_changed_entries])
 
444
            if self._dirblock_state in (DirState.NOT_IN_MEMORY,
 
445
                                        DirState.IN_MEMORY_UNMODIFIED):
 
446
                # If the dirstate is already marked a IN_MEMORY_MODIFIED, then
 
447
                # that takes precedence.
 
448
                self._dirblock_state = DirState.IN_MEMORY_HASH_MODIFIED
 
449
        else:
 
450
            # TODO: Since we now have a IN_MEMORY_HASH_MODIFIED state, we
 
451
            #       should fail noisily if someone tries to set
 
452
            #       IN_MEMORY_MODIFIED but we don't have a write-lock!
 
453
            # We don't know exactly what changed so disable smart saving
 
454
            self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
455
        if header_modified:
 
456
            self._header_state = DirState.IN_MEMORY_MODIFIED
 
457
 
 
458
    def _mark_unmodified(self):
 
459
        """Mark this dirstate as unmodified."""
 
460
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
 
461
        self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
462
        self._known_hash_changes = set()
 
463
 
418
464
    def add(self, path, file_id, kind, stat, fingerprint):
419
465
        """Add a path to be tracked.
420
466
 
457
503
        utf8path = (dirname + '/' + basename).strip('/').encode('utf8')
458
504
        dirname, basename = osutils.split(utf8path)
459
505
        # uses __class__ for speed; the check is needed for safety
460
 
        if file_id.__class__ is not str:
 
506
        if file_id.__class__ is not bytes:
461
507
            raise AssertionError(
462
508
                "must be a utf8 file_id not %s" % (type(file_id), ))
463
509
        # Make sure the file_id does not exist in this tree
464
510
        rename_from = None
465
511
        file_id_entry = self._get_entry(0, fileid_utf8=file_id, include_deleted=True)
466
512
        if file_id_entry != (None, None):
467
 
            if file_id_entry[1][0][0] == 'a':
 
513
            if file_id_entry[1][0][0] == b'a':
468
514
                if file_id_entry[0] != (dirname, basename, file_id):
469
515
                    # set the old name's current operation to rename
470
516
                    self.update_minimal(file_id_entry[0],
487
533
            entry_index, _ = self._find_entry_index(first_key, block)
488
534
            while (entry_index < len(block) and
489
535
                block[entry_index][0][0:2] == first_key[0:2]):
490
 
                if block[entry_index][1][0][0] not in 'ar':
 
536
                if block[entry_index][1][0][0] not in (b'a', b'r'):
491
537
                    # this path is in the dirstate in the current tree.
492
 
                    raise Exception, "adding already added path!"
 
538
                    raise Exception("adding already added path!")
493
539
                entry_index += 1
494
540
        else:
495
541
            # The block where we want to put the file is not present. But it
516
562
                old_path_utf8 = '%s/%s' % rename_from
517
563
            else:
518
564
                old_path_utf8 = rename_from[1]
519
 
            parent_info[0] = ('r', old_path_utf8, 0, False, '')
 
565
            parent_info[0] = (b'r', old_path_utf8, 0, False, b'')
520
566
        if kind == 'file':
521
567
            entry_data = entry_key, [
522
568
                (minikind, fingerprint, size, False, packed_stat),
523
569
                ] + parent_info
524
570
        elif kind == 'directory':
525
571
            entry_data = entry_key, [
526
 
                (minikind, '', 0, False, packed_stat),
 
572
                (minikind, b'', 0, False, packed_stat),
527
573
                ] + parent_info
528
574
        elif kind == 'symlink':
529
575
            entry_data = entry_key, [
539
585
        if not present:
540
586
            block.insert(entry_index, entry_data)
541
587
        else:
542
 
            if block[entry_index][1][0][0] != 'a':
 
588
            if block[entry_index][1][0][0] != b'a':
543
589
                raise AssertionError(" %r(%r) already added" % (basename, file_id))
544
590
            block[entry_index][1][0] = entry_data[1][0]
545
591
 
546
592
        if kind == 'directory':
547
593
           # insert a new dirblock
548
594
           self._ensure_block(block_index, entry_index, utf8path)
549
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
595
        self._mark_modified()
550
596
        if self._id_index:
551
 
            self._id_index.setdefault(entry_key[2], set()).add(entry_key)
 
597
            self._add_to_id_index(self._id_index, entry_key)
552
598
 
553
599
    def _bisect(self, paths):
554
600
        """Bisect through the disk structure for specific rows.
618
664
            block = state_file.read(read_size)
619
665
 
620
666
            start = mid
621
 
            entries = block.split('\n')
 
667
            entries = block.split(b'\n')
622
668
 
623
669
            if len(entries) < 2:
624
670
                # We didn't find a '\n', so we cannot have found any records.
632
678
            # Check the first and last entries, in case they are partial, or if
633
679
            # we don't care about the rest of this page
634
680
            first_entry_num = 0
635
 
            first_fields = entries[0].split('\0')
 
681
            first_fields = entries[0].split(b'\0')
636
682
            if len(first_fields) < entry_field_count:
637
683
                # We didn't get the complete first entry
638
684
                # so move start, and grab the next, which
639
685
                # should be a full entry
640
686
                start += len(entries[0])+1
641
 
                first_fields = entries[1].split('\0')
 
687
                first_fields = entries[1].split(b'\0')
642
688
                first_entry_num = 1
643
689
 
644
690
            if len(first_fields) <= 2:
652
698
                # after this first record.
653
699
                after = start
654
700
                if first_fields[1]:
655
 
                    first_path = first_fields[1] + '/' + first_fields[2]
 
701
                    first_path = first_fields[1] + b'/' + first_fields[2]
656
702
                else:
657
703
                    first_path = first_fields[2]
658
704
                first_loc = _bisect_path_left(cur_files, first_path)
668
714
 
669
715
                # Parse the last entry
670
716
                last_entry_num = len(entries)-1
671
 
                last_fields = entries[last_entry_num].split('\0')
 
717
                last_fields = entries[last_entry_num].split(b'\0')
672
718
                if len(last_fields) < entry_field_count:
673
719
                    # The very last hunk was not complete,
674
720
                    # read the previous hunk
675
721
                    after = mid + len(block) - len(entries[-1])
676
722
                    last_entry_num -= 1
677
 
                    last_fields = entries[last_entry_num].split('\0')
 
723
                    last_fields = entries[last_entry_num].split(b'\0')
678
724
                else:
679
725
                    after = mid + len(block)
680
726
 
681
727
                if last_fields[1]:
682
 
                    last_path = last_fields[1] + '/' + last_fields[2]
 
728
                    last_path = last_fields[1] + b'/' + last_fields[2]
683
729
                else:
684
730
                    last_path = last_fields[2]
685
731
                last_loc = _bisect_path_right(post, last_path)
705
751
                    # careful if we should append rather than overwrite
706
752
                    if last_entry_num != first_entry_num:
707
753
                        paths.setdefault(last_path, []).append(last_fields)
708
 
                    for num in xrange(first_entry_num+1, last_entry_num):
 
754
                    for num in range(first_entry_num+1, last_entry_num):
709
755
                        # TODO: jam 20070223 We are already splitting here, so
710
756
                        #       shouldn't we just split the whole thing rather
711
757
                        #       than doing the split again in add_one_record?
712
 
                        fields = entries[num].split('\0')
 
758
                        fields = entries[num].split(b'\0')
713
759
                        if fields[1]:
714
 
                            path = fields[1] + '/' + fields[2]
 
760
                            path = fields[1] + b'/' + fields[2]
715
761
                        else:
716
762
                            path = fields[2]
717
763
                        paths.setdefault(path, []).append(fields)
810
856
            block = state_file.read(read_size)
811
857
 
812
858
            start = mid
813
 
            entries = block.split('\n')
 
859
            entries = block.split(b'\n')
814
860
 
815
861
            if len(entries) < 2:
816
862
                # We didn't find a '\n', so we cannot have found any records.
824
870
            # Check the first and last entries, in case they are partial, or if
825
871
            # we don't care about the rest of this page
826
872
            first_entry_num = 0
827
 
            first_fields = entries[0].split('\0')
 
873
            first_fields = entries[0].split(b'\0')
828
874
            if len(first_fields) < entry_field_count:
829
875
                # We didn't get the complete first entry
830
876
                # so move start, and grab the next, which
831
877
                # should be a full entry
832
878
                start += len(entries[0])+1
833
 
                first_fields = entries[1].split('\0')
 
879
                first_fields = entries[1].split(b'\0')
834
880
                first_entry_num = 1
835
881
 
836
882
            if len(first_fields) <= 1:
857
903
 
858
904
                # Parse the last entry
859
905
                last_entry_num = len(entries)-1
860
 
                last_fields = entries[last_entry_num].split('\0')
 
906
                last_fields = entries[last_entry_num].split(b'\0')
861
907
                if len(last_fields) < entry_field_count:
862
908
                    # The very last hunk was not complete,
863
909
                    # read the previous hunk
864
910
                    after = mid + len(block) - len(entries[-1])
865
911
                    last_entry_num -= 1
866
 
                    last_fields = entries[last_entry_num].split('\0')
 
912
                    last_fields = entries[last_entry_num].split(b'\0')
867
913
                else:
868
914
                    after = mid + len(block)
869
915
 
891
937
                    # careful if we should append rather than overwrite
892
938
                    if last_entry_num != first_entry_num:
893
939
                        paths.setdefault(last_dir, []).append(last_fields)
894
 
                    for num in xrange(first_entry_num+1, last_entry_num):
 
940
                    for num in range(first_entry_num+1, last_entry_num):
895
941
                        # TODO: jam 20070223 We are already splitting here, so
896
942
                        #       shouldn't we just split the whole thing rather
897
943
                        #       than doing the split again in add_one_record?
898
 
                        fields = entries[num].split('\0')
 
944
                        fields = entries[num].split(b'\0')
899
945
                        paths.setdefault(fields[1], []).append(fields)
900
946
 
901
947
                    for cur_dir in middle_files:
943
989
            # Directories that need to be read
944
990
            pending_dirs = set()
945
991
            paths_to_search = set()
946
 
            for entry_list in newly_found.itervalues():
 
992
            for entry_list in viewvalues(newly_found):
947
993
                for dir_name_id, trees_info in entry_list:
948
994
                    found[dir_name_id] = trees_info
949
995
                    found_dir_names.add(dir_name_id[:2])
950
996
                    is_dir = False
951
997
                    for tree_info in trees_info:
952
998
                        minikind = tree_info[0]
953
 
                        if minikind == 'd':
 
999
                        if minikind == b'd':
954
1000
                            if is_dir:
955
1001
                                # We already processed this one as a directory,
956
1002
                                # we don't need to do the extra work again.
960
1006
                            is_dir = True
961
1007
                            if path not in processed_dirs:
962
1008
                                pending_dirs.add(path)
963
 
                        elif minikind == 'r':
 
1009
                        elif minikind == b'r':
964
1010
                            # Rename, we need to directly search the target
965
1011
                            # which is contained in the fingerprint column
966
1012
                            dir_name = osutils.split(tree_info[1])
993
1039
            return
994
1040
        # only require all dirblocks if we are doing a full-pass removal.
995
1041
        self._read_dirblocks_if_needed()
996
 
        dead_patterns = set([('a', 'r'), ('a', 'a'), ('r', 'r'), ('r', 'a')])
 
1042
        dead_patterns = {(b'a', b'r'), (b'a', b'a'), (b'r', b'r'), (b'r', b'a')}
997
1043
        def iter_entries_removable():
998
1044
            for block in self._dirblocks:
999
1045
                deleted_positions = []
1018
1064
 
1019
1065
        self._ghosts = []
1020
1066
        self._parents = [parents[0]]
1021
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1022
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
1067
        self._mark_modified(header_modified=True)
1023
1068
 
1024
1069
    def _empty_parent_info(self):
1025
1070
        return [DirState.NULL_PARENT_DETAILS] * (len(self._parents) -
1045
1090
        :param dirname: The utf8 dirname to ensure there is a block for.
1046
1091
        :return: The index for the block.
1047
1092
        """
1048
 
        if dirname == '' and parent_row_index == 0 and parent_block_index == 0:
 
1093
        if dirname == b'' and parent_row_index == 0 and parent_block_index == 0:
1049
1094
            # This is the signature of the root row, and the
1050
1095
            # contents-of-root row is always index 1
1051
1096
            return 1
1052
1097
        # the basename of the directory must be the end of its full name.
1053
1098
        if not (parent_block_index == -1 and
1054
 
            parent_block_index == -1 and dirname == ''):
 
1099
            parent_block_index == -1 and dirname == b''):
1055
1100
            if not dirname.endswith(
1056
1101
                    self._dirblocks[parent_block_index][1][parent_row_index][0][1]):
1057
1102
                raise AssertionError("bad dirname %r" % dirname)
1058
 
        block_index, present = self._find_block_index_from_key((dirname, '', ''))
 
1103
        block_index, present = self._find_block_index_from_key((dirname, b'', b''))
1059
1104
        if not present:
1060
1105
            ## In future, when doing partial parsing, this should load and
1061
1106
            # populate the entire block.
1072
1117
            to prevent unneeded overhead when callers have a sorted list already.
1073
1118
        :return: Nothing.
1074
1119
        """
1075
 
        if new_entries[0][0][0:2] != ('', ''):
 
1120
        if new_entries[0][0][0:2] != (b'', b''):
1076
1121
            raise AssertionError(
1077
1122
                "Missing root row %r" % (new_entries[0][0],))
1078
1123
        # The two blocks here are deliberate: the root block and the
1079
1124
        # contents-of-root block.
1080
 
        self._dirblocks = [('', []), ('', [])]
 
1125
        self._dirblocks = [(b'', []), (b'', [])]
1081
1126
        current_block = self._dirblocks[0][1]
1082
 
        current_dirname = ''
1083
 
        root_key = ('', '')
 
1127
        current_dirname = b''
 
1128
        root_key = (b'', b'')
1084
1129
        append_entry = current_block.append
1085
1130
        for entry in new_entries:
1086
1131
            if entry[0][0] != current_dirname:
1102
1147
        # The above loop leaves the "root block" entries mixed with the
1103
1148
        # "contents-of-root block". But we don't want an if check on
1104
1149
        # all entries, so instead we just fix it up here.
1105
 
        if self._dirblocks[1] != ('', []):
 
1150
        if self._dirblocks[1] != (b'', []):
1106
1151
            raise ValueError("bad dirblock start %r" % (self._dirblocks[1],))
1107
1152
        root_block = []
1108
1153
        contents_of_root_block = []
1111
1156
                root_block.append(entry)
1112
1157
            else:
1113
1158
                contents_of_root_block.append(entry)
1114
 
        self._dirblocks[0] = ('', root_block)
1115
 
        self._dirblocks[1] = ('', contents_of_root_block)
 
1159
        self._dirblocks[0] = (b'', root_block)
 
1160
        self._dirblocks[1] = (b'', contents_of_root_block)
1116
1161
 
1117
1162
    def _entries_for_path(self, path):
1118
1163
        """Return a list with all the entries that match path for all ids."""
1119
1164
        dirname, basename = os.path.split(path)
1120
 
        key = (dirname, basename, '')
 
1165
        key = (dirname, basename, b'')
1121
1166
        block_index, present = self._find_block_index_from_key(key)
1122
1167
        if not present:
1123
1168
            # the block which should contain path is absent.
1146
1191
            # minikind
1147
1192
            entire_entry[tree_offset + 0] = tree_data[0]
1148
1193
            # size
1149
 
            entire_entry[tree_offset + 2] = str(tree_data[2])
 
1194
            entire_entry[tree_offset + 2] = b'%d' % tree_data[2]
1150
1195
            # executable
1151
1196
            entire_entry[tree_offset + 3] = DirState._to_yesno[tree_data[3]]
1152
 
        return '\0'.join(entire_entry)
 
1197
        return b'\0'.join(entire_entry)
1153
1198
 
1154
1199
    def _fields_per_entry(self):
1155
1200
        """How many null separated fields should be in each entry row.
1156
1201
 
1157
 
        Each line now has an extra '\n' field which is not used
 
1202
        Each line now has an extra '\\n' field which is not used
1158
1203
        so we just skip over it
1159
 
        entry size:
 
1204
 
 
1205
        entry size::
1160
1206
            3 fields for the key
1161
1207
            + number of fields per tree_data (5) * tree count
1162
1208
            + newline
1191
1237
 
1192
1238
        :return: The block index, True if the block for the key is present.
1193
1239
        """
1194
 
        if key[0:2] == ('', ''):
 
1240
        if key[0:2] == (b'', b''):
1195
1241
            return 0, True
1196
1242
        try:
1197
1243
            if (self._last_block_index is not None and
1263
1309
                    parent_trees.append((parent_id, parent_tree))
1264
1310
                    parent_tree.lock_read()
1265
1311
                result.set_parent_trees(parent_trees, [])
1266
 
                result.set_state_from_inventory(tree.inventory)
 
1312
                result.set_state_from_inventory(tree.root_inventory)
1267
1313
            finally:
1268
1314
                for revid, parent_tree in parent_trees:
1269
1315
                    parent_tree.unlock()
1275
1321
            raise
1276
1322
        return result
1277
1323
 
 
1324
    def _check_delta_is_valid(self, delta):
 
1325
        return list(inventory._check_delta_unique_ids(
 
1326
                    inventory._check_delta_unique_old_paths(
 
1327
                    inventory._check_delta_unique_new_paths(
 
1328
                    inventory._check_delta_ids_match_entry(
 
1329
                    inventory._check_delta_ids_are_valid(
 
1330
                    inventory._check_delta_new_path_entry_both_or_None(delta)))))))
 
1331
 
1278
1332
    def update_by_delta(self, delta):
1279
1333
        """Apply an inventory delta to the dirstate for tree 0
1280
1334
 
1298
1352
        new_ids = set()
1299
1353
        # This loop transforms the delta to single atomic operations that can
1300
1354
        # be executed and validated.
1301
 
        for old_path, new_path, file_id, inv_entry in sorted(
1302
 
            inventory._check_delta_unique_old_paths(
1303
 
            inventory._check_delta_unique_new_paths(
1304
 
            inventory._check_delta_ids_match_entry(
1305
 
            inventory._check_delta_ids_are_valid(
1306
 
            inventory._check_delta_new_path_entry_both_or_None(delta))))),
1307
 
            reverse=True):
 
1355
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
 
1356
        for old_path, new_path, file_id, inv_entry in delta:
 
1357
            if file_id.__class__ is not bytes:
 
1358
                raise AssertionError(
 
1359
                    "must be a utf8 file_id not %s" % (type(file_id), ))
1308
1360
            if (file_id in insertions) or (file_id in removals):
1309
 
                raise errors.InconsistentDelta(old_path or new_path, file_id,
 
1361
                self._raise_invalid(old_path or new_path, file_id,
1310
1362
                    "repeated file_id")
1311
1363
            if old_path is not None:
1312
1364
                old_path = old_path.encode('utf-8')
1315
1367
                new_ids.add(file_id)
1316
1368
            if new_path is not None:
1317
1369
                if inv_entry is None:
1318
 
                    raise errors.InconsistentDelta(new_path, file_id,
 
1370
                    self._raise_invalid(new_path, file_id,
1319
1371
                        "new_path with no entry")
1320
1372
                new_path = new_path.encode('utf-8')
1321
1373
                dirname_utf8, basename = osutils.split(new_path)
1323
1375
                    parents.add((dirname_utf8, inv_entry.parent_id))
1324
1376
                key = (dirname_utf8, basename, file_id)
1325
1377
                minikind = DirState._kind_to_minikind[inv_entry.kind]
1326
 
                if minikind == 't':
1327
 
                    fingerprint = inv_entry.reference_revision or ''
 
1378
                if minikind == b't':
 
1379
                    fingerprint = inv_entry.reference_revision or b''
1328
1380
                else:
1329
 
                    fingerprint = ''
 
1381
                    fingerprint = b''
1330
1382
                insertions[file_id] = (key, minikind, inv_entry.executable,
1331
1383
                                       fingerprint, new_path)
1332
1384
            # Transform moves into delete+add pairs
1351
1403
                                               fingerprint, new_child_path)
1352
1404
        self._check_delta_ids_absent(new_ids, delta, 0)
1353
1405
        try:
1354
 
            self._apply_removals(removals.iteritems())
1355
 
            self._apply_insertions(insertions.values())
 
1406
            self._apply_removals(viewitems(removals))
 
1407
            self._apply_insertions(viewvalues(insertions))
1356
1408
            # Validate parents
1357
1409
            self._after_delta_check_parents(parents, 0)
1358
 
        except errors.BzrError, e:
 
1410
        except errors.BzrError as e:
1359
1411
            self._changes_aborted = True
1360
1412
            if 'integrity error' not in str(e):
1361
1413
                raise
1362
1414
            # _get_entry raises BzrError when a request is inconsistent; we
1363
1415
            # want such errors to be shown as InconsistentDelta - and that 
1364
1416
            # fits the behaviour we trigger.
1365
 
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
 
1417
            raise errors.InconsistentDeltaDelta(delta,
 
1418
                "error from _get_entry. %s" % (e,))
1366
1419
 
1367
1420
    def _apply_removals(self, removals):
1368
1421
        for file_id, path in sorted(removals, reverse=True,
1373
1426
            try:
1374
1427
                entry = self._dirblocks[block_i][1][entry_i]
1375
1428
            except IndexError:
1376
 
                self._changes_aborted = True
1377
 
                raise errors.InconsistentDelta(path, file_id,
 
1429
                self._raise_invalid(path, file_id,
1378
1430
                    "Wrong path for old path.")
1379
 
            if not f_present or entry[1][0][0] in 'ar':
1380
 
                self._changes_aborted = True
1381
 
                raise errors.InconsistentDelta(path, file_id,
 
1431
            if not f_present or entry[1][0][0] in (b'a', b'r'):
 
1432
                self._raise_invalid(path, file_id,
1382
1433
                    "Wrong path for old path.")
1383
1434
            if file_id != entry[0][2]:
1384
 
                self._changes_aborted = True
1385
 
                raise errors.InconsistentDelta(path, file_id,
 
1435
                self._raise_invalid(path, file_id,
1386
1436
                    "Attempt to remove path has wrong id - found %r."
1387
1437
                    % entry[0][2])
1388
1438
            self._make_absent(entry)
1392
1442
            # is rare enough it shouldn't be an issue (famous last words?) RBC
1393
1443
            # 20080730.
1394
1444
            block_i, entry_i, d_present, f_present = \
1395
 
                self._get_block_entry_index(path, '', 0)
 
1445
                self._get_block_entry_index(path, b'', 0)
1396
1446
            if d_present:
1397
1447
                # The dir block is still present in the dirstate; this could
1398
1448
                # be due to it being in a parent tree, or a corrupt delta.
1399
1449
                for child_entry in self._dirblocks[block_i][1]:
1400
 
                    if child_entry[1][0][0] not in ('r', 'a'):
1401
 
                        self._changes_aborted = True
1402
 
                        raise errors.InconsistentDelta(path, entry[0][2],
 
1450
                    if child_entry[1][0][0] not in (b'r', b'a'):
 
1451
                        self._raise_invalid(path, entry[0][2],
1403
1452
                            "The file id was deleted but its children were "
1404
1453
                            "not deleted.")
1405
1454
 
1409
1458
                self.update_minimal(key, minikind, executable, fingerprint,
1410
1459
                                    path_utf8=path_utf8)
1411
1460
        except errors.NotVersionedError:
1412
 
            self._changes_aborted = True
1413
 
            raise errors.InconsistentDelta(path_utf8.decode('utf8'), key[2],
 
1461
            self._raise_invalid(path_utf8.decode('utf8'), key[2],
1414
1462
                "Missing parent")
1415
1463
 
1416
1464
    def update_basis_by_delta(self, delta, new_revid):
1424
1472
        Note that an exception during the operation of this method will leave
1425
1473
        the dirstate in a corrupt state where it should not be saved.
1426
1474
 
1427
 
        Finally, we expect all changes to be synchronising the basis tree with
1428
 
        the working tree.
1429
 
 
1430
1475
        :param new_revid: The new revision id for the trees parent.
1431
1476
        :param delta: An inventory delta (see apply_inventory_delta) describing
1432
1477
            the changes from the current left most parent revision to new_revid.
1444
1489
 
1445
1490
        self._parents[0] = new_revid
1446
1491
 
1447
 
        delta = sorted(delta, reverse=True)
 
1492
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
1448
1493
        adds = []
1449
1494
        changes = []
1450
1495
        deletes = []
1460
1505
        # expanding them recursively as needed.
1461
1506
        # At the same time, to reduce interface friction we convert the input
1462
1507
        # inventory entries to dirstate.
1463
 
        root_only = ('', '')
 
1508
        root_only = (b'', b'')
1464
1509
        # Accumulate parent references (path_utf8, id), to check for parentless
1465
1510
        # items or items placed under files/links/tree-references. We get
1466
1511
        # references from every item in the delta that is not a deletion and
1470
1515
        # ids.
1471
1516
        new_ids = set()
1472
1517
        for old_path, new_path, file_id, inv_entry in delta:
 
1518
            if file_id.__class__ is not bytes:
 
1519
                raise AssertionError(
 
1520
                    "must be a utf8 file_id not %s" % (type(file_id), ))
1473
1521
            if inv_entry is not None and file_id != inv_entry.file_id:
1474
 
                raise errors.InconsistentDelta(new_path, file_id,
 
1522
                self._raise_invalid(new_path, file_id,
1475
1523
                    "mismatched entry file_id %r" % inv_entry)
1476
 
            if new_path is not None:
 
1524
            if new_path is None:
 
1525
                new_path_utf8 = None
 
1526
            else:
1477
1527
                if inv_entry is None:
1478
 
                    raise errors.InconsistentDelta(new_path, file_id,
 
1528
                    self._raise_invalid(new_path, file_id,
1479
1529
                        "new_path with no entry")
1480
1530
                new_path_utf8 = encode(new_path)
1481
1531
                # note the parent for validation
1483
1533
                if basename_utf8:
1484
1534
                    parents.add((dirname_utf8, inv_entry.parent_id))
1485
1535
            if old_path is None:
1486
 
                adds.append((None, encode(new_path), file_id,
 
1536
                old_path_utf8 = None
 
1537
            else:
 
1538
                old_path_utf8 = encode(old_path)
 
1539
            if old_path is None:
 
1540
                adds.append((None, new_path_utf8, file_id,
1487
1541
                    inv_to_entry(inv_entry), True))
1488
1542
                new_ids.add(file_id)
1489
1543
            elif new_path is None:
1490
 
                deletes.append((encode(old_path), None, file_id, None, True))
1491
 
            elif (old_path, new_path) != root_only:
 
1544
                deletes.append((old_path_utf8, None, file_id, None, True))
 
1545
            elif (old_path, new_path) == root_only:
 
1546
                # change things in-place
 
1547
                # Note: the case of a parent directory changing its file_id
 
1548
                #       tends to break optimizations here, because officially
 
1549
                #       the file has actually been moved, it just happens to
 
1550
                #       end up at the same path. If we can figure out how to
 
1551
                #       handle that case, we can avoid a lot of add+delete
 
1552
                #       pairs for objects that stay put.
 
1553
                # elif old_path == new_path:
 
1554
                changes.append((old_path_utf8, new_path_utf8, file_id,
 
1555
                                inv_to_entry(inv_entry)))
 
1556
            else:
1492
1557
                # Renames:
1493
1558
                # Because renames must preserve their children we must have
1494
1559
                # processed all relocations and removes before hand. The sort
1504
1569
                self._update_basis_apply_deletes(deletes)
1505
1570
                deletes = []
1506
1571
                # Split into an add/delete pair recursively.
1507
 
                adds.append((None, new_path_utf8, file_id,
1508
 
                    inv_to_entry(inv_entry), False))
 
1572
                adds.append((old_path_utf8, new_path_utf8, file_id,
 
1573
                             inv_to_entry(inv_entry), False))
1509
1574
                # Expunge deletes that we've seen so that deleted/renamed
1510
1575
                # children of a rename directory are handled correctly.
1511
 
                new_deletes = reversed(list(self._iter_child_entries(1,
1512
 
                    encode(old_path))))
 
1576
                new_deletes = reversed(list(
 
1577
                    self._iter_child_entries(1, old_path_utf8)))
1513
1578
                # Remove the current contents of the tree at orig_path, and
1514
1579
                # reinsert at the correct new path.
1515
1580
                for entry in new_deletes:
1516
 
                    if entry[0][0]:
1517
 
                        source_path = entry[0][0] + '/' + entry[0][1]
 
1581
                    child_dirname, child_basename, child_file_id = entry[0]
 
1582
                    if child_dirname:
 
1583
                        source_path = child_dirname + b'/' + child_basename
1518
1584
                    else:
1519
 
                        source_path = entry[0][1]
 
1585
                        source_path = child_basename
1520
1586
                    if new_path_utf8:
1521
 
                        target_path = new_path_utf8 + source_path[len(old_path):]
 
1587
                        target_path = \
 
1588
                            new_path_utf8 + source_path[len(old_path_utf8):]
1522
1589
                    else:
1523
 
                        if old_path == '':
 
1590
                        if old_path_utf8 == b'':
1524
1591
                            raise AssertionError("cannot rename directory to"
1525
 
                                " itself")
1526
 
                        target_path = source_path[len(old_path) + 1:]
 
1592
                                                 " itself")
 
1593
                        target_path = source_path[len(old_path_utf8) + 1:]
1527
1594
                    adds.append((None, target_path, entry[0][2], entry[1][1], False))
1528
1595
                    deletes.append(
1529
1596
                        (source_path, target_path, entry[0][2], None, False))
1530
1597
                deletes.append(
1531
 
                    (encode(old_path), new_path, file_id, None, False))
1532
 
            else:
1533
 
                # changes to just the root should not require remove/insertion
1534
 
                # of everything.
1535
 
                changes.append((encode(old_path), encode(new_path), file_id,
1536
 
                    inv_to_entry(inv_entry)))
 
1598
                    (old_path_utf8, new_path_utf8, file_id, None, False))
 
1599
 
1537
1600
        self._check_delta_ids_absent(new_ids, delta, 1)
1538
1601
        try:
1539
1602
            # Finish expunging deletes/first half of renames.
1544
1607
            self._update_basis_apply_changes(changes)
1545
1608
            # Validate parents
1546
1609
            self._after_delta_check_parents(parents, 1)
1547
 
        except errors.BzrError, e:
 
1610
        except errors.BzrError as e:
1548
1611
            self._changes_aborted = True
1549
1612
            if 'integrity error' not in str(e):
1550
1613
                raise
1551
1614
            # _get_entry raises BzrError when a request is inconsistent; we
1552
 
            # want such errors to be shown as InconsistentDelta - and that 
1553
 
            # fits the behaviour we trigger. Partof this is driven by dirstate
1554
 
            # only supporting deltas that turn the basis into a closer fit to
1555
 
            # the active tree.
1556
 
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
 
1615
            # want such errors to be shown as InconsistentDelta - and that
 
1616
            # fits the behaviour we trigger.
 
1617
            raise errors.InconsistentDeltaDelta(delta,
 
1618
                "error from _get_entry. %s" % (e,))
1557
1619
 
1558
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1559
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
1620
        self._mark_modified(header_modified=True)
1560
1621
        self._id_index = None
1561
1622
        return
1562
1623
 
1566
1627
            return
1567
1628
        id_index = self._get_id_index()
1568
1629
        for file_id in new_ids:
1569
 
            for key in id_index.get(file_id, []):
 
1630
            for key in id_index.get(file_id, ()):
1570
1631
                block_i, entry_i, d_present, f_present = \
1571
1632
                    self._get_block_entry_index(key[0], key[1], tree_index)
1572
1633
                if not f_present:
1576
1637
                if entry[0][2] != file_id:
1577
1638
                    # Different file_id, so not what we want.
1578
1639
                    continue
1579
 
                # NB: No changes made before this helper is called, so no need
1580
 
                # to set the _changes_aborted flag.
1581
 
                raise errors.InconsistentDelta(
1582
 
                    ("%s/%s" % key[0:2]).decode('utf8'), file_id,
 
1640
                self._raise_invalid(("%s/%s" % key[0:2]).decode('utf8'), file_id,
1583
1641
                    "This file_id is new in the delta but already present in "
1584
1642
                    "the target")
1585
1643
 
 
1644
    def _raise_invalid(self, path, file_id, reason):
 
1645
        self._changes_aborted = True
 
1646
        raise errors.InconsistentDelta(path, file_id, reason)
 
1647
 
1586
1648
    def _update_basis_apply_adds(self, adds):
1587
1649
        """Apply a sequence of adds to tree 1 during update_basis_by_delta.
1588
1650
 
1596
1658
        """
1597
1659
        # Adds are accumulated partly from renames, so can be in any input
1598
1660
        # order - sort it.
1599
 
        adds.sort()
 
1661
        # TODO: we may want to sort in dirblocks order. That way each entry
 
1662
        #       will end up in the same directory, allowing the _get_entry
 
1663
        #       fast-path for looking up 2 items in the same dir work.
 
1664
        adds.sort(key=lambda x: x[1])
1600
1665
        # adds is now in lexographic order, which places all parents before
1601
1666
        # their children, so we can process it linearly.
1602
 
        absent = 'ar'
 
1667
        st = static_tuple.StaticTuple
1603
1668
        for old_path, new_path, file_id, new_details, real_add in adds:
1604
 
            # the entry for this file_id must be in tree 0.
1605
 
            entry = self._get_entry(0, file_id, new_path)
1606
 
            if entry[0] is None or entry[0][2] != file_id:
1607
 
                self._changes_aborted = True
1608
 
                raise errors.InconsistentDelta(new_path, file_id,
1609
 
                    'working tree does not contain new entry')
1610
 
            if real_add and entry[1][1][0] not in absent:
1611
 
                self._changes_aborted = True
1612
 
                raise errors.InconsistentDelta(new_path, file_id,
1613
 
                    'The entry was considered to be a genuinely new record,'
1614
 
                    ' but there was already an old record for it.')
1615
 
            # We don't need to update the target of an 'r' because the handling
1616
 
            # of renames turns all 'r' situations into a delete at the original
1617
 
            # location.
1618
 
            entry[1][1] = new_details
 
1669
            dirname, basename = osutils.split(new_path)
 
1670
            entry_key = st(dirname, basename, file_id)
 
1671
            block_index, present = self._find_block_index_from_key(entry_key)
 
1672
            if not present:
 
1673
                # The block where we want to put the file is not present.
 
1674
                # However, it might have just been an empty directory. Look for
 
1675
                # the parent in the basis-so-far before throwing an error.
 
1676
                parent_dir, parent_base = osutils.split(dirname)
 
1677
                parent_block_idx, parent_entry_idx, _, parent_present = \
 
1678
                    self._get_block_entry_index(parent_dir, parent_base, 1)
 
1679
                if not parent_present:
 
1680
                    self._raise_invalid(new_path, file_id,
 
1681
                        "Unable to find block for this record."
 
1682
                        " Was the parent added?")
 
1683
                self._ensure_block(parent_block_idx, parent_entry_idx, dirname)
 
1684
 
 
1685
            block = self._dirblocks[block_index][1]
 
1686
            entry_index, present = self._find_entry_index(entry_key, block)
 
1687
            if real_add:
 
1688
                if old_path is not None:
 
1689
                    self._raise_invalid(new_path, file_id,
 
1690
                        'considered a real add but still had old_path at %s'
 
1691
                        % (old_path,))
 
1692
            if present:
 
1693
                entry = block[entry_index]
 
1694
                basis_kind = entry[1][1][0]
 
1695
                if basis_kind == b'a':
 
1696
                    entry[1][1] = new_details
 
1697
                elif basis_kind == b'r':
 
1698
                    raise NotImplementedError()
 
1699
                else:
 
1700
                    self._raise_invalid(new_path, file_id,
 
1701
                        "An entry was marked as a new add"
 
1702
                        " but the basis target already existed")
 
1703
            else:
 
1704
                # The exact key was not found in the block. However, we need to
 
1705
                # check if there is a key next to us that would have matched.
 
1706
                # We only need to check 2 locations, because there are only 2
 
1707
                # trees present.
 
1708
                for maybe_index in range(entry_index-1, entry_index+1):
 
1709
                    if maybe_index < 0 or maybe_index >= len(block):
 
1710
                        continue
 
1711
                    maybe_entry = block[maybe_index]
 
1712
                    if maybe_entry[0][:2] != (dirname, basename):
 
1713
                        # Just a random neighbor
 
1714
                        continue
 
1715
                    if maybe_entry[0][2] == file_id:
 
1716
                        raise AssertionError(
 
1717
                            '_find_entry_index didnt find a key match'
 
1718
                            ' but walking the data did, for %s'
 
1719
                            % (entry_key,))
 
1720
                    basis_kind = maybe_entry[1][1][0]
 
1721
                    if basis_kind not in (b'a', b'r'):
 
1722
                        self._raise_invalid(new_path, file_id,
 
1723
                            "we have an add record for path, but the path"
 
1724
                            " is already present with another file_id %s"
 
1725
                            % (maybe_entry[0][2],))
 
1726
 
 
1727
                entry = (entry_key, [DirState.NULL_PARENT_DETAILS,
 
1728
                                     new_details])
 
1729
                block.insert(entry_index, entry)
 
1730
 
 
1731
            active_kind = entry[1][0][0]
 
1732
            if active_kind == b'a':
 
1733
                # The active record shows up as absent, this could be genuine,
 
1734
                # or it could be present at some other location. We need to
 
1735
                # verify.
 
1736
                id_index = self._get_id_index()
 
1737
                # The id_index may not be perfectly accurate for tree1, because
 
1738
                # we haven't been keeping it updated. However, it should be
 
1739
                # fine for tree0, and that gives us enough info for what we
 
1740
                # need
 
1741
                keys = id_index.get(file_id, ())
 
1742
                for key in keys:
 
1743
                    block_i, entry_i, d_present, f_present = \
 
1744
                        self._get_block_entry_index(key[0], key[1], 0)
 
1745
                    if not f_present:
 
1746
                        continue
 
1747
                    active_entry = self._dirblocks[block_i][1][entry_i]
 
1748
                    if (active_entry[0][2] != file_id):
 
1749
                        # Some other file is at this path, we don't need to
 
1750
                        # link it.
 
1751
                        continue
 
1752
                    real_active_kind = active_entry[1][0][0]
 
1753
                    if real_active_kind in (b'a', b'r'):
 
1754
                        # We found a record, which was not *this* record,
 
1755
                        # which matches the file_id, but is not actually
 
1756
                        # present. Something seems *really* wrong.
 
1757
                        self._raise_invalid(new_path, file_id,
 
1758
                            "We found a tree0 entry that doesnt make sense")
 
1759
                    # Now, we've found a tree0 entry which matches the file_id
 
1760
                    # but is at a different location. So update them to be
 
1761
                    # rename records.
 
1762
                    active_dir, active_name = active_entry[0][:2]
 
1763
                    if active_dir:
 
1764
                        active_path = active_dir + '/' + active_name
 
1765
                    else:
 
1766
                        active_path = active_name
 
1767
                    active_entry[1][1] = st('r', new_path, 0, False, '')
 
1768
                    entry[1][0] = st('r', active_path, 0, False, '')
 
1769
            elif active_kind == 'r':
 
1770
                raise NotImplementedError()
 
1771
 
 
1772
            new_kind = new_details[0]
 
1773
            if new_kind == 'd':
 
1774
                self._ensure_block(block_index, entry_index, new_path)
1619
1775
 
1620
1776
    def _update_basis_apply_changes(self, changes):
1621
1777
        """Apply a sequence of changes to tree 1 during update_basis_by_delta.
1623
1779
        :param adds: A sequence of changes. Each change is a tuple:
1624
1780
            (path_utf8, path_utf8, file_id, (entry_details))
1625
1781
        """
1626
 
        absent = 'ar'
1627
1782
        for old_path, new_path, file_id, new_details in changes:
1628
1783
            # the entry for this file_id must be in tree 0.
1629
 
            entry = self._get_entry(0, file_id, new_path)
1630
 
            if entry[0] is None or entry[0][2] != file_id:
1631
 
                self._changes_aborted = True
1632
 
                raise errors.InconsistentDelta(new_path, file_id,
1633
 
                    'working tree does not contain new entry')
1634
 
            if (entry[1][0][0] in absent or
1635
 
                entry[1][1][0] in absent):
1636
 
                self._changes_aborted = True
1637
 
                raise errors.InconsistentDelta(new_path, file_id,
1638
 
                    'changed considered absent')
 
1784
            entry = self._get_entry(1, file_id, new_path)
 
1785
            if entry[0] is None or entry[1][1][0] in (b'a', b'r'):
 
1786
                self._raise_invalid(new_path, file_id,
 
1787
                    'changed entry considered not present')
1639
1788
            entry[1][1] = new_details
1640
1789
 
1641
1790
    def _update_basis_apply_deletes(self, deletes):
1653
1802
        null = DirState.NULL_PARENT_DETAILS
1654
1803
        for old_path, new_path, file_id, _, real_delete in deletes:
1655
1804
            if real_delete != (new_path is None):
1656
 
                self._changes_aborted = True
1657
 
                raise AssertionError("bad delete delta")
 
1805
                self._raise_invalid(old_path, file_id, "bad delete delta")
1658
1806
            # the entry for this file_id must be in tree 1.
1659
1807
            dirname, basename = osutils.split(old_path)
1660
1808
            block_index, entry_index, dir_present, file_present = \
1661
1809
                self._get_block_entry_index(dirname, basename, 1)
1662
1810
            if not file_present:
1663
 
                self._changes_aborted = True
1664
 
                raise errors.InconsistentDelta(old_path, file_id,
 
1811
                self._raise_invalid(old_path, file_id,
1665
1812
                    'basis tree does not contain removed entry')
1666
1813
            entry = self._dirblocks[block_index][1][entry_index]
 
1814
            # The state of the entry in the 'active' WT
 
1815
            active_kind = entry[1][0][0]
1667
1816
            if entry[0][2] != file_id:
1668
 
                self._changes_aborted = True
1669
 
                raise errors.InconsistentDelta(old_path, file_id,
 
1817
                self._raise_invalid(old_path, file_id,
1670
1818
                    'mismatched file_id in tree 1')
1671
 
            if real_delete:
1672
 
                if entry[1][0][0] != 'a':
1673
 
                    self._changes_aborted = True
1674
 
                    raise errors.InconsistentDelta(old_path, file_id,
1675
 
                            'This was marked as a real delete, but the WT state'
1676
 
                            ' claims that it still exists and is versioned.')
 
1819
            dir_block = ()
 
1820
            old_kind = entry[1][1][0]
 
1821
            if active_kind in 'ar':
 
1822
                # The active tree doesn't have this file_id.
 
1823
                # The basis tree is changing this record. If this is a
 
1824
                # rename, then we don't want the record here at all
 
1825
                # anymore. If it is just an in-place change, we want the
 
1826
                # record here, but we'll add it if we need to. So we just
 
1827
                # delete it
 
1828
                if active_kind == 'r':
 
1829
                    active_path = entry[1][0][1]
 
1830
                    active_entry = self._get_entry(0, file_id, active_path)
 
1831
                    if active_entry[1][1][0] != 'r':
 
1832
                        self._raise_invalid(old_path, file_id,
 
1833
                            "Dirstate did not have matching rename entries")
 
1834
                    elif active_entry[1][0][0] in 'ar':
 
1835
                        self._raise_invalid(old_path, file_id,
 
1836
                            "Dirstate had a rename pointing at an inactive"
 
1837
                            " tree0")
 
1838
                    active_entry[1][1] = null
1677
1839
                del self._dirblocks[block_index][1][entry_index]
 
1840
                if old_kind == 'd':
 
1841
                    # This was a directory, and the active tree says it
 
1842
                    # doesn't exist, and now the basis tree says it doesn't
 
1843
                    # exist. Remove its dirblock if present
 
1844
                    (dir_block_index,
 
1845
                     present) = self._find_block_index_from_key(
 
1846
                        (old_path, '', ''))
 
1847
                    if present:
 
1848
                        dir_block = self._dirblocks[dir_block_index][1]
 
1849
                        if not dir_block:
 
1850
                            # This entry is empty, go ahead and just remove it
 
1851
                            del self._dirblocks[dir_block_index]
1678
1852
            else:
1679
 
                if entry[1][0][0] == 'a':
1680
 
                    self._changes_aborted = True
1681
 
                    raise errors.InconsistentDelta(old_path, file_id,
1682
 
                        'The entry was considered a rename, but the source path'
1683
 
                        ' is marked as absent.')
1684
 
                    # For whatever reason, we were asked to rename an entry
1685
 
                    # that was originally marked as deleted. This could be
1686
 
                    # because we are renaming the parent directory, and the WT
1687
 
                    # current state has the file marked as deleted.
1688
 
                elif entry[1][0][0] == 'r':
1689
 
                    # implement the rename
1690
 
                    del self._dirblocks[block_index][1][entry_index]
1691
 
                else:
1692
 
                    # it is being resurrected here, so blank it out temporarily.
1693
 
                    self._dirblocks[block_index][1][entry_index][1][1] = null
 
1853
                # There is still an active record, so just mark this
 
1854
                # removed.
 
1855
                entry[1][1] = null
 
1856
                block_i, entry_i, d_present, f_present = \
 
1857
                    self._get_block_entry_index(old_path, '', 1)
 
1858
                if d_present:
 
1859
                    dir_block = self._dirblocks[block_i][1]
 
1860
            for child_entry in dir_block:
 
1861
                child_basis_kind = child_entry[1][1][0]
 
1862
                if child_basis_kind not in 'ar':
 
1863
                    self._raise_invalid(old_path, file_id,
 
1864
                        "The file id was deleted but its children were "
 
1865
                        "not deleted.")
1694
1866
 
1695
1867
    def _after_delta_check_parents(self, parents, index):
1696
1868
        """Check that parents required by the delta are all intact.
1705
1877
            # has the right file id.
1706
1878
            entry = self._get_entry(index, file_id, dirname_utf8)
1707
1879
            if entry[1] is None:
1708
 
                self._changes_aborted = True
1709
 
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
 
1880
                self._raise_invalid(dirname_utf8.decode('utf8'),
1710
1881
                    file_id, "This parent is not present.")
1711
1882
            # Parents of things must be directories
1712
1883
            if entry[1][index][0] != 'd':
1713
 
                self._changes_aborted = True
1714
 
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
 
1884
                self._raise_invalid(dirname_utf8.decode('utf8'),
1715
1885
                    file_id, "This parent is not a directory.")
1716
1886
 
1717
1887
    def _observed_sha1(self, entry, sha1, stat_value,
1718
 
        _stat_to_minikind=_stat_to_minikind, _pack_stat=pack_stat):
 
1888
        _stat_to_minikind=_stat_to_minikind):
1719
1889
        """Note the sha1 of a file.
1720
1890
 
1721
1891
        :param entry: The entry the sha1 is for.
1723
1893
        :param stat_value: The os.lstat for the file.
1724
1894
        """
1725
1895
        try:
1726
 
            minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
 
1896
            minikind = _stat_to_minikind[stat_value.st_mode & 0o170000]
1727
1897
        except KeyError:
1728
1898
            # Unhandled kind
1729
1899
            return None
1730
 
        packed_stat = _pack_stat(stat_value)
1731
1900
        if minikind == 'f':
1732
1901
            if self._cutoff_time is None:
1733
1902
                self._sha_cutoff_time()
1734
1903
            if (stat_value.st_mtime < self._cutoff_time
1735
1904
                and stat_value.st_ctime < self._cutoff_time):
1736
 
                entry[1][0] = ('f', sha1, entry[1][0][2], entry[1][0][3],
1737
 
                    packed_stat)
1738
 
                self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1905
                entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
 
1906
                               pack_stat(stat_value))
 
1907
                self._mark_modified([entry])
1739
1908
 
1740
1909
    def _sha_cutoff_time(self):
1741
1910
        """Return cutoff time.
1785
1954
            # paths are produced by UnicodeDirReader on purpose.
1786
1955
            abspath = abspath.encode(fs_encoding)
1787
1956
        target = os.readlink(abspath)
1788
 
        if fs_encoding not in ('UTF-8', 'US-ASCII', 'ANSI_X3.4-1968'):
 
1957
        if fs_encoding not in ('utf-8', 'ascii'):
1789
1958
            # Change encoding if needed
1790
1959
            target = target.decode(fs_encoding).encode('UTF-8')
1791
1960
        return target
1799
1968
        """Serialise the entire dirstate to a sequence of lines."""
1800
1969
        if (self._header_state == DirState.IN_MEMORY_UNMODIFIED and
1801
1970
            self._dirblock_state == DirState.IN_MEMORY_UNMODIFIED):
1802
 
            # read whats on disk.
 
1971
            # read what's on disk.
1803
1972
            self._state_file.seek(0)
1804
1973
            return self._state_file.readlines()
1805
1974
        lines = []
1806
1975
        lines.append(self._get_parents_line(self.get_parent_ids()))
1807
1976
        lines.append(self._get_ghosts_line(self._ghosts))
1808
 
        # append the root line which is special cased
1809
 
        lines.extend(map(self._entry_to_line, self._iter_entries()))
 
1977
        lines.extend(self._iter_entry_lines())
1810
1978
        return self._get_output_lines(lines)
1811
1979
 
1812
1980
    def _get_ghosts_line(self, ghost_ids):
1813
1981
        """Create a line for the state file for ghost information."""
1814
 
        return '\0'.join([str(len(ghost_ids))] + ghost_ids)
 
1982
        return b'\0'.join([b'%d' % len(ghost_ids)] + ghost_ids)
1815
1983
 
1816
1984
    def _get_parents_line(self, parent_ids):
1817
1985
        """Create a line for the state file for parents information."""
1818
 
        return '\0'.join([str(len(parent_ids))] + parent_ids)
 
1986
        return b'\0'.join([b'%d' % len(parent_ids)] + parent_ids)
 
1987
 
 
1988
    def _iter_entry_lines(self):
 
1989
        """Create lines for entries."""
 
1990
        return map(self._entry_to_line, self._iter_entries())
1819
1991
 
1820
1992
    def _get_fields_to_entry(self):
1821
1993
        """Get a function which converts entry fields into a entry record.
1894
2066
                          _int(fields[cur+2]),        # size
1895
2067
                          fields[cur+3] == 'y',       # executable
1896
2068
                          fields[cur+4],              # stat or revision_id
1897
 
                         ) for cur in xrange(3, len(fields)-1, 5)]
 
2069
                         ) for cur in range(3, len(fields)-1, 5)]
1898
2070
                return path_name_file_id_key, trees
1899
2071
            return fields_to_entry_n_parents
1900
2072
 
1924
2096
            tree present there.
1925
2097
        """
1926
2098
        self._read_dirblocks_if_needed()
1927
 
        key = dirname, basename, ''
 
2099
        key = dirname, basename, b''
1928
2100
        block_index, present = self._find_block_index_from_key(key)
1929
2101
        if not present:
1930
2102
            # no such directory - return the dir index and 0 for the row.
1934
2106
        # linear search through entries at this path to find the one
1935
2107
        # requested.
1936
2108
        while entry_index < len(block) and block[entry_index][0][1] == basename:
1937
 
            if block[entry_index][1][tree_index][0] not in 'ar':
 
2109
            if block[entry_index][1][tree_index][0] not in (b'a', b'r'):
1938
2110
                # neither absent or relocated
1939
2111
                return block_index, entry_index, True, True
1940
2112
            entry_index += 1
1941
2113
        return block_index, entry_index, True, False
1942
2114
 
1943
 
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None, include_deleted=False):
 
2115
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None,
 
2116
                   include_deleted=False):
1944
2117
        """Get the dirstate entry for path in tree tree_index.
1945
2118
 
1946
2119
        If either file_id or path is supplied, it is used as the key to lookup.
1961
2134
        """
1962
2135
        self._read_dirblocks_if_needed()
1963
2136
        if path_utf8 is not None:
1964
 
            if type(path_utf8) is not str:
1965
 
                raise errors.BzrError('path_utf8 is not a str: %s %r'
 
2137
            if not isinstance(path_utf8, bytes):
 
2138
                raise errors.BzrError('path_utf8 is not bytes: %s %r'
1966
2139
                    % (type(path_utf8), path_utf8))
1967
2140
            # path lookups are faster
1968
2141
            dirname, basename = osutils.split(path_utf8)
1971
2144
            if not file_present:
1972
2145
                return None, None
1973
2146
            entry = self._dirblocks[block_index][1][entry_index]
1974
 
            if not (entry[0][2] and entry[1][tree_index][0] not in ('a', 'r')):
 
2147
            if not (entry[0][2] and entry[1][tree_index][0] not in (b'a', b'r')):
1975
2148
                raise AssertionError('unversioned entry?')
1976
2149
            if fileid_utf8:
1977
2150
                if entry[0][2] != fileid_utf8:
1980
2153
                                          ' tree_index, file_id and path')
1981
2154
            return entry
1982
2155
        else:
1983
 
            possible_keys = self._get_id_index().get(fileid_utf8, None)
 
2156
            possible_keys = self._get_id_index().get(fileid_utf8, ())
1984
2157
            if not possible_keys:
1985
2158
                return None, None
1986
2159
            for key in possible_keys:
1999
2172
                    entry = self._dirblocks[block_index][1][entry_index]
2000
2173
                    # TODO: We might want to assert that entry[0][2] ==
2001
2174
                    #       fileid_utf8.
2002
 
                    if entry[1][tree_index][0] in 'fdlt':
 
2175
                    # GZ 2017-06-09: Hoist set of minkinds somewhere
 
2176
                    if entry[1][tree_index][0] in {b'f', b'd', b'l', b't'}:
2003
2177
                        # this is the result we are looking for: the
2004
2178
                        # real home of this file_id in this tree.
2005
2179
                        return entry
2006
 
                    if entry[1][tree_index][0] == 'a':
 
2180
                    if entry[1][tree_index][0] == b'a':
2007
2181
                        # there is no home for this entry in this tree
2008
2182
                        if include_deleted:
2009
2183
                            return entry
2010
2184
                        return None, None
2011
 
                    if entry[1][tree_index][0] != 'r':
 
2185
                    if entry[1][tree_index][0] != b'r':
2012
2186
                        raise AssertionError(
2013
2187
                            "entry %r has invalid minikind %r for tree %r" \
2014
2188
                            % (entry,
2040
2214
            sha1_provider = DefaultSHA1Provider()
2041
2215
        result = cls(path, sha1_provider)
2042
2216
        # root dir and root dir contents with no children.
2043
 
        empty_tree_dirblocks = [('', []), ('', [])]
 
2217
        empty_tree_dirblocks = [(b'', []), (b'', [])]
2044
2218
        # a new root directory, with a NULLSTAT.
2045
2219
        empty_tree_dirblocks[0][1].append(
2046
 
            (('', '', inventory.ROOT_ID), [
2047
 
                ('d', '', 0, False, DirState.NULLSTAT),
 
2220
            ((b'', b'', inventory.ROOT_ID), [
 
2221
                (b'd', b'', 0, False, DirState.NULLSTAT),
2048
2222
            ]))
2049
2223
        result.lock_write()
2050
2224
        try:
2068
2242
        minikind = DirState._kind_to_minikind[kind]
2069
2243
        tree_data = inv_entry.revision
2070
2244
        if kind == 'directory':
2071
 
            fingerprint = ''
 
2245
            fingerprint = b''
2072
2246
            size = 0
2073
2247
            executable = False
2074
2248
        elif kind == 'symlink':
2075
2249
            if inv_entry.symlink_target is None:
2076
 
                fingerprint = ''
 
2250
                fingerprint = b''
2077
2251
            else:
2078
2252
                fingerprint = inv_entry.symlink_target.encode('utf8')
2079
2253
            size = 0
2080
2254
            executable = False
2081
2255
        elif kind == 'file':
2082
 
            fingerprint = inv_entry.text_sha1 or ''
 
2256
            fingerprint = inv_entry.text_sha1 or b''
2083
2257
            size = inv_entry.text_size or 0
2084
2258
            executable = inv_entry.executable
2085
2259
        elif kind == 'tree-reference':
2086
 
            fingerprint = inv_entry.reference_revision or ''
 
2260
            fingerprint = inv_entry.reference_revision or b''
2087
2261
            size = 0
2088
2262
            executable = False
2089
2263
        else:
2090
2264
            raise Exception("can't pack %s" % inv_entry)
2091
 
        return (minikind, fingerprint, size, executable, tree_data)
 
2265
        return static_tuple.StaticTuple(minikind, fingerprint, size,
 
2266
                                        executable, tree_data)
2092
2267
 
2093
2268
    def _iter_child_entries(self, tree_index, path_utf8):
2094
2269
        """Iterate over all the entries that are children of path_utf.
2103
2278
        """
2104
2279
        pending_dirs = []
2105
2280
        next_pending_dirs = [path_utf8]
2106
 
        absent = 'ar'
 
2281
        absent = (b'a', b'r')
2107
2282
        while next_pending_dirs:
2108
2283
            pending_dirs = next_pending_dirs
2109
2284
            next_pending_dirs = []
2110
2285
            for path in pending_dirs:
2111
2286
                block_index, present = self._find_block_index_from_key(
2112
 
                    (path, '', ''))
 
2287
                    (path, b'', b''))
2113
2288
                if block_index == 0:
2114
2289
                    block_index = 1
2115
2290
                    if len(self._dirblocks) == 1:
2124
2299
                    kind = entry[1][tree_index][0]
2125
2300
                    if kind not in absent:
2126
2301
                        yield entry
2127
 
                    if kind == 'd':
 
2302
                    if kind == b'd':
2128
2303
                        if entry[0][0]:
2129
 
                            path = entry[0][0] + '/' + entry[0][1]
 
2304
                            path = entry[0][0] + b'/' + entry[0][1]
2130
2305
                        else:
2131
2306
                            path = entry[0][1]
2132
2307
                        next_pending_dirs.append(path)
2135
2310
        """Iterate over all the entries in the dirstate.
2136
2311
 
2137
2312
        Each yelt item is an entry in the standard format described in the
2138
 
        docstring of bzrlib.dirstate.
 
2313
        docstring of breezy.dirstate.
2139
2314
        """
2140
2315
        self._read_dirblocks_if_needed()
2141
2316
        for directory in self._dirblocks:
2143
2318
                yield entry
2144
2319
 
2145
2320
    def _get_id_index(self):
2146
 
        """Get an id index of self._dirblocks."""
 
2321
        """Get an id index of self._dirblocks.
 
2322
 
 
2323
        This maps from file_id => [(directory, name, file_id)] entries where
 
2324
        that file_id appears in one of the trees.
 
2325
        """
2147
2326
        if self._id_index is None:
2148
2327
            id_index = {}
2149
2328
            for key, tree_details in self._iter_entries():
2150
 
                id_index.setdefault(key[2], set()).add(key)
 
2329
                self._add_to_id_index(id_index, key)
2151
2330
            self._id_index = id_index
2152
2331
        return self._id_index
2153
2332
 
 
2333
    def _add_to_id_index(self, id_index, entry_key):
 
2334
        """Add this entry to the _id_index mapping."""
 
2335
        # This code used to use a set for every entry in the id_index. However,
 
2336
        # it is *rare* to have more than one entry. So a set is a large
 
2337
        # overkill. And even when we do, we won't ever have more than the
 
2338
        # number of parent trees. Which is still a small number (rarely >2). As
 
2339
        # such, we use a simple tuple, and do our own uniqueness checks. While
 
2340
        # the 'in' check is O(N) since N is nicely bounded it shouldn't ever
 
2341
        # cause quadratic failure.
 
2342
        file_id = entry_key[2]
 
2343
        entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
 
2344
        if file_id not in id_index:
 
2345
            id_index[file_id] = static_tuple.StaticTuple(entry_key,)
 
2346
        else:
 
2347
            entry_keys = id_index[file_id]
 
2348
            if entry_key not in entry_keys:
 
2349
                id_index[file_id] = entry_keys + (entry_key,)
 
2350
 
 
2351
    def _remove_from_id_index(self, id_index, entry_key):
 
2352
        """Remove this entry from the _id_index mapping.
 
2353
 
 
2354
        It is an programming error to call this when the entry_key is not
 
2355
        already present.
 
2356
        """
 
2357
        file_id = entry_key[2]
 
2358
        entry_keys = list(id_index[file_id])
 
2359
        entry_keys.remove(entry_key)
 
2360
        id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
 
2361
 
2154
2362
    def _get_output_lines(self, lines):
2155
2363
        """Format lines for final output.
2156
2364
 
2158
2366
            path lines.
2159
2367
        """
2160
2368
        output_lines = [DirState.HEADER_FORMAT_3]
2161
 
        lines.append('') # a final newline
2162
 
        inventory_text = '\0\n\0'.join(lines)
2163
 
        output_lines.append('crc32: %s\n' % (zlib.crc32(inventory_text),))
 
2369
        lines.append(b'') # a final newline
 
2370
        inventory_text = b'\0\n\0'.join(lines)
 
2371
        output_lines.append(b'crc32: %d\n' % (zlib.crc32(inventory_text),))
2164
2372
        # -3, 1 for num parents, 1 for ghosts, 1 for final newline
2165
2373
        num_entries = len(lines)-3
2166
 
        output_lines.append('num_entries: %s\n' % (num_entries,))
 
2374
        output_lines.append(b'num_entries: %d\n' % (num_entries,))
2167
2375
        output_lines.append(inventory_text)
2168
2376
        return output_lines
2169
2377
 
2170
2378
    def _make_deleted_row(self, fileid_utf8, parents):
2171
2379
        """Return a deleted row for fileid_utf8."""
2172
 
        return ('/', 'RECYCLED.BIN', 'file', fileid_utf8, 0, DirState.NULLSTAT,
2173
 
            ''), parents
 
2380
        return (b'/', b'RECYCLED.BIN', b'file', fileid_utf8, 0, DirState.NULLSTAT,
 
2381
            b''), parents
2174
2382
 
2175
2383
    def _num_present_parents(self):
2176
2384
        """The number of parent entries in each record row."""
2177
2385
        return len(self._parents) - len(self._ghosts)
2178
2386
 
2179
 
    @staticmethod
2180
 
    def on_file(path, sha1_provider=None):
 
2387
    @classmethod
 
2388
    def on_file(cls, path, sha1_provider=None, worth_saving_limit=0):
2181
2389
        """Construct a DirState on the file at path "path".
2182
2390
 
2183
2391
        :param path: The path at which the dirstate file on disk should live.
2184
2392
        :param sha1_provider: an object meeting the SHA1Provider interface.
2185
2393
            If None, a DefaultSHA1Provider is used.
 
2394
        :param worth_saving_limit: when the exact number of hash changed
 
2395
            entries is known, only bother saving the dirstate if more than
 
2396
            this count of entries have changed. -1 means never save.
2186
2397
        :return: An unlocked DirState object, associated with the given path.
2187
2398
        """
2188
2399
        if sha1_provider is None:
2189
2400
            sha1_provider = DefaultSHA1Provider()
2190
 
        result = DirState(path, sha1_provider)
 
2401
        result = cls(path, sha1_provider,
 
2402
                     worth_saving_limit=worth_saving_limit)
2191
2403
        return result
2192
2404
 
2193
2405
    def _read_dirblocks_if_needed(self):
2211
2423
        """
2212
2424
        self._read_prelude()
2213
2425
        parent_line = self._state_file.readline()
2214
 
        info = parent_line.split('\0')
 
2426
        info = parent_line.split(b'\0')
2215
2427
        num_parents = int(info[0])
2216
2428
        self._parents = info[1:-1]
2217
2429
        ghost_line = self._state_file.readline()
2218
 
        info = ghost_line.split('\0')
 
2430
        info = ghost_line.split(b'\0')
2219
2431
        num_ghosts = int(info[1])
2220
2432
        self._ghosts = info[2:-1]
2221
2433
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
2243
2455
            raise errors.BzrError(
2244
2456
                'invalid header line: %r' % (header,))
2245
2457
        crc_line = self._state_file.readline()
2246
 
        if not crc_line.startswith('crc32: '):
 
2458
        if not crc_line.startswith(b'crc32: '):
2247
2459
            raise errors.BzrError('missing crc32 checksum: %r' % crc_line)
2248
 
        self.crc_expected = int(crc_line[len('crc32: '):-1])
 
2460
        self.crc_expected = int(crc_line[len(b'crc32: '):-1])
2249
2461
        num_entries_line = self._state_file.readline()
2250
 
        if not num_entries_line.startswith('num_entries: '):
 
2462
        if not num_entries_line.startswith(b'num_entries: '):
2251
2463
            raise errors.BzrError('missing num_entries line')
2252
 
        self._num_entries = int(num_entries_line[len('num_entries: '):-1])
 
2464
        self._num_entries = int(num_entries_line[len(b'num_entries: '):-1])
2253
2465
 
2254
 
    def sha1_from_stat(self, path, stat_result, _pack_stat=pack_stat):
 
2466
    def sha1_from_stat(self, path, stat_result):
2255
2467
        """Find a sha1 given a stat lookup."""
2256
 
        return self._get_packed_stat_index().get(_pack_stat(stat_result), None)
 
2468
        return self._get_packed_stat_index().get(pack_stat(stat_result), None)
2257
2469
 
2258
2470
    def _get_packed_stat_index(self):
2259
2471
        """Get a packed_stat index of self._dirblocks."""
2260
2472
        if self._packed_stat_index is None:
2261
2473
            index = {}
2262
2474
            for key, tree_details in self._iter_entries():
2263
 
                if tree_details[0][0] == 'f':
 
2475
                if tree_details[0][0] == b'f':
2264
2476
                    index[tree_details[0][4]] = tree_details[0][1]
2265
2477
            self._packed_stat_index = index
2266
2478
        return self._packed_stat_index
2285
2497
            trace.mutter('Not saving DirState because '
2286
2498
                    '_changes_aborted is set.')
2287
2499
            return
2288
 
        if (self._header_state == DirState.IN_MEMORY_MODIFIED or
2289
 
            self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
 
2500
        # TODO: Since we now distinguish IN_MEMORY_MODIFIED from
 
2501
        #       IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail
 
2502
        #       to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we
 
2503
        #       fail to save IN_MEMORY_MODIFIED
 
2504
        if not self._worth_saving():
 
2505
            return
2290
2506
 
2291
 
            grabbed_write_lock = False
2292
 
            if self._lock_state != 'w':
2293
 
                grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
2294
 
                # Switch over to the new lock, as the old one may be closed.
 
2507
        grabbed_write_lock = False
 
2508
        if self._lock_state != 'w':
 
2509
            grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
 
2510
            # Switch over to the new lock, as the old one may be closed.
 
2511
            # TODO: jam 20070315 We should validate the disk file has
 
2512
            #       not changed contents, since temporary_write_lock may
 
2513
            #       not be an atomic operation.
 
2514
            self._lock_token = new_lock
 
2515
            self._state_file = new_lock.f
 
2516
            if not grabbed_write_lock:
 
2517
                # We couldn't grab a write lock, so we switch back to a read one
 
2518
                return
 
2519
        try:
 
2520
            lines = self.get_lines()
 
2521
            self._state_file.seek(0)
 
2522
            self._state_file.writelines(lines)
 
2523
            self._state_file.truncate()
 
2524
            self._state_file.flush()
 
2525
            self._maybe_fdatasync()
 
2526
            self._mark_unmodified()
 
2527
        finally:
 
2528
            if grabbed_write_lock:
 
2529
                self._lock_token = self._lock_token.restore_read_lock()
 
2530
                self._state_file = self._lock_token.f
2295
2531
                # TODO: jam 20070315 We should validate the disk file has
2296
 
                #       not changed contents. Since temporary_write_lock may
2297
 
                #       not be an atomic operation.
2298
 
                self._lock_token = new_lock
2299
 
                self._state_file = new_lock.f
2300
 
                if not grabbed_write_lock:
2301
 
                    # We couldn't grab a write lock, so we switch back to a read one
2302
 
                    return
2303
 
            try:
2304
 
                self._state_file.seek(0)
2305
 
                self._state_file.writelines(self.get_lines())
2306
 
                self._state_file.truncate()
2307
 
                self._state_file.flush()
2308
 
                self._header_state = DirState.IN_MEMORY_UNMODIFIED
2309
 
                self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
2310
 
            finally:
2311
 
                if grabbed_write_lock:
2312
 
                    self._lock_token = self._lock_token.restore_read_lock()
2313
 
                    self._state_file = self._lock_token.f
2314
 
                    # TODO: jam 20070315 We should validate the disk file has
2315
 
                    #       not changed contents. Since restore_read_lock may
2316
 
                    #       not be an atomic operation.
 
2532
                #       not changed contents. Since restore_read_lock may
 
2533
                #       not be an atomic operation.                
 
2534
 
 
2535
    def _maybe_fdatasync(self):
 
2536
        """Flush to disk if possible and if not configured off."""
 
2537
        if self._config_stack.get('dirstate.fdatasync'):
 
2538
            osutils.fdatasync(self._state_file.fileno())
 
2539
 
 
2540
    def _worth_saving(self):
 
2541
        """Is it worth saving the dirstate or not?"""
 
2542
        if (self._header_state == DirState.IN_MEMORY_MODIFIED
 
2543
            or self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
 
2544
            return True
 
2545
        if self._dirblock_state == DirState.IN_MEMORY_HASH_MODIFIED:
 
2546
            if self._worth_saving_limit == -1:
 
2547
                # We never save hash changes when the limit is -1
 
2548
                return False
 
2549
            # If we're using smart saving and only a small number of
 
2550
            # entries have changed their hash, don't bother saving. John has
 
2551
            # suggested using a heuristic here based on the size of the
 
2552
            # changed files and/or tree. For now, we go with a configurable
 
2553
            # number of changes, keeping the calculation time
 
2554
            # as low overhead as possible. (This also keeps all existing
 
2555
            # tests passing as the default is 0, i.e. always save.)
 
2556
            if len(self._known_hash_changes) >= self._worth_saving_limit:
 
2557
                return True
 
2558
        return False
2317
2559
 
2318
2560
    def _set_data(self, parent_ids, dirblocks):
2319
2561
        """Set the full dirstate data in memory.
2328
2570
        """
2329
2571
        # our memory copy is now authoritative.
2330
2572
        self._dirblocks = dirblocks
2331
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
2332
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2573
        self._mark_modified(header_modified=True)
2333
2574
        self._parents = list(parent_ids)
2334
2575
        self._id_index = None
2335
2576
        self._packed_stat_index = None
2351
2592
        if entry[0][2] == new_id:
2352
2593
            # Nothing to change.
2353
2594
            return
 
2595
        if new_id.__class__ != bytes:
 
2596
            raise AssertionError(
 
2597
                "must be a utf8 file_id not %s" % (type(new_id), ))
2354
2598
        # mark the old path absent, and insert a new root path
2355
2599
        self._make_absent(entry)
2356
 
        self.update_minimal(('', '', new_id), 'd',
2357
 
            path_utf8='', packed_stat=entry[1][0][4])
2358
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2600
        self.update_minimal((b'', b'', new_id), b'd',
 
2601
            path_utf8=b'', packed_stat=entry[1][0][4])
 
2602
        self._mark_modified()
2359
2603
 
2360
2604
    def set_parent_trees(self, trees, ghosts):
2361
2605
        """Set the parent trees for the dirstate.
2406
2650
        parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
2407
2651
        # how many trees do we end up with
2408
2652
        parent_count = len(parent_trees)
 
2653
        st = static_tuple.StaticTuple
2409
2654
 
2410
2655
        # one: the current tree
2411
2656
        for entry in self._iter_entries():
2412
2657
            # skip entries not in the current tree
2413
 
            if entry[1][0][0] in 'ar': # absent, relocated
 
2658
            if entry[1][0][0] in (b'a', b'r'): # absent, relocated
2414
2659
                continue
2415
2660
            by_path[entry[0]] = [entry[1][0]] + \
2416
2661
                [DirState.NULL_PARENT_DETAILS] * parent_count
2417
 
            id_index[entry[0][2]] = set([entry[0]])
 
2662
            # TODO: Possibly inline this, since we know it isn't present yet
 
2663
            #       id_index[entry[0][2]] = (entry[0],)
 
2664
            self._add_to_id_index(id_index, entry[0])
2418
2665
 
2419
2666
        # now the parent trees:
2420
2667
        for tree_index, tree in enumerate(parent_trees):
2426
2673
            # the suffix is from tree_index+1:parent_count+1.
2427
2674
            new_location_suffix = [DirState.NULL_PARENT_DETAILS] * (parent_count - tree_index)
2428
2675
            # now stitch in all the entries from this tree
2429
 
            for path, entry in tree.inventory.iter_entries_by_dir():
 
2676
            last_dirname = None
 
2677
            for path, entry in tree.iter_entries_by_dir():
2430
2678
                # here we process each trees details for each item in the tree.
2431
2679
                # we first update any existing entries for the id at other paths,
2432
2680
                # then we either create or update the entry for the id at the
2439
2687
                file_id = entry.file_id
2440
2688
                path_utf8 = path.encode('utf8')
2441
2689
                dirname, basename = osutils.split(path_utf8)
2442
 
                new_entry_key = (dirname, basename, file_id)
 
2690
                if dirname == last_dirname:
 
2691
                    # Try to re-use objects as much as possible
 
2692
                    dirname = last_dirname
 
2693
                else:
 
2694
                    last_dirname = dirname
 
2695
                new_entry_key = st(dirname, basename, file_id)
2443
2696
                # tree index consistency: All other paths for this id in this tree
2444
2697
                # index must point to the correct path.
2445
 
                for entry_key in id_index.setdefault(file_id, set()):
 
2698
                entry_keys = id_index.get(file_id, ())
 
2699
                for entry_key in entry_keys:
2446
2700
                    # TODO:PROFILING: It might be faster to just update
2447
2701
                    # rather than checking if we need to, and then overwrite
2448
2702
                    # the one we are located at.
2451
2705
                        # other trees, so put absent pointers there
2452
2706
                        # This is the vertical axis in the matrix, all pointing
2453
2707
                        # to the real path.
2454
 
                        by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2455
 
                # by path consistency: Insert into an existing path record (trivial), or
2456
 
                # add a new one with relocation pointers for the other tree indexes.
2457
 
                if new_entry_key in id_index[file_id]:
2458
 
                    # there is already an entry where this data belongs, just insert it.
 
2708
                        by_path[entry_key][tree_index] = st(b'r', path_utf8, 0,
 
2709
                                                            False, b'')
 
2710
                # by path consistency: Insert into an existing path record
 
2711
                # (trivial), or add a new one with relocation pointers for the
 
2712
                # other tree indexes.
 
2713
                if new_entry_key in entry_keys:
 
2714
                    # there is already an entry where this data belongs, just
 
2715
                    # insert it.
2459
2716
                    by_path[new_entry_key][tree_index] = \
2460
2717
                        self._inv_entry_to_details(entry)
2461
2718
                else:
2463
2720
                    # mapping from path,id. We need to look up the correct path
2464
2721
                    # for the indexes from 0 to tree_index -1
2465
2722
                    new_details = []
2466
 
                    for lookup_index in xrange(tree_index):
 
2723
                    for lookup_index in range(tree_index):
2467
2724
                        # boundary case: this is the first occurence of file_id
2468
 
                        # so there are no id_indexs, possibly take this out of
 
2725
                        # so there are no id_indexes, possibly take this out of
2469
2726
                        # the loop?
2470
 
                        if not len(id_index[file_id]):
 
2727
                        if not len(entry_keys):
2471
2728
                            new_details.append(DirState.NULL_PARENT_DETAILS)
2472
2729
                        else:
2473
2730
                            # grab any one entry, use it to find the right path.
2474
 
                            # TODO: optimise this to reduce memory use in highly
2475
 
                            # fragmented situations by reusing the relocation
2476
 
                            # records.
2477
 
                            a_key = iter(id_index[file_id]).next()
2478
 
                            if by_path[a_key][lookup_index][0] in ('r', 'a'):
2479
 
                                # its a pointer or missing statement, use it as is.
 
2731
                            a_key = next(iter(entry_keys))
 
2732
                            if by_path[a_key][lookup_index][0] in (b'r', b'a'):
 
2733
                                # its a pointer or missing statement, use it as
 
2734
                                # is.
2480
2735
                                new_details.append(by_path[a_key][lookup_index])
2481
2736
                            else:
2482
2737
                                # we have the right key, make a pointer to it.
2483
 
                                real_path = ('/'.join(a_key[0:2])).strip('/')
2484
 
                                new_details.append(('r', real_path, 0, False, ''))
 
2738
                                real_path = (b'/'.join(a_key[0:2])).strip(b'/')
 
2739
                                new_details.append(st(b'r', real_path, 0, False,
 
2740
                                                      b''))
2485
2741
                    new_details.append(self._inv_entry_to_details(entry))
2486
2742
                    new_details.extend(new_location_suffix)
2487
2743
                    by_path[new_entry_key] = new_details
2488
 
                    id_index[file_id].add(new_entry_key)
 
2744
                    self._add_to_id_index(id_index, new_entry_key)
2489
2745
        # --- end generation of full tree mappings
2490
2746
 
2491
2747
        # sort and output all the entries
2492
 
        new_entries = self._sort_entries(by_path.items())
 
2748
        new_entries = self._sort_entries(viewitems(by_path))
2493
2749
        self._entries_to_current_state(new_entries)
2494
2750
        self._parents = [rev_id for rev_id, tree in trees]
2495
2751
        self._ghosts = list(ghosts)
2496
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
2497
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2752
        self._mark_modified(header_modified=True)
2498
2753
        self._id_index = id_index
2499
2754
 
2500
2755
    def _sort_entries(self, entry_list):
2504
2759
        try to keep everything in sorted blocks all the time, but sometimes
2505
2760
        it's easier to sort after the fact.
2506
2761
        """
2507
 
        def _key(entry):
 
2762
        # When sorting, we usually have 10x more entries than directories. (69k
 
2763
        # total entries, 4k directories). So cache the results of splitting.
 
2764
        # Saving time and objects. Also, use StaticTuple to avoid putting all
 
2765
        # of these object into python's garbage collector.
 
2766
        split_dirs = {}
 
2767
        def _key(entry, _split_dirs=split_dirs, _st=static_tuple.StaticTuple):
2508
2768
            # sort by: directory parts, file name, file id
2509
 
            return entry[0][0].split('/'), entry[0][1], entry[0][2]
 
2769
            dirpath, fname, file_id = entry[0]
 
2770
            try:
 
2771
                split = _split_dirs[dirpath]
 
2772
            except KeyError:
 
2773
                split = _st.from_sequence(dirpath.split(b'/'))
 
2774
                _split_dirs[dirpath] = split
 
2775
            return _st(split, fname, file_id)
2510
2776
        return sorted(entry_list, key=_key)
2511
2777
 
2512
2778
    def set_state_from_inventory(self, new_inv):
2542
2808
        # underlying dirstate.
2543
2809
        old_iterator = iter(list(self._iter_entries()))
2544
2810
        # both must have roots so this is safe:
2545
 
        current_new = new_iterator.next()
2546
 
        current_old = old_iterator.next()
 
2811
        current_new = next(new_iterator)
 
2812
        current_old = next(old_iterator)
2547
2813
        def advance(iterator):
2548
2814
            try:
2549
 
                return iterator.next()
 
2815
                return next(iterator)
2550
2816
            except StopIteration:
2551
2817
                return None
2552
2818
        while current_new or current_old:
2553
2819
            # skip entries in old that are not really there
2554
 
            if current_old and current_old[1][0][0] in 'ar':
 
2820
            if current_old and current_old[1][0][0] in (b'a', b'r'):
2555
2821
                # relocated or absent
2556
2822
                current_old = advance(old_iterator)
2557
2823
                continue
2563
2829
                new_entry_key = (new_dirname, new_basename, new_id)
2564
2830
                current_new_minikind = \
2565
2831
                    DirState._kind_to_minikind[current_new[1].kind]
2566
 
                if current_new_minikind == 't':
2567
 
                    fingerprint = current_new[1].reference_revision or ''
 
2832
                if current_new_minikind == b't':
 
2833
                    fingerprint = current_new[1].reference_revision or b''
2568
2834
                else:
2569
2835
                    # We normally only insert or remove records, or update
2570
2836
                    # them when it has significantly changed.  Then we want to
2571
2837
                    # erase its fingerprint.  Unaffected records should
2572
2838
                    # normally not be updated at all.
2573
 
                    fingerprint = ''
 
2839
                    fingerprint = b''
2574
2840
            else:
2575
2841
                # for safety disable variables
2576
2842
                new_path_utf8 = new_dirname = new_basename = new_id = \
2615
2881
                # both sides are dealt with, move on
2616
2882
                current_old = advance(old_iterator)
2617
2883
                current_new = advance(new_iterator)
2618
 
            elif (cmp_by_dirs(new_dirname, current_old[0][0]) < 0
 
2884
            elif (lt_by_dirs(new_dirname, current_old[0][0])
2619
2885
                  or (new_dirname == current_old[0][0]
2620
2886
                      and new_entry_key[1:] < current_old[0][1:])):
2621
2887
                # new comes before:
2637
2903
                        current_old[0][1].decode('utf8'))
2638
2904
                self._make_absent(current_old)
2639
2905
                current_old = advance(old_iterator)
2640
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2906
        self._mark_modified()
2641
2907
        self._id_index = None
2642
2908
        self._packed_stat_index = None
2643
2909
        if tracing:
2644
2910
            trace.mutter("set_state_from_inventory complete.")
2645
2911
 
 
2912
    def set_state_from_scratch(self, working_inv, parent_trees, parent_ghosts):
 
2913
        """Wipe the currently stored state and set it to something new.
 
2914
 
 
2915
        This is a hard-reset for the data we are working with.
 
2916
        """
 
2917
        # Technically, we really want a write lock, but until we write, we
 
2918
        # don't really need it.
 
2919
        self._requires_lock()
 
2920
        # root dir and root dir contents with no children. We have to have a
 
2921
        # root for set_state_from_inventory to work correctly.
 
2922
        empty_root = ((b'', b'', inventory.ROOT_ID),
 
2923
                      [(b'd', b'', 0, False, DirState.NULLSTAT)])
 
2924
        empty_tree_dirblocks = [(b'', [empty_root]), (b'', [])]
 
2925
        self._set_data([], empty_tree_dirblocks)
 
2926
        self.set_state_from_inventory(working_inv)
 
2927
        self.set_parent_trees(parent_trees, parent_ghosts)
 
2928
 
2646
2929
    def _make_absent(self, current_old):
2647
2930
        """Mark current_old - an entry - as absent for tree 0.
2648
2931
 
2655
2938
        all_remaining_keys = set()
2656
2939
        # Dont check the working tree, because it's going.
2657
2940
        for details in current_old[1][1:]:
2658
 
            if details[0] not in 'ar': # absent, relocated
 
2941
            if details[0] not in (b'a', b'r'): # absent, relocated
2659
2942
                all_remaining_keys.add(current_old[0])
2660
 
            elif details[0] == 'r': # relocated
 
2943
            elif details[0] == b'r': # relocated
2661
2944
                # record the key for the real path.
2662
2945
                all_remaining_keys.add(tuple(osutils.split(details[1])) + (current_old[0][2],))
2663
2946
            # absent rows are not present at any path.
2673
2956
            block[1].pop(entry_index)
2674
2957
            # if we have an id_index in use, remove this key from it for this id.
2675
2958
            if self._id_index is not None:
2676
 
                self._id_index[current_old[0][2]].remove(current_old[0])
 
2959
                self._remove_from_id_index(self._id_index, current_old[0])
2677
2960
        # update all remaining keys for this id to record it as absent. The
2678
2961
        # existing details may either be the record we are marking as deleted
2679
2962
        # (if there were other trees with the id present at this path), or may
2689
2972
                raise AssertionError('could not find entry for %s' % (update_key,))
2690
2973
            update_tree_details = self._dirblocks[update_block_index][1][update_entry_index][1]
2691
2974
            # it must not be absent at the moment
2692
 
            if update_tree_details[0][0] == 'a': # absent
 
2975
            if update_tree_details[0][0] == b'a': # absent
2693
2976
                raise AssertionError('bad row %r' % (update_tree_details,))
2694
2977
            update_tree_details[0] = DirState.NULL_PARENT_DETAILS
2695
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2978
        self._mark_modified()
2696
2979
        return last_reference
2697
2980
 
2698
 
    def update_minimal(self, key, minikind, executable=False, fingerprint='',
 
2981
    def update_minimal(self, key, minikind, executable=False, fingerprint=b'',
2699
2982
        packed_stat=None, size=0, path_utf8=None, fullscan=False):
2700
2983
        """Update an entry to the state in tree 0.
2701
2984
 
2732
3015
        if not present:
2733
3016
            # New record. Check there isn't a entry at this path already.
2734
3017
            if not fullscan:
2735
 
                low_index, _ = self._find_entry_index(key[0:2] + ('',), block)
 
3018
                low_index, _ = self._find_entry_index(key[0:2] + (b'',), block)
2736
3019
                while low_index < len(block):
2737
3020
                    entry = block[low_index]
2738
3021
                    if entry[0][0:2] == key[0:2]:
2739
 
                        if entry[1][0][0] not in 'ar':
 
3022
                        if entry[1][0][0] not in (b'a', b'r'):
2740
3023
                            # This entry has the same path (but a different id) as
2741
3024
                            # the new entry we're adding, and is present in ths
2742
3025
                            # tree.
2743
 
                            raise errors.InconsistentDelta(
2744
 
                                ("%s/%s" % key[0:2]).decode('utf8'), key[2],
 
3026
                            self._raise_invalid(
 
3027
                                (b"%s/%s" % key[0:2]).decode('utf8'), key[2],
2745
3028
                                "Attempt to add item at path already occupied by "
2746
3029
                                "id %r" % entry[0][2])
2747
3030
                        low_index += 1
2748
3031
                    else:
2749
3032
                        break
2750
3033
            # new entry, synthesis cross reference here,
2751
 
            existing_keys = id_index.setdefault(key[2], set())
 
3034
            existing_keys = id_index.get(key[2], ())
2752
3035
            if not existing_keys:
2753
3036
                # not currently in the state, simplest case
2754
3037
                new_entry = key, [new_details] + self._empty_parent_info()
2784
3067
                    # entry, if not already examined, is skipped over by that
2785
3068
                    # loop.
2786
3069
                    other_entry = other_block[other_entry_index]
2787
 
                    other_entry[1][0] = ('r', path_utf8, 0, False, '')
2788
 
                    self._maybe_remove_row(other_block, other_entry_index,
2789
 
                        id_index)
 
3070
                    other_entry[1][0] = (b'r', path_utf8, 0, False, b'')
 
3071
                    if self._maybe_remove_row(other_block, other_entry_index,
 
3072
                                              id_index):
 
3073
                        # If the row holding this was removed, we need to
 
3074
                        # recompute where this entry goes
 
3075
                        entry_index, _ = self._find_entry_index(key, block)
2790
3076
 
2791
3077
                # This loop:
2792
3078
                # adds a tuple to the new details for each column
2794
3080
                #  - or by creating a new pointer to the right row inside that column
2795
3081
                num_present_parents = self._num_present_parents()
2796
3082
                if num_present_parents:
 
3083
                    # TODO: This re-evaluates the existing_keys set, do we need
 
3084
                    #       to do that ourselves?
2797
3085
                    other_key = list(existing_keys)[0]
2798
 
                for lookup_index in xrange(1, num_present_parents + 1):
 
3086
                for lookup_index in range(1, num_present_parents + 1):
2799
3087
                    # grab any one entry, use it to find the right path.
2800
3088
                    # TODO: optimise this to reduce memory use in highly
2801
3089
                    # fragmented situations by reusing the relocation
2809
3097
                    if not present:
2810
3098
                        raise AssertionError('update_minimal: could not find entry for %s' % (other_key,))
2811
3099
                    update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
2812
 
                    if update_details[0] in 'ar': # relocated, absent
 
3100
                    if update_details[0] in (b'a', b'r'): # relocated, absent
2813
3101
                        # its a pointer or absent in lookup_index's tree, use
2814
3102
                        # it as is.
2815
3103
                        new_entry[1].append(update_details)
2816
3104
                    else:
2817
3105
                        # we have the right key, make a pointer to it.
2818
3106
                        pointer_path = osutils.pathjoin(*other_key[0:2])
2819
 
                        new_entry[1].append(('r', pointer_path, 0, False, ''))
 
3107
                        new_entry[1].append((b'r', pointer_path, 0, False, b''))
2820
3108
            block.insert(entry_index, new_entry)
2821
 
            existing_keys.add(key)
 
3109
            self._add_to_id_index(id_index, key)
2822
3110
        else:
2823
3111
            # Does the new state matter?
2824
3112
            block[entry_index][1][0] = new_details
2833
3121
            # converted to relocated.
2834
3122
            if path_utf8 is None:
2835
3123
                raise AssertionError('no path')
2836
 
            for entry_key in id_index.setdefault(key[2], set()):
 
3124
            existing_keys = id_index.get(key[2], ())
 
3125
            if key not in existing_keys:
 
3126
                raise AssertionError('We found the entry in the blocks, but'
 
3127
                    ' the key is not in the id_index.'
 
3128
                    ' key: %s, existing_keys: %s' % (key, existing_keys))
 
3129
            for entry_key in existing_keys:
2837
3130
                # TODO:PROFILING: It might be faster to just update
2838
3131
                # rather than checking if we need to, and then overwrite
2839
3132
                # the one we are located at.
2849
3142
                    if not present:
2850
3143
                        raise AssertionError('not present: %r', entry_key)
2851
3144
                    self._dirblocks[block_index][1][entry_index][1][0] = \
2852
 
                        ('r', path_utf8, 0, False, '')
 
3145
                        (b'r', path_utf8, 0, False, b'')
2853
3146
        # add a containing dirblock if needed.
2854
 
        if new_details[0] == 'd':
2855
 
            subdir_key = (osutils.pathjoin(*key[0:2]), '', '')
 
3147
        if new_details[0] == b'd':
 
3148
            # GZ 2017-06-09: Using pathjoin why?
 
3149
            subdir_key = (osutils.pathjoin(*key[0:2]), b'', b'')
2856
3150
            block_index, present = self._find_block_index_from_key(subdir_key)
2857
3151
            if not present:
2858
3152
                self._dirblocks.insert(block_index, (subdir_key[0], []))
2859
3153
 
2860
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
3154
        self._mark_modified()
2861
3155
 
2862
3156
    def _maybe_remove_row(self, block, index, id_index):
2863
3157
        """Remove index if it is absent or relocated across the row.
2864
3158
        
2865
3159
        id_index is updated accordingly.
 
3160
        :return: True if we removed the row, False otherwise
2866
3161
        """
2867
3162
        present_in_row = False
2868
3163
        entry = block[index]
2869
3164
        for column in entry[1]:
2870
 
            if column[0] not in 'ar':
 
3165
            if column[0] not in (b'a', b'r'):
2871
3166
                present_in_row = True
2872
3167
                break
2873
3168
        if not present_in_row:
2874
3169
            block.pop(index)
2875
 
            id_index[entry[0][2]].remove(entry[0])
 
3170
            self._remove_from_id_index(id_index, entry[0])
 
3171
            return True
 
3172
        return False
2876
3173
 
2877
3174
    def _validate(self):
2878
3175
        """Check that invariants on the dirblock are correct.
2896
3193
        from pprint import pformat
2897
3194
        self._read_dirblocks_if_needed()
2898
3195
        if len(self._dirblocks) > 0:
2899
 
            if not self._dirblocks[0][0] == '':
 
3196
            if not self._dirblocks[0][0] == b'':
2900
3197
                raise AssertionError(
2901
3198
                    "dirblocks don't start with root block:\n" + \
2902
3199
                    pformat(self._dirblocks))
2903
3200
        if len(self._dirblocks) > 1:
2904
 
            if not self._dirblocks[1][0] == '':
 
3201
            if not self._dirblocks[1][0] == b'':
2905
3202
                raise AssertionError(
2906
3203
                    "dirblocks missing root directory:\n" + \
2907
3204
                    pformat(self._dirblocks))
2908
3205
        # the dirblocks are sorted by their path components, name, and dir id
2909
 
        dir_names = [d[0].split('/')
 
3206
        dir_names = [d[0].split(b'/')
2910
3207
                for d in self._dirblocks[1:]]
2911
3208
        if dir_names != sorted(dir_names):
2912
3209
            raise AssertionError(
2936
3233
            current tree. (It is invalid to have a non-absent file in an absent
2937
3234
            directory.)
2938
3235
            """
2939
 
            if entry[0][0:2] == ('', ''):
 
3236
            if entry[0][0:2] == (b'', b''):
2940
3237
                # There should be no parent for the root row
2941
3238
                return
2942
3239
            parent_entry = self._get_entry(tree_index, path_utf8=entry[0][0])
2944
3241
                raise AssertionError(
2945
3242
                    "no parent entry for: %s in tree %s"
2946
3243
                    % (this_path, tree_index))
2947
 
            if parent_entry[1][tree_index][0] != 'd':
 
3244
            if parent_entry[1][tree_index][0] != b'd':
2948
3245
                raise AssertionError(
2949
3246
                    "Parent entry for %s is not marked as a valid"
2950
3247
                    " directory. %s" % (this_path, parent_entry,))
2958
3255
        # We check this with a dict per tree pointing either to the present
2959
3256
        # name, or None if absent.
2960
3257
        tree_count = self._num_present_parents() + 1
2961
 
        id_path_maps = [dict() for i in range(tree_count)]
 
3258
        id_path_maps = [{} for _ in range(tree_count)]
2962
3259
        # Make sure that all renamed entries point to the correct location.
2963
3260
        for entry in self._iter_entries():
2964
3261
            file_id = entry[0][2]
2972
3269
            for tree_index, tree_state in enumerate(entry[1]):
2973
3270
                this_tree_map = id_path_maps[tree_index]
2974
3271
                minikind = tree_state[0]
2975
 
                if minikind in 'ar':
 
3272
                if minikind in (b'a', b'r'):
2976
3273
                    absent_positions += 1
2977
3274
                # have we seen this id before in this column?
2978
3275
                if file_id in this_tree_map:
2979
3276
                    previous_path, previous_loc = this_tree_map[file_id]
2980
3277
                    # any later mention of this file must be consistent with
2981
3278
                    # what was said before
2982
 
                    if minikind == 'a':
 
3279
                    if minikind == b'a':
2983
3280
                        if previous_path is not None:
2984
3281
                            raise AssertionError(
2985
3282
                            "file %s is absent in row %r but also present " \
2986
3283
                            "at %r"% \
2987
3284
                            (file_id, entry, previous_path))
2988
 
                    elif minikind == 'r':
 
3285
                    elif minikind == b'r':
2989
3286
                        target_location = tree_state[1]
2990
3287
                        if previous_path != target_location:
2991
3288
                            raise AssertionError(
3001
3298
                                (entry, previous_path, previous_loc))
3002
3299
                        check_valid_parent()
3003
3300
                else:
3004
 
                    if minikind == 'a':
 
3301
                    if minikind == b'a':
3005
3302
                        # absent; should not occur anywhere else
3006
3303
                        this_tree_map[file_id] = None, this_path
3007
 
                    elif minikind == 'r':
 
3304
                    elif minikind == b'r':
3008
3305
                        # relocation, must occur at expected location
3009
3306
                        this_tree_map[file_id] = tree_state[1], this_path
3010
3307
                    else:
3014
3311
                raise AssertionError(
3015
3312
                    "entry %r has no data for any tree." % (entry,))
3016
3313
        if self._id_index is not None:
3017
 
            for file_id, entry_keys in self._id_index.iteritems():
 
3314
            for file_id, entry_keys in viewitems(self._id_index):
3018
3315
                for entry_key in entry_keys:
 
3316
                    # Check that the entry in the map is pointing to the same
 
3317
                    # file_id
3019
3318
                    if entry_key[2] != file_id:
3020
3319
                        raise AssertionError(
3021
3320
                            'file_id %r did not match entry key %s'
3022
3321
                            % (file_id, entry_key))
 
3322
                    # And that from this entry key, we can look up the original
 
3323
                    # record
 
3324
                    block_index, present = self._find_block_index_from_key(entry_key)
 
3325
                    if not present:
 
3326
                        raise AssertionError('missing block for entry key: %r', entry_key)
 
3327
                    entry_index, present = self._find_entry_index(entry_key, self._dirblocks[block_index][1])
 
3328
                    if not present:
 
3329
                        raise AssertionError('missing entry for key: %r', entry_key)
 
3330
                if len(entry_keys) != len(set(entry_keys)):
 
3331
                    raise AssertionError(
 
3332
                        'id_index contained non-unique data for %s'
 
3333
                        % (entry_keys,))
3023
3334
 
3024
3335
    def _wipe_state(self):
3025
3336
        """Forget all state information about the dirstate."""
3082
3393
 
3083
3394
 
3084
3395
def py_update_entry(state, entry, abspath, stat_value,
3085
 
                 _stat_to_minikind=DirState._stat_to_minikind,
3086
 
                 _pack_stat=pack_stat):
 
3396
                 _stat_to_minikind=DirState._stat_to_minikind):
3087
3397
    """Update the entry based on what is actually on disk.
3088
3398
 
3089
3399
    This function only calculates the sha if it needs to - if the entry is
3098
3408
        target of a symlink.
3099
3409
    """
3100
3410
    try:
3101
 
        minikind = _stat_to_minikind[stat_value.st_mode & 0170000]
 
3411
        minikind = _stat_to_minikind[stat_value.st_mode & 0o170000]
3102
3412
    except KeyError:
3103
3413
        # Unhandled kind
3104
3414
        return None
3105
 
    packed_stat = _pack_stat(stat_value)
 
3415
    packed_stat = pack_stat(stat_value)
3106
3416
    (saved_minikind, saved_link_or_sha1, saved_file_size,
3107
3417
     saved_executable, saved_packed_stat) = entry[1][0]
3108
3418
 
3109
 
    if minikind == 'd' and saved_minikind == 't':
3110
 
        minikind = 't'
 
3419
    if minikind == b'd' and saved_minikind == b't':
 
3420
        minikind = b't'
3111
3421
    if (minikind == saved_minikind
3112
3422
        and packed_stat == saved_packed_stat):
3113
3423
        # The stat hasn't changed since we saved, so we can re-use the
3114
3424
        # saved sha hash.
3115
 
        if minikind == 'd':
 
3425
        if minikind == b'd':
3116
3426
            return None
3117
3427
 
3118
3428
        # size should also be in packed_stat
3122
3432
    # If we have gotten this far, that means that we need to actually
3123
3433
    # process this entry.
3124
3434
    link_or_sha1 = None
3125
 
    if minikind == 'f':
 
3435
    worth_saving = True
 
3436
    if minikind == b'f':
3126
3437
        executable = state._is_executable(stat_value.st_mode,
3127
3438
                                         saved_executable)
3128
3439
        if state._cutoff_time is None:
3130
3441
        if (stat_value.st_mtime < state._cutoff_time
3131
3442
            and stat_value.st_ctime < state._cutoff_time
3132
3443
            and len(entry[1]) > 1
3133
 
            and entry[1][1][0] != 'a'):
 
3444
            and entry[1][1][0] != b'a'):
3134
3445
            # Could check for size changes for further optimised
3135
3446
            # avoidance of sha1's. However the most prominent case of
3136
3447
            # over-shaing is during initial add, which this catches.
3138
3449
            # are calculated at the same time, so checking just the size
3139
3450
            # gains nothing w.r.t. performance.
3140
3451
            link_or_sha1 = state._sha1_file(abspath)
3141
 
            entry[1][0] = ('f', link_or_sha1, stat_value.st_size,
 
3452
            entry[1][0] = (b'f', link_or_sha1, stat_value.st_size,
3142
3453
                           executable, packed_stat)
3143
3454
        else:
3144
 
            entry[1][0] = ('f', '', stat_value.st_size,
 
3455
            entry[1][0] = (b'f', b'', stat_value.st_size,
3145
3456
                           executable, DirState.NULLSTAT)
3146
 
    elif minikind == 'd':
 
3457
            worth_saving = False
 
3458
    elif minikind == b'd':
3147
3459
        link_or_sha1 = None
3148
 
        entry[1][0] = ('d', '', 0, False, packed_stat)
3149
 
        if saved_minikind != 'd':
 
3460
        entry[1][0] = (b'd', b'', 0, False, packed_stat)
 
3461
        if saved_minikind != b'd':
3150
3462
            # This changed from something into a directory. Make sure we
3151
3463
            # have a directory block for it. This doesn't happen very
3152
3464
            # often, so this doesn't have to be super fast.
3154
3466
                state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3155
3467
            state._ensure_block(block_index, entry_index,
3156
3468
                               osutils.pathjoin(entry[0][0], entry[0][1]))
3157
 
    elif minikind == 'l':
 
3469
        else:
 
3470
            worth_saving = False
 
3471
    elif minikind == b'l':
 
3472
        if saved_minikind == b'l':
 
3473
            worth_saving = False
3158
3474
        link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3159
3475
        if state._cutoff_time is None:
3160
3476
            state._sha_cutoff_time()
3161
3477
        if (stat_value.st_mtime < state._cutoff_time
3162
3478
            and stat_value.st_ctime < state._cutoff_time):
3163
 
            entry[1][0] = ('l', link_or_sha1, stat_value.st_size,
 
3479
            entry[1][0] = (b'l', link_or_sha1, stat_value.st_size,
3164
3480
                           False, packed_stat)
3165
3481
        else:
3166
 
            entry[1][0] = ('l', '', stat_value.st_size,
 
3482
            entry[1][0] = (b'l', b'', stat_value.st_size,
3167
3483
                           False, DirState.NULLSTAT)
3168
 
    state._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
3484
    if worth_saving:
 
3485
        state._mark_modified([entry])
3169
3486
    return link_or_sha1
3170
3487
 
3171
3488
 
3184
3501
        self.old_dirname_to_file_id = {}
3185
3502
        self.new_dirname_to_file_id = {}
3186
3503
        # Are we doing a partial iter_changes?
3187
 
        self.partial = search_specific_files != set([''])
 
3504
        self.partial = search_specific_files != {''}
3188
3505
        # Using a list so that we can access the values and change them in
3189
3506
        # nested scope. Each one is [path, file_id, entry]
3190
3507
        self.last_source_parent = [None, None]
3236
3553
            source_details = DirState.NULL_PARENT_DETAILS
3237
3554
        else:
3238
3555
            source_details = entry[1][self.source_index]
 
3556
        # GZ 2017-06-09: Eck, more sets.
 
3557
        _fdltr = {b'f', b'd', b'l', b't', b'r'}
 
3558
        _fdlt = {b'f', b'd', b'l', b't'}
 
3559
        _ra = (b'r', b'a')
3239
3560
        target_details = entry[1][self.target_index]
3240
3561
        target_minikind = target_details[0]
3241
 
        if path_info is not None and target_minikind in 'fdlt':
 
3562
        if path_info is not None and target_minikind in _fdlt:
3242
3563
            if not (self.target_index == 0):
3243
3564
                raise AssertionError()
3244
3565
            link_or_sha1 = update_entry(self.state, entry,
3250
3571
            link_or_sha1 = None
3251
3572
        file_id = entry[0][2]
3252
3573
        source_minikind = source_details[0]
3253
 
        if source_minikind in 'fdltr' and target_minikind in 'fdlt':
 
3574
        if source_minikind in _fdltr and target_minikind in _fdlt:
3254
3575
            # claimed content in both: diff
3255
3576
            #   r    | fdlt   |      | add source to search, add id path move and perform
3256
3577
            #        |        |      | diff check on source-target
3257
3578
            #   r    | fdlt   |  a   | dangling file that was present in the basis.
3258
3579
            #        |        |      | ???
3259
 
            if source_minikind in 'r':
 
3580
            if source_minikind == b'r':
3260
3581
                # add the source to the search path to find any children it
3261
3582
                # has.  TODO ? : only add if it is a container ?
3262
3583
                if not osutils.is_inside_any(self.searched_specific_files,
3272
3593
                # update the source details variable to be the real
3273
3594
                # location.
3274
3595
                if old_entry == (None, None):
3275
 
                    raise errors.CorruptDirstate(self.state._filename,
 
3596
                    raise DirstateCorrupt(self.state._filename,
3276
3597
                        "entry '%s/%s' is considered renamed from %r"
3277
3598
                        " but source does not exist\n"
3278
3599
                        "entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
3294
3615
                    if path is None:
3295
3616
                        old_path = path = pathjoin(old_dirname, old_basename)
3296
3617
                    self.new_dirname_to_file_id[path] = file_id
3297
 
                    if source_minikind != 'd':
 
3618
                    if source_minikind != b'd':
3298
3619
                        content_change = True
3299
3620
                    else:
3300
3621
                        # directories have no fingerprint
3301
3622
                        content_change = False
3302
3623
                    target_exec = False
3303
3624
                elif target_kind == 'file':
3304
 
                    if source_minikind != 'f':
 
3625
                    if source_minikind != b'f':
3305
3626
                        content_change = True
3306
3627
                    else:
3307
3628
                        # Check the sha. We can't just rely on the size as
3323
3644
                    else:
3324
3645
                        target_exec = target_details[3]
3325
3646
                elif target_kind == 'symlink':
3326
 
                    if source_minikind != 'l':
 
3647
                    if source_minikind != b'l':
3327
3648
                        content_change = True
3328
3649
                    else:
3329
3650
                        content_change = (link_or_sha1 != source_details[1])
3330
3651
                    target_exec = False
3331
3652
                elif target_kind == 'tree-reference':
3332
 
                    if source_minikind != 't':
 
3653
                    if source_minikind != b't':
3333
3654
                        content_change = True
3334
3655
                    else:
3335
3656
                        content_change = False
3338
3659
                    if path is None:
3339
3660
                        path = pathjoin(old_dirname, old_basename)
3340
3661
                    raise errors.BadFileKindError(path, path_info[2])
3341
 
            if source_minikind == 'd':
 
3662
            if source_minikind == b'd':
3342
3663
                if path is None:
3343
3664
                    old_path = path = pathjoin(old_dirname, old_basename)
3344
3665
                self.old_dirname_to_file_id[old_path] = file_id
3409
3730
                       (self.utf8_decode(old_basename)[0], self.utf8_decode(entry[0][1])[0]),
3410
3731
                       (source_kind, target_kind),
3411
3732
                       (source_exec, target_exec)), changed
3412
 
        elif source_minikind in 'a' and target_minikind in 'fdlt':
 
3733
        elif source_minikind in b'a' and target_minikind in _fdlt:
3413
3734
            # looks like a new file
3414
3735
            path = pathjoin(entry[0][0], entry[0][1])
3415
3736
            # parent id is the entry for the path in the target tree
3446
3767
                       (None, self.utf8_decode(entry[0][1])[0]),
3447
3768
                       (None, None),
3448
3769
                       (None, False)), True
3449
 
        elif source_minikind in 'fdlt' and target_minikind in 'a':
 
3770
        elif source_minikind in _fdlt and target_minikind in b'a':
3450
3771
            # unversioned, possibly, or possibly not deleted: we dont care.
3451
3772
            # if its still on disk, *and* theres no other entry at this
3452
3773
            # path [we dont know this in this routine at the moment -
3464
3785
                   (self.utf8_decode(entry[0][1])[0], None),
3465
3786
                   (DirState._minikind_to_kind[source_minikind], None),
3466
3787
                   (source_details[3], None)), True
3467
 
        elif source_minikind in 'fdlt' and target_minikind in 'r':
 
3788
        elif source_minikind in _fdlt and target_minikind in b'r':
3468
3789
            # a rename; could be a true rename, or a rename inherited from
3469
3790
            # a renamed parent. TODO: handle this efficiently. Its not
3470
3791
            # common case to rename dirs though, so a correct but slow
3471
3792
            # implementation will do.
3472
3793
            if not osutils.is_inside_any(self.searched_specific_files, target_details[1]):
3473
3794
                self.search_specific_files.add(target_details[1])
3474
 
        elif source_minikind in 'ra' and target_minikind in 'ra':
 
3795
        elif source_minikind in _ra and target_minikind in _ra:
3475
3796
            # neither of the selected trees contain this file,
3476
3797
            # so skip over it. This is not currently directly tested, but
3477
3798
            # is indirectly via test_too_much.TestCommands.test_conflicts.
3480
3801
            raise AssertionError("don't know how to compare "
3481
3802
                "source_minikind=%r, target_minikind=%r"
3482
3803
                % (source_minikind, target_minikind))
3483
 
            ## import pdb;pdb.set_trace()
3484
3804
        return None, None
3485
3805
 
3486
3806
    def __iter__(self):
3503
3823
                osutils.parent_directories(new_path.encode('utf8')))
3504
3824
            # Add the root directory which parent_directories does not
3505
3825
            # provide.
3506
 
            self.search_specific_file_parents.add('')
 
3826
            self.search_specific_file_parents.add(b'')
3507
3827
 
3508
3828
    def iter_changes(self):
3509
3829
        """Iterate over the changes."""
3510
3830
        utf8_decode = cache_utf8._utf8_decode
3511
 
        _cmp_by_dirs = cmp_by_dirs
 
3831
        _lt_by_dirs = lt_by_dirs
3512
3832
        _process_entry = self._process_entry
3513
3833
        search_specific_files = self.search_specific_files
3514
3834
        searched_specific_files = self.searched_specific_files
3563
3883
            root_abspath = self.tree.abspath(current_root_unicode)
3564
3884
            try:
3565
3885
                root_stat = os.lstat(root_abspath)
3566
 
            except OSError, e:
 
3886
            except OSError as e:
3567
3887
                if e.errno == errno.ENOENT:
3568
3888
                    # the path does not exist: let _process_entry know that.
3569
3889
                    root_dir_info = None
3571
3891
                    # some other random error: hand it up.
3572
3892
                    raise
3573
3893
            else:
3574
 
                root_dir_info = ('', current_root,
 
3894
                root_dir_info = (b'', current_root,
3575
3895
                    osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
3576
3896
                    root_abspath)
3577
3897
                if root_dir_info[2] == 'directory':
3605
3925
                       (None, root_dir_info[2]),
3606
3926
                       (None, new_executable)
3607
3927
                      )
3608
 
            initial_key = (current_root, '', '')
 
3928
            initial_key = (current_root, b'', b'')
3609
3929
            block_index, _ = self.state._find_block_index_from_key(initial_key)
3610
3930
            if block_index == 0:
3611
3931
                # we have processed the total root already, but because the
3616
3936
            else:
3617
3937
                dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
3618
3938
                try:
3619
 
                    current_dir_info = dir_iterator.next()
3620
 
                except OSError, e:
 
3939
                    current_dir_info = next(dir_iterator)
 
3940
                except OSError as e:
3621
3941
                    # on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
3622
3942
                    # python 2.5 has e.errno == EINVAL,
3623
3943
                    #            and e.winerror == ERROR_DIRECTORY
3635
3955
                    else:
3636
3956
                        raise
3637
3957
                else:
3638
 
                    if current_dir_info[0][0] == '':
 
3958
                    if current_dir_info[0][0] == b'':
3639
3959
                        # remove .bzr from iteration
3640
 
                        bzr_index = bisect.bisect_left(current_dir_info[1], ('.bzr',))
3641
 
                        if current_dir_info[1][bzr_index][0] != '.bzr':
 
3960
                        bzr_index = bisect.bisect_left(current_dir_info[1], (b'.bzr',))
 
3961
                        if current_dir_info[1][bzr_index][0] != b'.bzr':
3642
3962
                            raise AssertionError()
3643
3963
                        del current_dir_info[1][bzr_index]
3644
3964
            # walk until both the directory listing and the versioned metadata
3652
3972
                   current_block is not None):
3653
3973
                if (current_dir_info and current_block
3654
3974
                    and current_dir_info[0][0] != current_block[0]):
3655
 
                    if _cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
 
3975
                    if _lt_by_dirs(current_dir_info[0][0], current_block[0]):
3656
3976
                        # filesystem data refers to paths not covered by the dirblock.
3657
3977
                        # this has two possibilities:
3658
3978
                        # A) it is versioned but empty, so there is no block for it
3692
4012
 
3693
4013
                        # This dir info has been handled, go to the next
3694
4014
                        try:
3695
 
                            current_dir_info = dir_iterator.next()
 
4015
                            current_dir_info = next(dir_iterator)
3696
4016
                        except StopIteration:
3697
4017
                            current_dir_info = None
3698
4018
                    else:
3753
4073
                            if changed or self.include_unchanged:
3754
4074
                                yield result
3755
4075
                    elif (current_entry[0][1] != current_path_info[1]
3756
 
                          or current_entry[1][self.target_index][0] in 'ar'):
 
4076
                          or current_entry[1][self.target_index][0] in (b'a', b'r')):
3757
4077
                        # The current path on disk doesn't match the dirblock
3758
4078
                        # record. Either the dirblock is marked as absent, or
3759
4079
                        # the file on disk is not present at all in the
3844
4164
                        current_block = None
3845
4165
                if current_dir_info is not None:
3846
4166
                    try:
3847
 
                        current_dir_info = dir_iterator.next()
 
4167
                        current_dir_info = next(dir_iterator)
3848
4168
                    except StopIteration:
3849
4169
                        current_dir_info = None
3850
4170
        for result in self._iter_specific_file_parents():
3872
4192
            found_item = False
3873
4193
            for candidate_entry in path_entries:
3874
4194
                # Find entries present in target at this path:
3875
 
                if candidate_entry[1][self.target_index][0] not in 'ar':
 
4195
                if candidate_entry[1][self.target_index][0] not in (b'a', b'r'):
3876
4196
                    found_item = True
3877
4197
                    selected_entries.append(candidate_entry)
3878
4198
                # Find entries present in source at this path:
3879
4199
                elif (self.source_index is not None and
3880
 
                    candidate_entry[1][self.source_index][0] not in 'ar'):
 
4200
                    candidate_entry[1][self.source_index][0] not in (b'a', b'r')):
3881
4201
                    found_item = True
3882
 
                    if candidate_entry[1][self.target_index][0] == 'a':
 
4202
                    if candidate_entry[1][self.target_index][0] == b'a':
3883
4203
                        # Deleted, emit it here.
3884
4204
                        selected_entries.append(candidate_entry)
3885
4205
                    else:
3909
4229
                        result[6][1] != 'directory'):
3910
4230
                        # This stopped being a directory, the old children have
3911
4231
                        # to be included.
3912
 
                        if entry[1][self.source_index][0] == 'r':
 
4232
                        if entry[1][self.source_index][0] == b'r':
3913
4233
                            # renamed, take the source path
3914
4234
                            entry_path_utf8 = entry[1][self.source_index][1]
3915
4235
                        else:
3916
4236
                            entry_path_utf8 = path_utf8
3917
 
                        initial_key = (entry_path_utf8, '', '')
 
4237
                        initial_key = (entry_path_utf8, b'', b'')
3918
4238
                        block_index, _ = self.state._find_block_index_from_key(
3919
4239
                            initial_key)
3920
4240
                        if block_index == 0:
3929
4249
                                current_block = None
3930
4250
                        if current_block is not None:
3931
4251
                            for entry in current_block[1]:
3932
 
                                if entry[1][self.source_index][0] in 'ar':
 
4252
                                if entry[1][self.source_index][0] in (b'a', b'r'):
3933
4253
                                    # Not in the source tree, so doesn't have to be
3934
4254
                                    # included.
3935
4255
                                    continue
3949
4269
        abspath = self.tree.abspath(unicode_path)
3950
4270
        try:
3951
4271
            stat = os.lstat(abspath)
3952
 
        except OSError, e:
 
4272
        except OSError as e:
3953
4273
            if e.errno == errno.ENOENT:
3954
4274
                # the path does not exist.
3955
4275
                return None
3956
4276
            else:
3957
4277
                raise
3958
 
        utf8_basename = utf8_path.rsplit('/', 1)[-1]
 
4278
        utf8_basename = utf8_path.rsplit(b'/', 1)[-1]
3959
4279
        dir_info = (utf8_path, utf8_basename,
3960
4280
            osutils.file_kind_from_stat_mode(stat.st_mode), stat,
3961
4281
            abspath)
3969
4289
 
3970
4290
# Try to load the compiled form if possible
3971
4291
try:
3972
 
    from bzrlib._dirstate_helpers_pyx import (
 
4292
    from ._dirstate_helpers_pyx import (
3973
4293
        _read_dirblocks,
3974
4294
        bisect_dirblock,
3975
4295
        _bisect_path_left,
3976
4296
        _bisect_path_right,
3977
 
        cmp_by_dirs,
 
4297
        lt_by_dirs,
 
4298
        pack_stat,
3978
4299
        ProcessEntryC as _process_entry,
3979
4300
        update_entry as update_entry,
3980
4301
        )
3981
 
except ImportError, e:
 
4302
except ImportError as e:
3982
4303
    osutils.failed_to_load_extension(e)
3983
 
    from bzrlib._dirstate_helpers_py import (
 
4304
    from ._dirstate_helpers_py import (
3984
4305
        _read_dirblocks,
3985
4306
        bisect_dirblock,
3986
4307
        _bisect_path_left,
3987
4308
        _bisect_path_right,
3988
 
        cmp_by_dirs,
 
4309
        lt_by_dirs,
 
4310
        pack_stat,
3989
4311
        )
3990
4312
    # FIXME: It would be nice to be able to track moved lines so that the
3991
4313
    # corresponding python code can be moved to the _dirstate_helpers_py