~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

Abbreviate pack_stat struct format to '>6L'

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
202
219
"""
203
220
 
204
221
import bisect
205
 
import binascii
206
222
import errno
207
223
import operator
208
224
import os
209
225
from stat import S_IEXEC
210
226
import stat
211
 
import struct
212
227
import sys
213
228
import time
214
229
import zlib
215
230
 
216
231
from bzrlib import (
217
232
    cache_utf8,
 
233
    config,
218
234
    debug,
219
235
    errors,
220
236
    inventory,
222
238
    osutils,
223
239
    static_tuple,
224
240
    trace,
 
241
    urlutils,
225
242
    )
226
243
 
227
244
 
232
249
ERROR_DIRECTORY = 267
233
250
 
234
251
 
235
 
if not getattr(struct, '_compile', None):
236
 
    # Cannot pre-compile the dirstate pack_stat
237
 
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
238
 
        """Convert stat values into a packed representation."""
239
 
        return _encode(_pack('>LLLLLL', st.st_size, int(st.st_mtime),
240
 
            int(st.st_ctime), st.st_dev, st.st_ino & 0xFFFFFFFF,
241
 
            st.st_mode))[:-1]
242
 
else:
243
 
    # compile the struct compiler we need, so as to only do it once
244
 
    from _struct import Struct
245
 
    _compiled_pack = Struct('>LLLLLL').pack
246
 
    def pack_stat(st, _encode=binascii.b2a_base64, _pack=_compiled_pack):
247
 
        """Convert stat values into a packed representation."""
248
 
        # jam 20060614 it isn't really worth removing more entries if we
249
 
        # are going to leave it in packed form.
250
 
        # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
251
 
        # With all entries, filesize is 5.9M and read time is maybe 280ms
252
 
        # well within the noise margin
253
 
 
254
 
        # base64 encoding always adds a final newline, so strip it off
255
 
        # The current version
256
 
        return _encode(_pack(st.st_size, int(st.st_mtime), int(st.st_ctime),
257
 
            st.st_dev, st.st_ino & 0xFFFFFFFF, st.st_mode))[:-1]
258
 
        # This is 0.060s / 1.520s faster by not encoding as much information
259
 
        # return _encode(_pack('>LL', int(st.st_mtime), st.st_mode))[:-1]
260
 
        # This is not strictly faster than _encode(_pack())[:-1]
261
 
        # return '%X.%X.%X.%X.%X.%X' % (
262
 
        #      st.st_size, int(st.st_mtime), int(st.st_ctime),
263
 
        #      st.st_dev, st.st_ino, st.st_mode)
264
 
        # Similar to the _encode(_pack('>LL'))
265
 
        # return '%X.%X' % (int(st.st_mtime), st.st_mode)
266
 
 
267
 
 
268
 
def _unpack_stat(packed_stat):
269
 
    """Turn a packed_stat back into the stat fields.
270
 
 
271
 
    This is meant as a debugging tool, should not be used in real code.
272
 
    """
273
 
    (st_size, st_mtime, st_ctime, st_dev, st_ino,
274
 
     st_mode) = struct.unpack('>LLLLLL', binascii.a2b_base64(packed_stat))
275
 
    return dict(st_size=st_size, st_mtime=st_mtime, st_ctime=st_ctime,
276
 
                st_dev=st_dev, st_ino=st_ino, st_mode=st_mode)
277
 
 
278
 
 
279
252
class SHA1Provider(object):
280
253
    """An interface for getting sha1s of a file."""
281
254
 
371
344
    # A pack_stat (the x's) that is just noise and will never match the output
372
345
    # of base64 encode.
373
346
    NULLSTAT = 'x' * 32
374
 
    NULL_PARENT_DETAILS = ('a', '', 0, False, '')
 
347
    NULL_PARENT_DETAILS = static_tuple.StaticTuple('a', '', 0, False, '')
375
348
 
376
349
    HEADER_FORMAT_2 = '#bazaar dirstate flat format 2\n'
377
350
    HEADER_FORMAT_3 = '#bazaar dirstate flat format 3\n'
431
404
        self._known_hash_changes = set()
432
405
        # How many hash changed entries can we have without saving
433
406
        self._worth_saving_limit = worth_saving_limit
 
407
        self._config_stack = config.LocationStack(urlutils.local_path_to_url(
 
408
            path))
434
409
 
435
410
    def __repr__(self):
436
411
        return "%s(%r)" % \
1205
1180
    def _fields_per_entry(self):
1206
1181
        """How many null separated fields should be in each entry row.
1207
1182
 
1208
 
        Each line now has an extra '\n' field which is not used
 
1183
        Each line now has an extra '\\n' field which is not used
1209
1184
        so we just skip over it
1210
 
        entry size:
 
1185
 
 
1186
        entry size::
1211
1187
            3 fields for the key
1212
1188
            + number of fields per tree_data (5) * tree count
1213
1189
            + newline
1326
1302
            raise
1327
1303
        return result
1328
1304
 
 
1305
    def _check_delta_is_valid(self, delta):
 
1306
        return list(inventory._check_delta_unique_ids(
 
1307
                    inventory._check_delta_unique_old_paths(
 
1308
                    inventory._check_delta_unique_new_paths(
 
1309
                    inventory._check_delta_ids_match_entry(
 
1310
                    inventory._check_delta_ids_are_valid(
 
1311
                    inventory._check_delta_new_path_entry_both_or_None(delta)))))))
 
1312
 
1329
1313
    def update_by_delta(self, delta):
1330
1314
        """Apply an inventory delta to the dirstate for tree 0
1331
1315
 
1349
1333
        new_ids = set()
1350
1334
        # This loop transforms the delta to single atomic operations that can
1351
1335
        # be executed and validated.
1352
 
        for old_path, new_path, file_id, inv_entry in sorted(
1353
 
            inventory._check_delta_unique_old_paths(
1354
 
            inventory._check_delta_unique_new_paths(
1355
 
            inventory._check_delta_ids_match_entry(
1356
 
            inventory._check_delta_ids_are_valid(
1357
 
            inventory._check_delta_new_path_entry_both_or_None(delta))))),
1358
 
            reverse=True):
 
1336
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
 
1337
        for old_path, new_path, file_id, inv_entry in delta:
1359
1338
            if (file_id in insertions) or (file_id in removals):
1360
 
                raise errors.InconsistentDelta(old_path or new_path, file_id,
 
1339
                self._raise_invalid(old_path or new_path, file_id,
1361
1340
                    "repeated file_id")
1362
1341
            if old_path is not None:
1363
1342
                old_path = old_path.encode('utf-8')
1366
1345
                new_ids.add(file_id)
1367
1346
            if new_path is not None:
1368
1347
                if inv_entry is None:
1369
 
                    raise errors.InconsistentDelta(new_path, file_id,
 
1348
                    self._raise_invalid(new_path, file_id,
1370
1349
                        "new_path with no entry")
1371
1350
                new_path = new_path.encode('utf-8')
1372
1351
                dirname_utf8, basename = osutils.split(new_path)
1413
1392
            # _get_entry raises BzrError when a request is inconsistent; we
1414
1393
            # want such errors to be shown as InconsistentDelta - and that 
1415
1394
            # fits the behaviour we trigger.
1416
 
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
 
1395
            raise errors.InconsistentDeltaDelta(delta,
 
1396
                "error from _get_entry. %s" % (e,))
1417
1397
 
1418
1398
    def _apply_removals(self, removals):
1419
1399
        for file_id, path in sorted(removals, reverse=True,
1424
1404
            try:
1425
1405
                entry = self._dirblocks[block_i][1][entry_i]
1426
1406
            except IndexError:
1427
 
                self._changes_aborted = True
1428
 
                raise errors.InconsistentDelta(path, file_id,
 
1407
                self._raise_invalid(path, file_id,
1429
1408
                    "Wrong path for old path.")
1430
1409
            if not f_present or entry[1][0][0] in 'ar':
1431
 
                self._changes_aborted = True
1432
 
                raise errors.InconsistentDelta(path, file_id,
 
1410
                self._raise_invalid(path, file_id,
1433
1411
                    "Wrong path for old path.")
1434
1412
            if file_id != entry[0][2]:
1435
 
                self._changes_aborted = True
1436
 
                raise errors.InconsistentDelta(path, file_id,
 
1413
                self._raise_invalid(path, file_id,
1437
1414
                    "Attempt to remove path has wrong id - found %r."
1438
1415
                    % entry[0][2])
1439
1416
            self._make_absent(entry)
1449
1426
                # be due to it being in a parent tree, or a corrupt delta.
1450
1427
                for child_entry in self._dirblocks[block_i][1]:
1451
1428
                    if child_entry[1][0][0] not in ('r', 'a'):
1452
 
                        self._changes_aborted = True
1453
 
                        raise errors.InconsistentDelta(path, entry[0][2],
 
1429
                        self._raise_invalid(path, entry[0][2],
1454
1430
                            "The file id was deleted but its children were "
1455
1431
                            "not deleted.")
1456
1432
 
1460
1436
                self.update_minimal(key, minikind, executable, fingerprint,
1461
1437
                                    path_utf8=path_utf8)
1462
1438
        except errors.NotVersionedError:
1463
 
            self._changes_aborted = True
1464
 
            raise errors.InconsistentDelta(path_utf8.decode('utf8'), key[2],
 
1439
            self._raise_invalid(path_utf8.decode('utf8'), key[2],
1465
1440
                "Missing parent")
1466
1441
 
1467
1442
    def update_basis_by_delta(self, delta, new_revid):
1475
1450
        Note that an exception during the operation of this method will leave
1476
1451
        the dirstate in a corrupt state where it should not be saved.
1477
1452
 
1478
 
        Finally, we expect all changes to be synchronising the basis tree with
1479
 
        the working tree.
1480
 
 
1481
1453
        :param new_revid: The new revision id for the trees parent.
1482
1454
        :param delta: An inventory delta (see apply_inventory_delta) describing
1483
1455
            the changes from the current left most parent revision to new_revid.
1495
1467
 
1496
1468
        self._parents[0] = new_revid
1497
1469
 
1498
 
        delta = sorted(delta, reverse=True)
 
1470
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
1499
1471
        adds = []
1500
1472
        changes = []
1501
1473
        deletes = []
1522
1494
        new_ids = set()
1523
1495
        for old_path, new_path, file_id, inv_entry in delta:
1524
1496
            if inv_entry is not None and file_id != inv_entry.file_id:
1525
 
                raise errors.InconsistentDelta(new_path, file_id,
 
1497
                self._raise_invalid(new_path, file_id,
1526
1498
                    "mismatched entry file_id %r" % inv_entry)
1527
 
            if new_path is not None:
 
1499
            if new_path is None:
 
1500
                new_path_utf8 = None
 
1501
            else:
1528
1502
                if inv_entry is None:
1529
 
                    raise errors.InconsistentDelta(new_path, file_id,
 
1503
                    self._raise_invalid(new_path, file_id,
1530
1504
                        "new_path with no entry")
1531
1505
                new_path_utf8 = encode(new_path)
1532
1506
                # note the parent for validation
1534
1508
                if basename_utf8:
1535
1509
                    parents.add((dirname_utf8, inv_entry.parent_id))
1536
1510
            if old_path is None:
1537
 
                adds.append((None, encode(new_path), file_id,
 
1511
                old_path_utf8 = None
 
1512
            else:
 
1513
                old_path_utf8 = encode(old_path)
 
1514
            if old_path is None:
 
1515
                adds.append((None, new_path_utf8, file_id,
1538
1516
                    inv_to_entry(inv_entry), True))
1539
1517
                new_ids.add(file_id)
1540
1518
            elif new_path is None:
1541
 
                deletes.append((encode(old_path), None, file_id, None, True))
1542
 
            elif (old_path, new_path) != root_only:
 
1519
                deletes.append((old_path_utf8, None, file_id, None, True))
 
1520
            elif (old_path, new_path) == root_only:
 
1521
                # change things in-place
 
1522
                # Note: the case of a parent directory changing its file_id
 
1523
                #       tends to break optimizations here, because officially
 
1524
                #       the file has actually been moved, it just happens to
 
1525
                #       end up at the same path. If we can figure out how to
 
1526
                #       handle that case, we can avoid a lot of add+delete
 
1527
                #       pairs for objects that stay put.
 
1528
                # elif old_path == new_path:
 
1529
                changes.append((old_path_utf8, new_path_utf8, file_id,
 
1530
                                inv_to_entry(inv_entry)))
 
1531
            else:
1543
1532
                # Renames:
1544
1533
                # Because renames must preserve their children we must have
1545
1534
                # processed all relocations and removes before hand. The sort
1555
1544
                self._update_basis_apply_deletes(deletes)
1556
1545
                deletes = []
1557
1546
                # Split into an add/delete pair recursively.
1558
 
                adds.append((None, new_path_utf8, file_id,
1559
 
                    inv_to_entry(inv_entry), False))
 
1547
                adds.append((old_path_utf8, new_path_utf8, file_id,
 
1548
                             inv_to_entry(inv_entry), False))
1560
1549
                # Expunge deletes that we've seen so that deleted/renamed
1561
1550
                # children of a rename directory are handled correctly.
1562
 
                new_deletes = reversed(list(self._iter_child_entries(1,
1563
 
                    encode(old_path))))
 
1551
                new_deletes = reversed(list(
 
1552
                    self._iter_child_entries(1, old_path_utf8)))
1564
1553
                # Remove the current contents of the tree at orig_path, and
1565
1554
                # reinsert at the correct new path.
1566
1555
                for entry in new_deletes:
1567
 
                    if entry[0][0]:
1568
 
                        source_path = entry[0][0] + '/' + entry[0][1]
 
1556
                    child_dirname, child_basename, child_file_id = entry[0]
 
1557
                    if child_dirname:
 
1558
                        source_path = child_dirname + '/' + child_basename
1569
1559
                    else:
1570
 
                        source_path = entry[0][1]
 
1560
                        source_path = child_basename
1571
1561
                    if new_path_utf8:
1572
1562
                        target_path = new_path_utf8 + source_path[len(old_path):]
1573
1563
                    else:
1574
1564
                        if old_path == '':
1575
1565
                            raise AssertionError("cannot rename directory to"
1576
 
                                " itself")
 
1566
                                                 " itself")
1577
1567
                        target_path = source_path[len(old_path) + 1:]
1578
1568
                    adds.append((None, target_path, entry[0][2], entry[1][1], False))
1579
1569
                    deletes.append(
1580
1570
                        (source_path, target_path, entry[0][2], None, False))
1581
 
                deletes.append(
1582
 
                    (encode(old_path), new_path, file_id, None, False))
1583
 
            else:
1584
 
                # changes to just the root should not require remove/insertion
1585
 
                # of everything.
1586
 
                changes.append((encode(old_path), encode(new_path), file_id,
1587
 
                    inv_to_entry(inv_entry)))
 
1571
                deletes.append((old_path_utf8, new_path, file_id, None, False))
1588
1572
        self._check_delta_ids_absent(new_ids, delta, 1)
1589
1573
        try:
1590
1574
            # Finish expunging deletes/first half of renames.
1600
1584
            if 'integrity error' not in str(e):
1601
1585
                raise
1602
1586
            # _get_entry raises BzrError when a request is inconsistent; we
1603
 
            # want such errors to be shown as InconsistentDelta - and that 
1604
 
            # fits the behaviour we trigger. Partof this is driven by dirstate
1605
 
            # only supporting deltas that turn the basis into a closer fit to
1606
 
            # the active tree.
1607
 
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
 
1587
            # want such errors to be shown as InconsistentDelta - and that
 
1588
            # fits the behaviour we trigger.
 
1589
            raise errors.InconsistentDeltaDelta(delta,
 
1590
                "error from _get_entry. %s" % (e,))
1608
1591
 
1609
1592
        self._mark_modified(header_modified=True)
1610
1593
        self._id_index = None
1626
1609
                if entry[0][2] != file_id:
1627
1610
                    # Different file_id, so not what we want.
1628
1611
                    continue
1629
 
                # NB: No changes made before this helper is called, so no need
1630
 
                # to set the _changes_aborted flag.
1631
 
                raise errors.InconsistentDelta(
1632
 
                    ("%s/%s" % key[0:2]).decode('utf8'), file_id,
 
1612
                self._raise_invalid(("%s/%s" % key[0:2]).decode('utf8'), file_id,
1633
1613
                    "This file_id is new in the delta but already present in "
1634
1614
                    "the target")
1635
1615
 
 
1616
    def _raise_invalid(self, path, file_id, reason):
 
1617
        self._changes_aborted = True
 
1618
        raise errors.InconsistentDelta(path, file_id, reason)
 
1619
 
1636
1620
    def _update_basis_apply_adds(self, adds):
1637
1621
        """Apply a sequence of adds to tree 1 during update_basis_by_delta.
1638
1622
 
1646
1630
        """
1647
1631
        # Adds are accumulated partly from renames, so can be in any input
1648
1632
        # order - sort it.
1649
 
        adds.sort()
 
1633
        # TODO: we may want to sort in dirblocks order. That way each entry
 
1634
        #       will end up in the same directory, allowing the _get_entry
 
1635
        #       fast-path for looking up 2 items in the same dir work.
 
1636
        adds.sort(key=lambda x: x[1])
1650
1637
        # adds is now in lexographic order, which places all parents before
1651
1638
        # their children, so we can process it linearly.
1652
1639
        absent = 'ar'
 
1640
        st = static_tuple.StaticTuple
1653
1641
        for old_path, new_path, file_id, new_details, real_add in adds:
1654
 
            # the entry for this file_id must be in tree 0.
1655
 
            entry = self._get_entry(0, file_id, new_path)
1656
 
            if entry[0] is None or entry[0][2] != file_id:
1657
 
                self._changes_aborted = True
1658
 
                raise errors.InconsistentDelta(new_path, file_id,
1659
 
                    'working tree does not contain new entry')
1660
 
            if real_add and entry[1][1][0] not in absent:
1661
 
                self._changes_aborted = True
1662
 
                raise errors.InconsistentDelta(new_path, file_id,
1663
 
                    'The entry was considered to be a genuinely new record,'
1664
 
                    ' but there was already an old record for it.')
1665
 
            # We don't need to update the target of an 'r' because the handling
1666
 
            # of renames turns all 'r' situations into a delete at the original
1667
 
            # location.
1668
 
            entry[1][1] = new_details
 
1642
            dirname, basename = osutils.split(new_path)
 
1643
            entry_key = st(dirname, basename, file_id)
 
1644
            block_index, present = self._find_block_index_from_key(entry_key)
 
1645
            if not present:
 
1646
                self._raise_invalid(new_path, file_id,
 
1647
                    "Unable to find block for this record."
 
1648
                    " Was the parent added?")
 
1649
            block = self._dirblocks[block_index][1]
 
1650
            entry_index, present = self._find_entry_index(entry_key, block)
 
1651
            if real_add:
 
1652
                if old_path is not None:
 
1653
                    self._raise_invalid(new_path, file_id,
 
1654
                        'considered a real add but still had old_path at %s'
 
1655
                        % (old_path,))
 
1656
            if present:
 
1657
                entry = block[entry_index]
 
1658
                basis_kind = entry[1][1][0]
 
1659
                if basis_kind == 'a':
 
1660
                    entry[1][1] = new_details
 
1661
                elif basis_kind == 'r':
 
1662
                    raise NotImplementedError()
 
1663
                else:
 
1664
                    self._raise_invalid(new_path, file_id,
 
1665
                        "An entry was marked as a new add"
 
1666
                        " but the basis target already existed")
 
1667
            else:
 
1668
                # The exact key was not found in the block. However, we need to
 
1669
                # check if there is a key next to us that would have matched.
 
1670
                # We only need to check 2 locations, because there are only 2
 
1671
                # trees present.
 
1672
                for maybe_index in range(entry_index-1, entry_index+1):
 
1673
                    if maybe_index < 0 or maybe_index >= len(block):
 
1674
                        continue
 
1675
                    maybe_entry = block[maybe_index]
 
1676
                    if maybe_entry[0][:2] != (dirname, basename):
 
1677
                        # Just a random neighbor
 
1678
                        continue
 
1679
                    if maybe_entry[0][2] == file_id:
 
1680
                        raise AssertionError(
 
1681
                            '_find_entry_index didnt find a key match'
 
1682
                            ' but walking the data did, for %s'
 
1683
                            % (entry_key,))
 
1684
                    basis_kind = maybe_entry[1][1][0]
 
1685
                    if basis_kind not in 'ar':
 
1686
                        self._raise_invalid(new_path, file_id,
 
1687
                            "we have an add record for path, but the path"
 
1688
                            " is already present with another file_id %s"
 
1689
                            % (maybe_entry[0][2],))
 
1690
 
 
1691
                entry = (entry_key, [DirState.NULL_PARENT_DETAILS,
 
1692
                                     new_details])
 
1693
                block.insert(entry_index, entry)
 
1694
 
 
1695
            active_kind = entry[1][0][0]
 
1696
            if active_kind == 'a':
 
1697
                # The active record shows up as absent, this could be genuine,
 
1698
                # or it could be present at some other location. We need to
 
1699
                # verify.
 
1700
                id_index = self._get_id_index()
 
1701
                # The id_index may not be perfectly accurate for tree1, because
 
1702
                # we haven't been keeping it updated. However, it should be
 
1703
                # fine for tree0, and that gives us enough info for what we
 
1704
                # need
 
1705
                keys = id_index.get(file_id, ())
 
1706
                for key in keys:
 
1707
                    block_i, entry_i, d_present, f_present = \
 
1708
                        self._get_block_entry_index(key[0], key[1], 0)
 
1709
                    if not f_present:
 
1710
                        continue
 
1711
                    active_entry = self._dirblocks[block_i][1][entry_i]
 
1712
                    if (active_entry[0][2] != file_id):
 
1713
                        # Some other file is at this path, we don't need to
 
1714
                        # link it.
 
1715
                        continue
 
1716
                    real_active_kind = active_entry[1][0][0]
 
1717
                    if real_active_kind in 'ar':
 
1718
                        # We found a record, which was not *this* record,
 
1719
                        # which matches the file_id, but is not actually
 
1720
                        # present. Something seems *really* wrong.
 
1721
                        self._raise_invalid(new_path, file_id,
 
1722
                            "We found a tree0 entry that doesnt make sense")
 
1723
                    # Now, we've found a tree0 entry which matches the file_id
 
1724
                    # but is at a different location. So update them to be
 
1725
                    # rename records.
 
1726
                    active_dir, active_name = active_entry[0][:2]
 
1727
                    if active_dir:
 
1728
                        active_path = active_dir + '/' + active_name
 
1729
                    else:
 
1730
                        active_path = active_name
 
1731
                    active_entry[1][1] = st('r', new_path, 0, False, '')
 
1732
                    entry[1][0] = st('r', active_path, 0, False, '')
 
1733
            elif active_kind == 'r':
 
1734
                raise NotImplementedError()
 
1735
 
 
1736
            new_kind = new_details[0]
 
1737
            if new_kind == 'd':
 
1738
                self._ensure_block(block_index, entry_index, new_path)
1669
1739
 
1670
1740
    def _update_basis_apply_changes(self, changes):
1671
1741
        """Apply a sequence of changes to tree 1 during update_basis_by_delta.
1676
1746
        absent = 'ar'
1677
1747
        for old_path, new_path, file_id, new_details in changes:
1678
1748
            # the entry for this file_id must be in tree 0.
1679
 
            entry = self._get_entry(0, file_id, new_path)
1680
 
            if entry[0] is None or entry[0][2] != file_id:
1681
 
                self._changes_aborted = True
1682
 
                raise errors.InconsistentDelta(new_path, file_id,
1683
 
                    'working tree does not contain new entry')
1684
 
            if (entry[1][0][0] in absent or
1685
 
                entry[1][1][0] in absent):
1686
 
                self._changes_aborted = True
1687
 
                raise errors.InconsistentDelta(new_path, file_id,
1688
 
                    'changed considered absent')
 
1749
            entry = self._get_entry(1, file_id, new_path)
 
1750
            if entry[0] is None or entry[1][1][0] in 'ar':
 
1751
                self._raise_invalid(new_path, file_id,
 
1752
                    'changed entry considered not present')
1689
1753
            entry[1][1] = new_details
1690
1754
 
1691
1755
    def _update_basis_apply_deletes(self, deletes):
1703
1767
        null = DirState.NULL_PARENT_DETAILS
1704
1768
        for old_path, new_path, file_id, _, real_delete in deletes:
1705
1769
            if real_delete != (new_path is None):
1706
 
                self._changes_aborted = True
1707
 
                raise AssertionError("bad delete delta")
 
1770
                self._raise_invalid(old_path, file_id, "bad delete delta")
1708
1771
            # the entry for this file_id must be in tree 1.
1709
1772
            dirname, basename = osutils.split(old_path)
1710
1773
            block_index, entry_index, dir_present, file_present = \
1711
1774
                self._get_block_entry_index(dirname, basename, 1)
1712
1775
            if not file_present:
1713
 
                self._changes_aborted = True
1714
 
                raise errors.InconsistentDelta(old_path, file_id,
 
1776
                self._raise_invalid(old_path, file_id,
1715
1777
                    'basis tree does not contain removed entry')
1716
1778
            entry = self._dirblocks[block_index][1][entry_index]
 
1779
            # The state of the entry in the 'active' WT
 
1780
            active_kind = entry[1][0][0]
1717
1781
            if entry[0][2] != file_id:
1718
 
                self._changes_aborted = True
1719
 
                raise errors.InconsistentDelta(old_path, file_id,
 
1782
                self._raise_invalid(old_path, file_id,
1720
1783
                    'mismatched file_id in tree 1')
1721
 
            if real_delete:
1722
 
                if entry[1][0][0] != 'a':
1723
 
                    self._changes_aborted = True
1724
 
                    raise errors.InconsistentDelta(old_path, file_id,
1725
 
                            'This was marked as a real delete, but the WT state'
1726
 
                            ' claims that it still exists and is versioned.')
 
1784
            dir_block = ()
 
1785
            old_kind = entry[1][1][0]
 
1786
            if active_kind in 'ar':
 
1787
                # The active tree doesn't have this file_id.
 
1788
                # The basis tree is changing this record. If this is a
 
1789
                # rename, then we don't want the record here at all
 
1790
                # anymore. If it is just an in-place change, we want the
 
1791
                # record here, but we'll add it if we need to. So we just
 
1792
                # delete it
 
1793
                if active_kind == 'r':
 
1794
                    active_path = entry[1][0][1]
 
1795
                    active_entry = self._get_entry(0, file_id, active_path)
 
1796
                    if active_entry[1][1][0] != 'r':
 
1797
                        self._raise_invalid(old_path, file_id,
 
1798
                            "Dirstate did not have matching rename entries")
 
1799
                    elif active_entry[1][0][0] in 'ar':
 
1800
                        self._raise_invalid(old_path, file_id,
 
1801
                            "Dirstate had a rename pointing at an inactive"
 
1802
                            " tree0")
 
1803
                    active_entry[1][1] = null
1727
1804
                del self._dirblocks[block_index][1][entry_index]
 
1805
                if old_kind == 'd':
 
1806
                    # This was a directory, and the active tree says it
 
1807
                    # doesn't exist, and now the basis tree says it doesn't
 
1808
                    # exist. Remove its dirblock if present
 
1809
                    (dir_block_index,
 
1810
                     present) = self._find_block_index_from_key(
 
1811
                        (old_path, '', ''))
 
1812
                    if present:
 
1813
                        dir_block = self._dirblocks[dir_block_index][1]
 
1814
                        if not dir_block:
 
1815
                            # This entry is empty, go ahead and just remove it
 
1816
                            del self._dirblocks[dir_block_index]
1728
1817
            else:
1729
 
                if entry[1][0][0] == 'a':
1730
 
                    self._changes_aborted = True
1731
 
                    raise errors.InconsistentDelta(old_path, file_id,
1732
 
                        'The entry was considered a rename, but the source path'
1733
 
                        ' is marked as absent.')
1734
 
                    # For whatever reason, we were asked to rename an entry
1735
 
                    # that was originally marked as deleted. This could be
1736
 
                    # because we are renaming the parent directory, and the WT
1737
 
                    # current state has the file marked as deleted.
1738
 
                elif entry[1][0][0] == 'r':
1739
 
                    # implement the rename
1740
 
                    del self._dirblocks[block_index][1][entry_index]
1741
 
                else:
1742
 
                    # it is being resurrected here, so blank it out temporarily.
1743
 
                    self._dirblocks[block_index][1][entry_index][1][1] = null
 
1818
                # There is still an active record, so just mark this
 
1819
                # removed.
 
1820
                entry[1][1] = null
 
1821
                block_i, entry_i, d_present, f_present = \
 
1822
                    self._get_block_entry_index(old_path, '', 1)
 
1823
                if d_present:
 
1824
                    dir_block = self._dirblocks[block_i][1]
 
1825
            for child_entry in dir_block:
 
1826
                child_basis_kind = child_entry[1][1][0]
 
1827
                if child_basis_kind not in 'ar':
 
1828
                    self._raise_invalid(old_path, file_id,
 
1829
                        "The file id was deleted but its children were "
 
1830
                        "not deleted.")
1744
1831
 
1745
1832
    def _after_delta_check_parents(self, parents, index):
1746
1833
        """Check that parents required by the delta are all intact.
1755
1842
            # has the right file id.
1756
1843
            entry = self._get_entry(index, file_id, dirname_utf8)
1757
1844
            if entry[1] is None:
1758
 
                self._changes_aborted = True
1759
 
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
 
1845
                self._raise_invalid(dirname_utf8.decode('utf8'),
1760
1846
                    file_id, "This parent is not present.")
1761
1847
            # Parents of things must be directories
1762
1848
            if entry[1][index][0] != 'd':
1763
 
                self._changes_aborted = True
1764
 
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
 
1849
                self._raise_invalid(dirname_utf8.decode('utf8'),
1765
1850
                    file_id, "This parent is not a directory.")
1766
1851
 
1767
1852
    def _observed_sha1(self, entry, sha1, stat_value,
1768
 
        _stat_to_minikind=_stat_to_minikind, _pack_stat=pack_stat):
 
1853
        _stat_to_minikind=_stat_to_minikind):
1769
1854
        """Note the sha1 of a file.
1770
1855
 
1771
1856
        :param entry: The entry the sha1 is for.
1777
1862
        except KeyError:
1778
1863
            # Unhandled kind
1779
1864
            return None
1780
 
        packed_stat = _pack_stat(stat_value)
1781
1865
        if minikind == 'f':
1782
1866
            if self._cutoff_time is None:
1783
1867
                self._sha_cutoff_time()
1784
1868
            if (stat_value.st_mtime < self._cutoff_time
1785
1869
                and stat_value.st_ctime < self._cutoff_time):
1786
1870
                entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
1787
 
                               packed_stat)
 
1871
                               pack_stat(stat_value))
1788
1872
                self._mark_modified([entry])
1789
1873
 
1790
1874
    def _sha_cutoff_time(self):
1993
2077
            entry_index += 1
1994
2078
        return block_index, entry_index, True, False
1995
2079
 
1996
 
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None, include_deleted=False):
 
2080
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None,
 
2081
                   include_deleted=False):
1997
2082
        """Get the dirstate entry for path in tree tree_index.
1998
2083
 
1999
2084
        If either file_id or path is supplied, it is used as the key to lookup.
2198
2283
 
2199
2284
    def _get_id_index(self):
2200
2285
        """Get an id index of self._dirblocks.
2201
 
        
 
2286
 
2202
2287
        This maps from file_id => [(directory, name, file_id)] entries where
2203
2288
        that file_id appears in one of the trees.
2204
2289
        """
2218
2303
        # such, we use a simple tuple, and do our own uniqueness checks. While
2219
2304
        # the 'in' check is O(N) since N is nicely bounded it shouldn't ever
2220
2305
        # cause quadratic failure.
2221
 
        # TODO: This should use StaticTuple
2222
2306
        file_id = entry_key[2]
2223
2307
        entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
2224
2308
        if file_id not in id_index:
2343
2427
            raise errors.BzrError('missing num_entries line')
2344
2428
        self._num_entries = int(num_entries_line[len('num_entries: '):-1])
2345
2429
 
2346
 
    def sha1_from_stat(self, path, stat_result, _pack_stat=pack_stat):
 
2430
    def sha1_from_stat(self, path, stat_result):
2347
2431
        """Find a sha1 given a stat lookup."""
2348
 
        return self._get_packed_stat_index().get(_pack_stat(stat_result), None)
 
2432
        return self._get_packed_stat_index().get(pack_stat(stat_result), None)
2349
2433
 
2350
2434
    def _get_packed_stat_index(self):
2351
2435
        """Get a packed_stat index of self._dirblocks."""
2381
2465
        #       IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail
2382
2466
        #       to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we
2383
2467
        #       fail to save IN_MEMORY_MODIFIED
2384
 
        if self._worth_saving():
2385
 
            grabbed_write_lock = False
2386
 
            if self._lock_state != 'w':
2387
 
                grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
2388
 
                # Switch over to the new lock, as the old one may be closed.
 
2468
        if not self._worth_saving():
 
2469
            return
 
2470
 
 
2471
        grabbed_write_lock = False
 
2472
        if self._lock_state != 'w':
 
2473
            grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
 
2474
            # Switch over to the new lock, as the old one may be closed.
 
2475
            # TODO: jam 20070315 We should validate the disk file has
 
2476
            #       not changed contents, since temporary_write_lock may
 
2477
            #       not be an atomic operation.
 
2478
            self._lock_token = new_lock
 
2479
            self._state_file = new_lock.f
 
2480
            if not grabbed_write_lock:
 
2481
                # We couldn't grab a write lock, so we switch back to a read one
 
2482
                return
 
2483
        try:
 
2484
            lines = self.get_lines()
 
2485
            self._state_file.seek(0)
 
2486
            self._state_file.writelines(lines)
 
2487
            self._state_file.truncate()
 
2488
            self._state_file.flush()
 
2489
            self._maybe_fdatasync()
 
2490
            self._mark_unmodified()
 
2491
        finally:
 
2492
            if grabbed_write_lock:
 
2493
                self._lock_token = self._lock_token.restore_read_lock()
 
2494
                self._state_file = self._lock_token.f
2389
2495
                # TODO: jam 20070315 We should validate the disk file has
2390
 
                #       not changed contents. Since temporary_write_lock may
2391
 
                #       not be an atomic operation.
2392
 
                self._lock_token = new_lock
2393
 
                self._state_file = new_lock.f
2394
 
                if not grabbed_write_lock:
2395
 
                    # We couldn't grab a write lock, so we switch back to a read one
2396
 
                    return
2397
 
            try:
2398
 
                lines = self.get_lines()
2399
 
                self._state_file.seek(0)
2400
 
                self._state_file.writelines(lines)
2401
 
                self._state_file.truncate()
2402
 
                self._state_file.flush()
2403
 
                self._mark_unmodified()
2404
 
            finally:
2405
 
                if grabbed_write_lock:
2406
 
                    self._lock_token = self._lock_token.restore_read_lock()
2407
 
                    self._state_file = self._lock_token.f
2408
 
                    # TODO: jam 20070315 We should validate the disk file has
2409
 
                    #       not changed contents. Since restore_read_lock may
2410
 
                    #       not be an atomic operation.
 
2496
                #       not changed contents. Since restore_read_lock may
 
2497
                #       not be an atomic operation.                
 
2498
 
 
2499
    def _maybe_fdatasync(self):
 
2500
        """Flush to disk if possible and if not configured off."""
 
2501
        if self._config_stack.get('dirstate.fdatasync'):
 
2502
            osutils.fdatasync(self._state_file.fileno())
2411
2503
 
2412
2504
    def _worth_saving(self):
2413
2505
        """Is it worth saving the dirstate or not?"""
2899
2991
                            # This entry has the same path (but a different id) as
2900
2992
                            # the new entry we're adding, and is present in ths
2901
2993
                            # tree.
2902
 
                            raise errors.InconsistentDelta(
 
2994
                            self._raise_invalid(
2903
2995
                                ("%s/%s" % key[0:2]).decode('utf8'), key[2],
2904
2996
                                "Attempt to add item at path already occupied by "
2905
2997
                                "id %r" % entry[0][2])
3258
3350
 
3259
3351
 
3260
3352
def py_update_entry(state, entry, abspath, stat_value,
3261
 
                 _stat_to_minikind=DirState._stat_to_minikind,
3262
 
                 _pack_stat=pack_stat):
 
3353
                 _stat_to_minikind=DirState._stat_to_minikind):
3263
3354
    """Update the entry based on what is actually on disk.
3264
3355
 
3265
3356
    This function only calculates the sha if it needs to - if the entry is
3278
3369
    except KeyError:
3279
3370
        # Unhandled kind
3280
3371
        return None
3281
 
    packed_stat = _pack_stat(stat_value)
 
3372
    packed_stat = pack_stat(stat_value)
3282
3373
    (saved_minikind, saved_link_or_sha1, saved_file_size,
3283
3374
     saved_executable, saved_packed_stat) = entry[1][0]
3284
3375
 
3663
3754
            raise AssertionError("don't know how to compare "
3664
3755
                "source_minikind=%r, target_minikind=%r"
3665
3756
                % (source_minikind, target_minikind))
3666
 
            ## import pdb;pdb.set_trace()
3667
3757
        return None, None
3668
3758
 
3669
3759
    def __iter__(self):
4158
4248
        _bisect_path_left,
4159
4249
        _bisect_path_right,
4160
4250
        cmp_by_dirs,
 
4251
        pack_stat,
4161
4252
        ProcessEntryC as _process_entry,
4162
4253
        update_entry as update_entry,
4163
4254
        )
4169
4260
        _bisect_path_left,
4170
4261
        _bisect_path_right,
4171
4262
        cmp_by_dirs,
 
4263
        pack_stat,
4172
4264
        )
4173
4265
    # FIXME: It would be nice to be able to track moved lines so that the
4174
4266
    # corresponding python code can be moved to the _dirstate_helpers_py