~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: Vincent Ladeuil
  • Date: 2008-01-03 08:49:38 UTC
  • mfrom: (3111.1.31 175524)
  • mto: This revision was merged to the branch mainline in revision 3158.
  • Revision ID: v.ladeuil+lp@free.fr-20080103084938-7kvurk5uvde2ui54
Fix bug #175524, http test servers are 1.1 compliant

Show diffs side-by-side

added added

removed removed

Lines of Context:
29
29
 
30
30
dirstate format = header line, full checksum, row count, parent details,
31
31
 ghost_details, entries;
32
 
header line = "#bazaar dirstate flat format 2", NL;
 
32
header line = "#bazaar dirstate flat format 3", NL;
33
33
full checksum = "crc32: ", ["-"], WHOLE_NUMBER, NL;
34
 
row count = "num_entries: ", digit, NL;
 
34
row count = "num_entries: ", WHOLE_NUMBER, NL;
35
35
parent_details = WHOLE NUMBER, {REVISION_ID}* NL;
36
36
ghost_details = WHOLE NUMBER, {REVISION_ID}*, NL;
37
37
entries = {entry};
118
118
where we need id->path mapping; we also usually read the whole file, so
119
119
I'm going to skip that for the moment, as we have the ability to locate
120
120
via bisect any path in any tree, and if we lookup things by path, we can
121
 
accumulate a id->path mapping as we go, which will tend to match what we
 
121
accumulate an id->path mapping as we go, which will tend to match what we
122
122
looked for.
123
123
 
124
124
I plan to implement this asap, so please speak up now to alter/tweak the
143
143
Locking:
144
144
 Eventually reuse dirstate objects across locks IFF the dirstate file has not
145
145
 been modified, but will require that we flush/ignore cached stat-hit data
146
 
 because we wont want to restat all files on disk just because a lock was
 
146
 because we won't want to restat all files on disk just because a lock was
147
147
 acquired, yet we cannot trust the data after the previous lock was released.
148
148
 
149
149
Memory representation:
162
162
      manageable number. Will scale badly on trees with 10K entries in a 
163
163
      single directory. compare with Inventory.InventoryDirectory which has
164
164
      a dictionary for the children. No bisect capability, can only probe for
165
 
      exact matches, or grab all elements and sorta.
166
 
    - Whats the risk of error here? Once we have the base format being processed
 
165
      exact matches, or grab all elements and sort.
 
166
    - What's the risk of error here? Once we have the base format being processed
167
167
      we should have a net win regardless of optimality. So we are going to 
168
 
      go with what seems reasonably.
 
168
      go with what seems reasonable.
169
169
open questions:
170
170
 
171
 
maybe we should do a test profile of these core structure - 10K simulated searches/lookups/etc?
 
171
Maybe we should do a test profile of the core structure - 10K simulated
 
172
searches/lookups/etc?
172
173
 
173
174
Objects for each row?
174
175
The lifetime of Dirstate objects is current per lock, but see above for
199
200
 
200
201
"""
201
202
 
202
 
 
 
203
import bisect
203
204
import binascii
204
 
import bisect
205
205
import errno
206
206
import os
207
207
from stat import S_IEXEC
212
212
import zlib
213
213
 
214
214
from bzrlib import (
 
215
    cache_utf8,
 
216
    debug,
215
217
    errors,
216
218
    inventory,
217
219
    lock,
220
222
    )
221
223
 
222
224
 
223
 
class _Bisector(object):
224
 
    """This just keeps track of information as we are bisecting."""
225
 
 
226
 
 
227
225
def pack_stat(st, _encode=binascii.b2a_base64, _pack=struct.pack):
228
226
    """Convert stat values into a packed representation."""
229
227
    # jam 20060614 it isn't really worth removing more entries if we
230
228
    # are going to leave it in packed form.
231
229
    # With only st_mtime and st_mode filesize is 5.5M and read time is 275ms
232
 
    # With all entries filesize is 5.9M and read time is mabye 280ms
 
230
    # With all entries, filesize is 5.9M and read time is maybe 280ms
233
231
    # well within the noise margin
234
232
 
235
233
    # base64 encoding always adds a final newline, so strip it off
252
250
 
253
251
    A dirstate is a specialised data structure for managing local working
254
252
    tree state information. Its not yet well defined whether it is platform
255
 
    specific, and if it is how we detect/parameterise that.
 
253
    specific, and if it is how we detect/parameterize that.
256
254
 
257
255
    Dirstates use the usual lock_write, lock_read and unlock mechanisms.
258
256
    Unlike most bzr disk formats, DirStates must be locked for reading, using
308
306
    def __init__(self, path):
309
307
        """Create a  DirState object.
310
308
 
311
 
        Attributes of note:
312
 
 
313
 
        :attr _root_entrie: The root row of the directory/file information,
314
 
            - contains the path to / - '', ''
315
 
            - kind of 'directory',
316
 
            - the file id of the root in utf8
317
 
            - size of 0
318
 
            - a packed state
319
 
            - and no sha information.
320
309
        :param path: The path at which the dirstate file on disk should live.
321
310
        """
322
311
        # _header_state and _dirblock_state represent the current state
340
329
        self._lock_token = None
341
330
        self._lock_state = None
342
331
        self._id_index = None
 
332
        # a map from packed_stat to sha's.
 
333
        self._packed_stat_index = None
343
334
        self._end_of_header = None
344
335
        self._cutoff_time = None
345
336
        self._split_path_cache = {}
346
337
        self._bisect_page_size = DirState.BISECT_PAGE_SIZE
 
338
        if 'hashcache' in debug.debug_flags:
 
339
            self._sha1_file = self._sha1_file_and_mutter
 
340
        else:
 
341
            self._sha1_file = osutils.sha_file_by_name
 
342
        # These two attributes provide a simple cache for lookups into the
 
343
        # dirstate in-memory vectors. By probing respectively for the last
 
344
        # block, and for the next entry, we save nearly 2 bisections per path
 
345
        # during commit.
 
346
        self._last_block_index = None
 
347
        self._last_entry_index = None
347
348
 
348
349
    def __repr__(self):
349
350
        return "%s(%r)" % \
369
370
        # find the location in the block.
370
371
        # check its not there
371
372
        # add it.
372
 
        #------- copied from inventory.make_entry
 
373
        #------- copied from inventory.ensure_normalized_name - keep synced.
373
374
        # --- normalized_filename wants a unicode basename only, so get one.
374
375
        dirname, basename = osutils.split(path)
375
376
        # we dont import normalized_filename directly because we want to be
462
463
        if self._id_index:
463
464
            self._id_index.setdefault(entry_key[2], set()).add(entry_key)
464
465
 
465
 
    def _bisect(self, dir_name_list):
 
466
    def _bisect(self, paths):
466
467
        """Bisect through the disk structure for specific rows.
467
468
 
468
 
        :param dir_name_list: A list of (dir, name) pairs.
469
 
        :return: A dict mapping (dir, name) => entry for found entries. Missing
 
469
        :param paths: A list of paths to find
 
470
        :return: A dict mapping path => entries for found entries. Missing
470
471
                 entries will not be in the map.
 
472
                 The list is not sorted, and entries will be populated
 
473
                 based on when they were read.
471
474
        """
472
475
        self._requires_lock()
473
476
        # We need the file pointer to be right after the initial header block
493
496
        found = {}
494
497
 
495
498
        # Avoid infinite seeking
496
 
        max_count = 30*len(dir_name_list)
 
499
        max_count = 30*len(paths)
497
500
        count = 0
498
501
        # pending is a list of places to look.
499
502
        # each entry is a tuple of low, high, dir_names
501
504
        #   high -> the last byte offset (inclusive)
502
505
        #   dir_names -> The list of (dir, name) pairs that should be found in
503
506
        #                the [low, high] range
504
 
        pending = [(low, high, dir_name_list)]
 
507
        pending = [(low, high, paths)]
505
508
 
506
509
        page_size = self._bisect_page_size
507
510
 
560
563
                # Find what entries we are looking for, which occur before and
561
564
                # after this first record.
562
565
                after = start
563
 
                first_dir_name = (first_fields[1], first_fields[2])
564
 
                first_loc = bisect.bisect_left(cur_files, first_dir_name)
 
566
                if first_fields[1]:
 
567
                    first_path = first_fields[1] + '/' + first_fields[2]
 
568
                else:
 
569
                    first_path = first_fields[2]
 
570
                first_loc = _bisect_path_left(cur_files, first_path)
565
571
 
566
572
                # These exist before the current location
567
573
                pre = cur_files[:first_loc]
584
590
                else:
585
591
                    after = mid + len(block)
586
592
 
587
 
                last_dir_name = (last_fields[1], last_fields[2])
588
 
                last_loc = bisect.bisect_right(post, last_dir_name)
 
593
                if last_fields[1]:
 
594
                    last_path = last_fields[1] + '/' + last_fields[2]
 
595
                else:
 
596
                    last_path = last_fields[2]
 
597
                last_loc = _bisect_path_right(post, last_path)
589
598
 
590
599
                middle_files = post[:last_loc]
591
600
                post = post[last_loc:]
596
605
                    # Either we will find them here, or we can mark them as
597
606
                    # missing.
598
607
 
599
 
                    if middle_files[0] == first_dir_name:
 
608
                    if middle_files[0] == first_path:
600
609
                        # We might need to go before this location
601
 
                        pre.append(first_dir_name)
602
 
                    if middle_files[-1] == last_dir_name:
603
 
                        post.insert(0, last_dir_name)
 
610
                        pre.append(first_path)
 
611
                    if middle_files[-1] == last_path:
 
612
                        post.insert(0, last_path)
604
613
 
605
614
                    # Find out what paths we have
606
 
                    paths = {first_dir_name:[first_fields]}
607
 
                    # last_dir_name might == first_dir_name so we need to be
 
615
                    paths = {first_path:[first_fields]}
 
616
                    # last_path might == first_path so we need to be
608
617
                    # careful if we should append rather than overwrite
609
618
                    if last_entry_num != first_entry_num:
610
 
                        paths.setdefault(last_dir_name, []).append(last_fields)
 
619
                        paths.setdefault(last_path, []).append(last_fields)
611
620
                    for num in xrange(first_entry_num+1, last_entry_num):
612
621
                        # TODO: jam 20070223 We are already splitting here, so
613
622
                        #       shouldn't we just split the whole thing rather
614
623
                        #       than doing the split again in add_one_record?
615
624
                        fields = entries[num].split('\0')
616
 
                        dir_name = (fields[1], fields[2])
617
 
                        paths.setdefault(dir_name, []).append(fields)
 
625
                        if fields[1]:
 
626
                            path = fields[1] + '/' + fields[2]
 
627
                        else:
 
628
                            path = fields[2]
 
629
                        paths.setdefault(path, []).append(fields)
618
630
 
619
 
                    for dir_name in middle_files:
620
 
                        for fields in paths.get(dir_name, []):
 
631
                    for path in middle_files:
 
632
                        for fields in paths.get(path, []):
621
633
                            # offset by 1 because of the opening '\0'
622
634
                            # consider changing fields_to_entry to avoid the
623
635
                            # extra list slice
624
636
                            entry = fields_to_entry(fields[1:])
625
 
                            found.setdefault(dir_name, []).append(entry)
 
637
                            found.setdefault(path, []).append(entry)
626
638
 
627
639
            # Now we have split up everything into pre, middle, and post, and
628
640
            # we have handled everything that fell in 'middle'.
645
657
        _bisect_dirblocks is meant to find the contents of directories, which
646
658
        differs from _bisect, which only finds individual entries.
647
659
 
648
 
        :param dir_list: An sorted list of directory names ['', 'dir', 'foo'].
 
660
        :param dir_list: A sorted list of directory names ['', 'dir', 'foo'].
649
661
        :return: A map from dir => entries_for_dir
650
662
        """
651
663
        # TODO: jam 20070223 A lot of the bisecting logic could be shared
818
830
 
819
831
        return found
820
832
 
821
 
    def _bisect_recursive(self, dir_name_list):
 
833
    def _bisect_recursive(self, paths):
822
834
        """Bisect for entries for all paths and their children.
823
835
 
824
836
        This will use bisect to find all records for the supplied paths. It
837
849
        # Directories that have been read
838
850
        processed_dirs = set()
839
851
        # Get the ball rolling with the first bisect for all entries.
840
 
        newly_found = self._bisect(dir_name_list)
 
852
        newly_found = self._bisect(paths)
841
853
 
842
854
        while newly_found:
843
855
            # Directories that need to be read
867
879
                            if dir_name[0] in pending_dirs:
868
880
                                # This entry will be found in the dir search
869
881
                                continue
870
 
                            # TODO: We need to check if this entry has
871
 
                            #       already been found. Otherwise we might be
872
 
                            #       hitting infinite recursion.
873
882
                            if dir_name not in found_dir_names:
874
 
                                paths_to_search.add(dir_name)
 
883
                                paths_to_search.add(tree_info[1])
875
884
            # Now we have a list of paths to look for directly, and
876
885
            # directory blocks that need to be read.
877
886
            # newly_found is mixing the keys between (dir, name) and path
882
891
            processed_dirs.update(pending_dirs)
883
892
        return found
884
893
 
 
894
    def _discard_merge_parents(self):
 
895
        """Discard any parents trees beyond the first.
 
896
        
 
897
        Note that if this fails the dirstate is corrupted.
 
898
 
 
899
        After this function returns the dirstate contains 2 trees, neither of
 
900
        which are ghosted.
 
901
        """
 
902
        self._read_header_if_needed()
 
903
        parents = self.get_parent_ids()
 
904
        if len(parents) < 1:
 
905
            return
 
906
        # only require all dirblocks if we are doing a full-pass removal.
 
907
        self._read_dirblocks_if_needed()
 
908
        dead_patterns = set([('a', 'r'), ('a', 'a'), ('r', 'r'), ('r', 'a')])
 
909
        def iter_entries_removable():
 
910
            for block in self._dirblocks:
 
911
                deleted_positions = []
 
912
                for pos, entry in enumerate(block[1]):
 
913
                    yield entry
 
914
                    if (entry[1][0][0], entry[1][1][0]) in dead_patterns:
 
915
                        deleted_positions.append(pos)
 
916
                if deleted_positions:
 
917
                    if len(deleted_positions) == len(block[1]):
 
918
                        del block[1][:]
 
919
                    else:
 
920
                        for pos in reversed(deleted_positions):
 
921
                            del block[1][pos]
 
922
        # if the first parent is a ghost:
 
923
        if parents[0] in self.get_ghosts():
 
924
            empty_parent = [DirState.NULL_PARENT_DETAILS]
 
925
            for entry in iter_entries_removable():
 
926
                entry[1][1:] = empty_parent
 
927
        else:
 
928
            for entry in iter_entries_removable():
 
929
                del entry[1][2:]
 
930
 
 
931
        self._ghosts = []
 
932
        self._parents = [parents[0]]
 
933
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
934
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
935
 
885
936
    def _empty_parent_info(self):
886
937
        return [DirState.NULL_PARENT_DETAILS] * (len(self._parents) -
887
938
                                                    len(self._ghosts))
1033
1084
        """
1034
1085
        if key[0:2] == ('', ''):
1035
1086
            return 0, True
 
1087
        try:
 
1088
            if (self._last_block_index is not None and
 
1089
                self._dirblocks[self._last_block_index][0] == key[0]):
 
1090
                return self._last_block_index, True
 
1091
        except IndexError:
 
1092
            pass
1036
1093
        block_index = bisect_dirblock(self._dirblocks, key[0], 1,
1037
1094
                                      cache=self._split_path_cache)
1038
1095
        # _right returns one-past-where-key is so we have to subtract
1043
1100
        # simple and correct:
1044
1101
        present = (block_index < len(self._dirblocks) and
1045
1102
            self._dirblocks[block_index][0] == key[0])
 
1103
        self._last_block_index = block_index
 
1104
        # Reset the entry index cache to the beginning of the block.
 
1105
        self._last_entry_index = -1
1046
1106
        return block_index, present
1047
1107
 
1048
1108
    def _find_entry_index(self, key, block):
1050
1110
 
1051
1111
        :return: The entry index, True if the entry for the key is present.
1052
1112
        """
 
1113
        len_block = len(block)
 
1114
        try:
 
1115
            if self._last_entry_index is not None:
 
1116
                # mini-bisect here.
 
1117
                entry_index = self._last_entry_index + 1
 
1118
                # A hit is when the key is after the last slot, and before or
 
1119
                # equal to the next slot.
 
1120
                if ((entry_index > 0 and block[entry_index - 1][0] < key) and
 
1121
                    key <= block[entry_index][0]):
 
1122
                    self._last_entry_index = entry_index
 
1123
                    present = (block[entry_index][0] == key)
 
1124
                    return entry_index, present
 
1125
        except IndexError:
 
1126
            pass
1053
1127
        entry_index = bisect.bisect_left(block, (key, []))
1054
 
        present = (entry_index < len(block) and
 
1128
        present = (entry_index < len_block and
1055
1129
            block[entry_index][0] == key)
 
1130
        self._last_entry_index = entry_index
1056
1131
        return entry_index, present
1057
1132
 
1058
1133
    @staticmethod
1088
1163
            raise
1089
1164
        return result
1090
1165
 
 
1166
    def update_basis_by_delta(self, delta, new_revid):
 
1167
        """Update the parents of this tree after a commit.
 
1168
 
 
1169
        This gives the tree one parent, with revision id new_revid. The
 
1170
        inventory delta is applied to the current basis tree to generate the
 
1171
        inventory for the parent new_revid, and all other parent trees are
 
1172
        discarded.
 
1173
 
 
1174
        Note that an exception during the operation of this method will leave
 
1175
        the dirstate in a corrupt state where it should not be saved.
 
1176
 
 
1177
        Finally, we expect all changes to be synchronising the basis tree with
 
1178
        the working tree.
 
1179
 
 
1180
        :param new_revid: The new revision id for the trees parent.
 
1181
        :param delta: An inventory delta (see apply_inventory_delta) describing
 
1182
            the changes from the current left most parent revision to new_revid.
 
1183
        """
 
1184
        self._read_dirblocks_if_needed()
 
1185
        self._discard_merge_parents()
 
1186
        if self._ghosts != []:
 
1187
            raise NotImplementedError(self.update_basis_by_delta)
 
1188
        if len(self._parents) == 0:
 
1189
            # setup a blank tree, the most simple way.
 
1190
            empty_parent = DirState.NULL_PARENT_DETAILS
 
1191
            for entry in self._iter_entries():
 
1192
                entry[1].append(empty_parent)
 
1193
            self._parents.append(new_revid)
 
1194
 
 
1195
        self._parents[0] = new_revid
 
1196
 
 
1197
        delta = sorted(delta, reverse=True)
 
1198
        adds = []
 
1199
        changes = []
 
1200
        deletes = []
 
1201
        # The paths this function accepts are unicode and must be encoded as we
 
1202
        # go.
 
1203
        encode = cache_utf8.encode
 
1204
        inv_to_entry = self._inv_entry_to_details
 
1205
        # delta is now (deletes, changes), (adds) in reverse lexographical
 
1206
        # order.
 
1207
        # deletes in reverse lexographic order are safe to process in situ.
 
1208
        # renames are not, as a rename from any path could go to a path
 
1209
        # lexographically lower, so we transform renames into delete, add pairs,
 
1210
        # expanding them recursively as needed.
 
1211
        # At the same time, to reduce interface friction we convert the input
 
1212
        # inventory entries to dirstate.
 
1213
        root_only = ('', '')
 
1214
        for old_path, new_path, file_id, inv_entry in delta:
 
1215
            if old_path is None:
 
1216
                adds.append((None, encode(new_path), file_id,
 
1217
                    inv_to_entry(inv_entry), True))
 
1218
            elif new_path is None:
 
1219
                deletes.append((encode(old_path), None, file_id, None, True))
 
1220
            elif (old_path, new_path) != root_only:
 
1221
                # Renames:
 
1222
                # Because renames must preserve their children we must have
 
1223
                # processed all relocations and removes before hand. The sort
 
1224
                # order ensures we've examined the child paths, but we also
 
1225
                # have to execute the removals, or the split to an add/delete
 
1226
                # pair will result in the deleted item being reinserted, or
 
1227
                # renamed items being reinserted twice - and possibly at the
 
1228
                # wrong place. Splitting into a delete/add pair also simplifies
 
1229
                # the handling of entries with ('f', ...), ('r' ...) because
 
1230
                # the target of the 'r' is old_path here, and we add that to
 
1231
                # deletes, meaning that the add handler does not need to check
 
1232
                # for 'r' items on every pass.
 
1233
                self._update_basis_apply_deletes(deletes)
 
1234
                deletes = []
 
1235
                new_path_utf8 = encode(new_path)
 
1236
                # Split into an add/delete pair recursively.
 
1237
                adds.append((None, new_path_utf8, file_id,
 
1238
                    inv_to_entry(inv_entry), False))
 
1239
                # Expunge deletes that we've seen so that deleted/renamed
 
1240
                # children of a rename directory are handled correctly.
 
1241
                new_deletes = reversed(list(self._iter_child_entries(1,
 
1242
                    encode(old_path))))
 
1243
                # Remove the current contents of the tree at orig_path, and
 
1244
                # reinsert at the correct new path.
 
1245
                for entry in new_deletes:
 
1246
                    if entry[0][0]:
 
1247
                        source_path = entry[0][0] + '/' + entry[0][1]
 
1248
                    else:
 
1249
                        source_path = entry[0][1]
 
1250
                    target_path = new_path_utf8 + source_path[len(old_path):]
 
1251
                    adds.append((None, target_path, entry[0][2], entry[1][1], False))
 
1252
                    deletes.append(
 
1253
                        (source_path, target_path, entry[0][2], None, False))
 
1254
                deletes.append(
 
1255
                    (encode(old_path), new_path, file_id, None, False))
 
1256
            else:
 
1257
                # changes to just the root should not require remove/insertion
 
1258
                # of everything.
 
1259
                changes.append((encode(old_path), encode(new_path), file_id,
 
1260
                    inv_to_entry(inv_entry)))
 
1261
 
 
1262
        # Finish expunging deletes/first half of renames.
 
1263
        self._update_basis_apply_deletes(deletes)
 
1264
        # Reinstate second half of renames and new paths.
 
1265
        self._update_basis_apply_adds(adds)
 
1266
        # Apply in-situ changes.
 
1267
        self._update_basis_apply_changes(changes)
 
1268
 
 
1269
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1270
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
1271
        self._id_index = None
 
1272
        return
 
1273
 
 
1274
    def _update_basis_apply_adds(self, adds):
 
1275
        """Apply a sequence of adds to tree 1 during update_basis_by_delta.
 
1276
 
 
1277
        They may be adds, or renames that have been split into add/delete
 
1278
        pairs.
 
1279
 
 
1280
        :param adds: A sequence of adds. Each add is a tuple:
 
1281
            (None, new_path_utf8, file_id, (entry_details), real_add). real_add
 
1282
            is False when the add is the second half of a remove-and-reinsert
 
1283
            pair created to handle renames and deletes.
 
1284
        """
 
1285
        # Adds are accumulated partly from renames, so can be in any input
 
1286
        # order - sort it.
 
1287
        adds.sort()
 
1288
        # adds is now in lexographic order, which places all parents before
 
1289
        # their children, so we can process it linearly.
 
1290
        absent = 'ar'
 
1291
        for old_path, new_path, file_id, new_details, real_add in adds:
 
1292
            assert old_path is None
 
1293
            # the entry for this file_id must be in tree 0.
 
1294
            entry = self._get_entry(0, file_id, new_path)
 
1295
            if entry[0][2] != file_id:
 
1296
                raise errors.BzrError('dirstate: cannot apply delta, working'
 
1297
                    ' tree does not contain new entry %r %r' %
 
1298
                    (new_path, file_id))
 
1299
            if real_add and entry[1][1][0] not in absent:
 
1300
                raise errors.BzrError('dirstate: inconsistent delta, with '
 
1301
                    'tree 0. %r %r' % (new_path, file_id))
 
1302
            # We don't need to update the target of an 'r' because the handling
 
1303
            # of renames turns all 'r' situations into a delete at the original
 
1304
            # location.
 
1305
            entry[1][1] = new_details
 
1306
 
 
1307
    def _update_basis_apply_changes(self, changes):
 
1308
        """Apply a sequence of changes to tree 1 during update_basis_by_delta.
 
1309
 
 
1310
        :param adds: A sequence of changes. Each change is a tuple:
 
1311
            (path_utf8, path_utf8, file_id, (entry_details))
 
1312
        """
 
1313
        absent = 'ar'
 
1314
        for old_path, new_path, file_id, new_details in changes:
 
1315
            assert old_path == new_path
 
1316
            # the entry for this file_id must be in tree 0.
 
1317
            entry = self._get_entry(0, file_id, new_path)
 
1318
            if entry[0][2] != file_id:
 
1319
                raise errors.BzrError('dirstate: cannot apply delta, working'
 
1320
                    ' tree does not contain new entry %r %r' %
 
1321
                    (new_path, file_id))
 
1322
            if (entry[1][0][0] in absent or
 
1323
                entry[1][1][0] in absent):
 
1324
                raise errors.BzrError('dirstate: inconsistent delta, with '
 
1325
                    'tree 0. %r %r' % (new_path, file_id))
 
1326
            entry[1][1] = new_details
 
1327
 
 
1328
    def _update_basis_apply_deletes(self, deletes):
 
1329
        """Apply a sequence of deletes to tree 1 during update_basis_by_delta.
 
1330
 
 
1331
        They may be deletes, or renames that have been split into add/delete
 
1332
        pairs.
 
1333
 
 
1334
        :param deletes: A sequence of deletes. Each delete is a tuple:
 
1335
            (old_path_utf8, new_path_utf8, file_id, None, real_delete).
 
1336
            real_delete is True when the desired outcome is an actual deletion
 
1337
            rather than the rename handling logic temporarily deleting a path
 
1338
            during the replacement of a parent.
 
1339
        """
 
1340
        null = DirState.NULL_PARENT_DETAILS
 
1341
        for old_path, new_path, file_id, _, real_delete in deletes:
 
1342
            if real_delete:
 
1343
                assert new_path is None
 
1344
            else:
 
1345
                assert new_path is not None
 
1346
            # the entry for this file_id must be in tree 1.
 
1347
            dirname, basename = osutils.split(old_path)
 
1348
            block_index, entry_index, dir_present, file_present = \
 
1349
                self._get_block_entry_index(dirname, basename, 1)
 
1350
            if not file_present:
 
1351
                raise errors.BzrError('dirstate: cannot apply delta, basis'
 
1352
                    ' tree does not contain new entry %r %r' %
 
1353
                    (old_path, file_id))
 
1354
            entry = self._dirblocks[block_index][1][entry_index]
 
1355
            if entry[0][2] != file_id:
 
1356
                raise errors.BzrError('mismatched file_id in tree 1 %r %r' %
 
1357
                    (old_path, file_id))
 
1358
            if real_delete:
 
1359
                if entry[1][0][0] != 'a':
 
1360
                    raise errors.BzrError('dirstate: inconsistent delta, with '
 
1361
                        'tree 0. %r %r' % (old_path, file_id))
 
1362
                del self._dirblocks[block_index][1][entry_index]
 
1363
            else:
 
1364
                if entry[1][0][0] == 'a':
 
1365
                    raise errors.BzrError('dirstate: inconsistent delta, with '
 
1366
                        'tree 0. %r %r' % (old_path, file_id))
 
1367
                elif entry[1][0][0] == 'r':
 
1368
                    # implement the rename
 
1369
                    del self._dirblocks[block_index][1][entry_index]
 
1370
                else:
 
1371
                    # it is being resurrected here, so blank it out temporarily.
 
1372
                    self._dirblocks[block_index][1][entry_index][1][1] = null
 
1373
 
1091
1374
    def update_entry(self, entry, abspath, stat_value,
1092
1375
                     _stat_to_minikind=_stat_to_minikind,
1093
1376
                     _pack_stat=pack_stat):
1124
1407
        # process this entry.
1125
1408
        link_or_sha1 = None
1126
1409
        if minikind == 'f':
1127
 
            link_or_sha1 = self._sha1_file(abspath, entry)
 
1410
            link_or_sha1 = self._sha1_file(abspath)
1128
1411
            executable = self._is_executable(stat_value.st_mode,
1129
1412
                                             saved_executable)
1130
1413
            if self._cutoff_time is None:
1178
1461
        """Return the os.lstat value for this path."""
1179
1462
        return os.lstat(abspath)
1180
1463
 
1181
 
    def _sha1_file(self, abspath, entry):
1182
 
        """Calculate the SHA1 of a file by reading the full text"""
1183
 
        f = file(abspath, 'rb', buffering=65000)
1184
 
        try:
1185
 
            return osutils.sha_file(f)
1186
 
        finally:
1187
 
            f.close()
 
1464
    def _sha1_file_and_mutter(self, abspath):
 
1465
        # when -Dhashcache is turned on, this is monkey-patched in to log
 
1466
        # file reads
 
1467
        trace.mutter("dirstate sha1 " + abspath)
 
1468
        return osutils.sha_file_by_name(abspath)
1188
1469
 
1189
1470
    def _is_executable(self, mode, old_executable):
1190
1471
        """Is this file executable?"""
1327
1608
            be attempted.
1328
1609
        :return: A tuple describing where the path is located, or should be
1329
1610
            inserted. The tuple contains four fields: the block index, the row
1330
 
            index, anda two booleans are True when the directory is present, and
1331
 
            when the entire path is present.  There is no guarantee that either
 
1611
            index, the directory is present (boolean), the entire path is
 
1612
            present (boolean).  There is no guarantee that either
1332
1613
            coordinate is currently reachable unless the found field for it is
1333
1614
            True. For instance, a directory not present in the searched tree
1334
1615
            may be returned with a value one greater than the current highest
1346
1627
            return block_index, 0, False, False
1347
1628
        block = self._dirblocks[block_index][1] # access the entries only
1348
1629
        entry_index, present = self._find_entry_index(key, block)
1349
 
        # linear search through present entries at this path to find the one
 
1630
        # linear search through entries at this path to find the one
1350
1631
        # requested.
1351
1632
        while entry_index < len(block) and block[entry_index][0][1] == basename:
1352
 
            if block[entry_index][1][tree_index][0] not in \
1353
 
                       ('a', 'r'): # absent, relocated
 
1633
            if block[entry_index][1][tree_index][0] not in 'ar':
 
1634
                # neither absent or relocated
1354
1635
                return block_index, entry_index, True, True
1355
1636
            entry_index += 1
1356
1637
        return block_index, entry_index, True, False
1357
1638
 
1358
1639
    def _get_entry(self, tree_index, fileid_utf8=None, path_utf8=None):
1359
 
        """Get the dirstate entry for path in tree tree_index
 
1640
        """Get the dirstate entry for path in tree tree_index.
1360
1641
 
1361
1642
        If either file_id or path is supplied, it is used as the key to lookup.
1362
1643
        If both are supplied, the fastest lookup is used, and an error is
1373
1654
        """
1374
1655
        self._read_dirblocks_if_needed()
1375
1656
        if path_utf8 is not None:
1376
 
            assert path_utf8.__class__ == str, 'path_utf8 is not a str: %s %s' % (type(path_utf8), path_utf8)
 
1657
            assert path_utf8.__class__ == str, ('path_utf8 is not a str: %s %s'
 
1658
                % (type(path_utf8), path_utf8))
1377
1659
            # path lookups are faster
1378
1660
            dirname, basename = osutils.split(path_utf8)
1379
1661
            block_index, entry_index, dir_present, file_present = \
1401
1683
                    continue
1402
1684
                # WARNING: DO not change this code to use _get_block_entry_index
1403
1685
                # as that function is not suitable: it does not use the key
1404
 
                # to lookup, and thus the wront coordinates are returned.
 
1686
                # to lookup, and thus the wrong coordinates are returned.
1405
1687
                block = self._dirblocks[block_index][1]
1406
1688
                entry_index, present = self._find_entry_index(key, block)
1407
1689
                if present:
1466
1748
        kind = inv_entry.kind
1467
1749
        minikind = DirState._kind_to_minikind[kind]
1468
1750
        tree_data = inv_entry.revision
1469
 
        assert len(tree_data) > 0, 'empty revision for the inv_entry.'
 
1751
        assert tree_data, 'empty revision for the inv_entry %s.' % \
 
1752
            inv_entry.file_id
1470
1753
        if kind == 'directory':
1471
1754
            fingerprint = ''
1472
1755
            size = 0
1487
1770
            raise Exception("can't pack %s" % inv_entry)
1488
1771
        return (minikind, fingerprint, size, executable, tree_data)
1489
1772
 
 
1773
    def _iter_child_entries(self, tree_index, path_utf8):
 
1774
        """Iterate over all the entries that are children of path_utf.
 
1775
 
 
1776
        This only returns entries that are present (not in 'a', 'r') in 
 
1777
        tree_index. tree_index data is not refreshed, so if tree 0 is used,
 
1778
        results may differ from that obtained if paths were statted to
 
1779
        determine what ones were directories.
 
1780
 
 
1781
        Asking for the children of a non-directory will return an empty
 
1782
        iterator.
 
1783
        """
 
1784
        pending_dirs = []
 
1785
        next_pending_dirs = [path_utf8]
 
1786
        absent = 'ar'
 
1787
        while next_pending_dirs:
 
1788
            pending_dirs = next_pending_dirs
 
1789
            next_pending_dirs = []
 
1790
            for path in pending_dirs:
 
1791
                block_index, present = self._find_block_index_from_key(
 
1792
                    (path, '', ''))
 
1793
                if block_index == 0:
 
1794
                    block_index = 1
 
1795
                    if len(self._dirblocks) == 1:
 
1796
                        # asked for the children of the root with no other
 
1797
                        # contents.
 
1798
                        return
 
1799
                if not present:
 
1800
                    # children of a non-directory asked for.
 
1801
                    continue
 
1802
                block = self._dirblocks[block_index]
 
1803
                for entry in block[1]:
 
1804
                    kind = entry[1][tree_index][0]
 
1805
                    if kind not in absent:
 
1806
                        yield entry
 
1807
                    if kind == 'd':
 
1808
                        if entry[0][0]:
 
1809
                            path = entry[0][0] + '/' + entry[0][1]
 
1810
                        else:
 
1811
                            path = entry[0][1]
 
1812
                        next_pending_dirs.append(path)
 
1813
    
1490
1814
    def _iter_entries(self):
1491
1815
        """Iterate over all the entries in the dirstate.
1492
1816
 
1508
1832
        return self._id_index
1509
1833
 
1510
1834
    def _get_output_lines(self, lines):
1511
 
        """format lines for final output.
 
1835
        """Format lines for final output.
1512
1836
 
1513
 
        :param lines: A sequece of lines containing the parents list and the
 
1837
        :param lines: A sequence of lines containing the parents list and the
1514
1838
            path lines.
1515
1839
        """
1516
1840
        output_lines = [DirState.HEADER_FORMAT_3]
1524
1848
        return output_lines
1525
1849
 
1526
1850
    def _make_deleted_row(self, fileid_utf8, parents):
1527
 
        """Return a deleted for for fileid_utf8."""
 
1851
        """Return a deleted row for fileid_utf8."""
1528
1852
        return ('/', 'RECYCLED.BIN', 'file', fileid_utf8, 0, DirState.NULLSTAT,
1529
1853
            ''), parents
1530
1854
 
1550
1874
        """
1551
1875
        self._read_header_if_needed()
1552
1876
        if self._dirblock_state == DirState.NOT_IN_MEMORY:
1553
 
            # move the _state_file pointer to after the header (in case bisect
1554
 
            # has been called in the mean time)
1555
 
            self._state_file.seek(self._end_of_header)
1556
 
            text = self._state_file.read()
1557
 
            # TODO: check the crc checksums. crc_measured = zlib.crc32(text)
1558
 
 
1559
 
            fields = text.split('\0')
1560
 
            # Remove the last blank entry
1561
 
            trailing = fields.pop()
1562
 
            assert trailing == ''
1563
 
            # consider turning fields into a tuple.
1564
 
 
1565
 
            # skip the first field which is the trailing null from the header.
1566
 
            cur = 1
1567
 
            # Each line now has an extra '\n' field which is not used
1568
 
            # so we just skip over it
1569
 
            # entry size:
1570
 
            #  3 fields for the key
1571
 
            #  + number of fields per tree_data (5) * tree count
1572
 
            #  + newline
1573
 
            num_present_parents = self._num_present_parents()
1574
 
            tree_count = 1 + num_present_parents
1575
 
            entry_size = self._fields_per_entry()
1576
 
            expected_field_count = entry_size * self._num_entries
1577
 
            field_count = len(fields)
1578
 
            # this checks our adjustment, and also catches file too short.
1579
 
            assert field_count - cur == expected_field_count, \
1580
 
                'field count incorrect %s != %s, entry_size=%s, '\
1581
 
                'num_entries=%s fields=%r' % (
1582
 
                    field_count - cur, expected_field_count, entry_size,
1583
 
                    self._num_entries, fields)
1584
 
 
1585
 
            if num_present_parents == 1:
1586
 
                # Bind external functions to local names
1587
 
                _int = int
1588
 
                # We access all fields in order, so we can just iterate over
1589
 
                # them. Grab an straight iterator over the fields. (We use an
1590
 
                # iterator because we don't want to do a lot of additions, nor
1591
 
                # do we want to do a lot of slicing)
1592
 
                next = iter(fields).next
1593
 
                # Move the iterator to the current position
1594
 
                for x in xrange(cur):
1595
 
                    next()
1596
 
                # The two blocks here are deliberate: the root block and the
1597
 
                # contents-of-root block.
1598
 
                self._dirblocks = [('', []), ('', [])]
1599
 
                current_block = self._dirblocks[0][1]
1600
 
                current_dirname = ''
1601
 
                append_entry = current_block.append
1602
 
                for count in xrange(self._num_entries):
1603
 
                    dirname = next()
1604
 
                    name = next()
1605
 
                    file_id = next()
1606
 
                    if dirname != current_dirname:
1607
 
                        # new block - different dirname
1608
 
                        current_block = []
1609
 
                        current_dirname = dirname
1610
 
                        self._dirblocks.append((current_dirname, current_block))
1611
 
                        append_entry = current_block.append
1612
 
                    # we know current_dirname == dirname, so re-use it to avoid
1613
 
                    # creating new strings
1614
 
                    entry = ((current_dirname, name, file_id),
1615
 
                             [(# Current Tree
1616
 
                                 next(),                # minikind
1617
 
                                 next(),                # fingerprint
1618
 
                                 _int(next()),          # size
1619
 
                                 next() == 'y',         # executable
1620
 
                                 next(),                # packed_stat or revision_id
1621
 
                             ),
1622
 
                             ( # Parent 1
1623
 
                                 next(),                # minikind
1624
 
                                 next(),                # fingerprint
1625
 
                                 _int(next()),          # size
1626
 
                                 next() == 'y',         # executable
1627
 
                                 next(),                # packed_stat or revision_id
1628
 
                             ),
1629
 
                             ])
1630
 
                    trailing = next()
1631
 
                    assert trailing == '\n'
1632
 
                    # append the entry to the current block
1633
 
                    append_entry(entry)
1634
 
                self._split_root_dirblock_into_contents()
1635
 
            else:
1636
 
                fields_to_entry = self._get_fields_to_entry()
1637
 
                entries = [fields_to_entry(fields[pos:pos+entry_size])
1638
 
                           for pos in xrange(cur, field_count, entry_size)]
1639
 
                self._entries_to_current_state(entries)
1640
 
            # To convert from format 2  => format 3
1641
 
            # self._dirblocks = sorted(self._dirblocks,
1642
 
            #                          key=lambda blk:blk[0].split('/'))
1643
 
            # To convert from format 3 => format 2
1644
 
            # self._dirblocks = sorted(self._dirblocks)
1645
 
            self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
1877
            _read_dirblocks(self)
1646
1878
 
1647
1879
    def _read_header(self):
1648
1880
        """This reads in the metadata header, and the parent ids.
1676
1908
            self._read_header()
1677
1909
 
1678
1910
    def _read_prelude(self):
1679
 
        """Read in the prelude header of the dirstate file
 
1911
        """Read in the prelude header of the dirstate file.
1680
1912
 
1681
1913
        This only reads in the stuff that is not connected to the crc
1682
1914
        checksum. The position will be correct to read in the rest of
1694
1926
        assert num_entries_line.startswith('num_entries: '), 'missing num_entries line'
1695
1927
        self._num_entries = int(num_entries_line[len('num_entries: '):-1])
1696
1928
 
 
1929
    def sha1_from_stat(self, path, stat_result, _pack_stat=pack_stat):
 
1930
        """Find a sha1 given a stat lookup."""
 
1931
        return self._get_packed_stat_index().get(_pack_stat(stat_result), None)
 
1932
 
 
1933
    def _get_packed_stat_index(self):
 
1934
        """Get a packed_stat index of self._dirblocks."""
 
1935
        if self._packed_stat_index is None:
 
1936
            index = {}
 
1937
            for key, tree_details in self._iter_entries():
 
1938
                if tree_details[0][0] == 'f':
 
1939
                    index[tree_details[0][4]] = tree_details[0][1]
 
1940
            self._packed_stat_index = index
 
1941
        return self._packed_stat_index
 
1942
 
1697
1943
    def save(self):
1698
1944
        """Save any pending changes created during this session.
1699
1945
 
1700
1946
        We reuse the existing file, because that prevents race conditions with
1701
1947
        file creation, and use oslocks on it to prevent concurrent modification
1702
 
        and reads - because dirstates incremental data aggretation is not
 
1948
        and reads - because dirstate's incremental data aggregation is not
1703
1949
        compatible with reading a modified file, and replacing a file in use by
1704
 
        another process is impossible on windows.
 
1950
        another process is impossible on Windows.
1705
1951
 
1706
1952
        A dirstate in read only mode should be smart enough though to validate
1707
1953
        that the file has not changed, and otherwise discard its cache and
1755
2001
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1756
2002
        self._parents = list(parent_ids)
1757
2003
        self._id_index = None
 
2004
        self._packed_stat_index = None
1758
2005
 
1759
2006
    def set_path_id(self, path, new_id):
1760
2007
        """Change the id of path to new_id in the current working tree.
1768
2015
            "path_id %r is not a plain string" % (new_id,)
1769
2016
        self._read_dirblocks_if_needed()
1770
2017
        if len(path):
1771
 
            # logic not written
 
2018
            # TODO: logic not written
1772
2019
            raise NotImplementedError(self.set_path_id)
1773
2020
        # TODO: check new id is unique
1774
2021
        entry = self._get_entry(0, path_utf8=path)
1836
2083
        # one: the current tree
1837
2084
        for entry in self._iter_entries():
1838
2085
            # skip entries not in the current tree
1839
 
            if entry[1][0][0] in ('a', 'r'): # absent, relocated
 
2086
            if entry[1][0][0] in 'ar': # absent, relocated
1840
2087
                continue
1841
2088
            by_path[entry[0]] = [entry[1][0]] + \
1842
2089
                [DirState.NULL_PARENT_DETAILS] * parent_count
1876
2123
                        # this file id is at a different path in one of the
1877
2124
                        # other trees, so put absent pointers there
1878
2125
                        # This is the vertical axis in the matrix, all pointing
1879
 
                        # tot he real path.
 
2126
                        # to the real path.
1880
2127
                        by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
1881
2128
                # by path consistency: Insert into an existing path record (trivial), or 
1882
2129
                # add a new one with relocation pointers for the other tree indexes.
1930
2177
        try to keep everything in sorted blocks all the time, but sometimes
1931
2178
        it's easier to sort after the fact.
1932
2179
        """
1933
 
        # TODO: Might be faster to do a schwartzian transform?
1934
2180
        def _key(entry):
1935
2181
            # sort by: directory parts, file name, file id
1936
2182
            return entry[0][0].split('/'), entry[0][1], entry[0][2]
1944
2190
 
1945
2191
        :param new_inv: The inventory object to set current state from.
1946
2192
        """
 
2193
        if 'evil' in debug.debug_flags:
 
2194
            trace.mutter_callsite(1,
 
2195
                "set_state_from_inventory called; please mutate the tree instead")
1947
2196
        self._read_dirblocks_if_needed()
1948
2197
        # sketch:
1949
 
        # incremental algorithm:
1950
 
        # two iterators: current data and new data, both in dirblock order. 
 
2198
        # Two iterators: current data and new data, both in dirblock order. 
 
2199
        # We zip them together, which tells about entries that are new in the
 
2200
        # inventory, or removed in the inventory, or present in both and
 
2201
        # possibly changed.  
 
2202
        #
 
2203
        # You might think we could just synthesize a new dirstate directly
 
2204
        # since we're processing it in the right order.  However, we need to
 
2205
        # also consider there may be any number of parent trees and relocation
 
2206
        # pointers, and we don't want to duplicate that here.
1951
2207
        new_iterator = new_inv.iter_entries_by_dir()
1952
2208
        # we will be modifying the dirstate, so we need a stable iterator. In
1953
2209
        # future we might write one, for now we just clone the state into a
1954
 
        # list - which is a shallow copy, so each 
 
2210
        # list - which is a shallow copy.
1955
2211
        old_iterator = iter(list(self._iter_entries()))
1956
2212
        # both must have roots so this is safe:
1957
2213
        current_new = new_iterator.next()
1963
2219
                return None
1964
2220
        while current_new or current_old:
1965
2221
            # skip entries in old that are not really there
1966
 
            if current_old and current_old[1][0][0] in ('r', 'a'):
 
2222
            if current_old and current_old[1][0][0] in 'ar':
1967
2223
                # relocated or absent
1968
2224
                current_old = advance(old_iterator)
1969
2225
                continue
1976
2232
                current_new_minikind = \
1977
2233
                    DirState._kind_to_minikind[current_new[1].kind]
1978
2234
                if current_new_minikind == 't':
1979
 
                    fingerprint = current_new[1].reference_revision
 
2235
                    fingerprint = current_new[1].reference_revision or ''
1980
2236
                else:
 
2237
                    # We normally only insert or remove records, or update
 
2238
                    # them when it has significantly changed.  Then we want to
 
2239
                    # erase its fingerprint.  Unaffected records should
 
2240
                    # normally not be updated at all.
1981
2241
                    fingerprint = ''
1982
2242
            else:
1983
2243
                # for safety disable variables
1984
 
                new_path_utf8 = new_dirname = new_basename = new_id = new_entry_key = None
 
2244
                new_path_utf8 = new_dirname = new_basename = new_id = \
 
2245
                    new_entry_key = None
1985
2246
            # 5 cases, we dont have a value that is strictly greater than everything, so
1986
2247
            # we make both end conditions explicit
1987
2248
            if not current_old:
1996
2257
                current_old = advance(old_iterator)
1997
2258
            elif new_entry_key == current_old[0]:
1998
2259
                # same -  common case
 
2260
                # We're looking at the same path and id in both the dirstate
 
2261
                # and inventory, so just need to update the fields in the
 
2262
                # dirstate from the one in the inventory.
1999
2263
                # TODO: update the record if anything significant has changed.
2000
2264
                # the minimal required trigger is if the execute bit or cached
2001
2265
                # kind has changed.
2007
2271
                # both sides are dealt with, move on
2008
2272
                current_old = advance(old_iterator)
2009
2273
                current_new = advance(new_iterator)
2010
 
            elif (new_entry_key[0].split('/') < current_old[0][0].split('/')
2011
 
                  and new_entry_key[1:] < current_old[0][1:]):
 
2274
            elif (cmp_by_dirs(new_dirname, current_old[0][0]) < 0
 
2275
                  or (new_dirname == current_old[0][0]
 
2276
                      and new_entry_key[1:] < current_old[0][1:])):
2012
2277
                # new comes before:
2013
2278
                # add a entry for this and advance new
2014
2279
                self.update_minimal(new_entry_key, current_new_minikind,
2016
2281
                    path_utf8=new_path_utf8, fingerprint=fingerprint)
2017
2282
                current_new = advance(new_iterator)
2018
2283
            else:
2019
 
                # old comes before:
 
2284
                # we've advanced past the place where the old key would be,
 
2285
                # without seeing it in the new list.  so it must be gone.
2020
2286
                self._make_absent(current_old)
2021
2287
                current_old = advance(old_iterator)
2022
2288
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2023
2289
        self._id_index = None
 
2290
        self._packed_stat_index = None
2024
2291
 
2025
2292
    def _make_absent(self, current_old):
2026
2293
        """Mark current_old - an entry - as absent for tree 0.
2027
2294
 
2028
 
        :return: True if this was the last details entry for they entry key:
 
2295
        :return: True if this was the last details entry for the entry key:
2029
2296
            that is, if the underlying block has had the entry removed, thus
2030
2297
            shrinking in length.
2031
2298
        """
2032
2299
        # build up paths that this id will be left at after the change is made,
2033
2300
        # so we can update their cross references in tree 0
2034
2301
        all_remaining_keys = set()
2035
 
        # Dont check the working tree, because its going.
 
2302
        # Dont check the working tree, because it's going.
2036
2303
        for details in current_old[1][1:]:
2037
 
            if details[0] not in ('a', 'r'): # absent, relocated
 
2304
            if details[0] not in 'ar': # absent, relocated
2038
2305
                all_remaining_keys.add(current_old[0])
2039
2306
            elif details[0] == 'r': # relocated
2040
2307
                # record the key for the real path.
2053
2320
            if self._id_index is not None:
2054
2321
                self._id_index[current_old[0][2]].remove(current_old[0])
2055
2322
        # update all remaining keys for this id to record it as absent. The
2056
 
        # existing details may either be the record we are making as deleted
 
2323
        # existing details may either be the record we are marking as deleted
2057
2324
        # (if there were other trees with the id present at this path), or may
2058
2325
        # be relocations.
2059
2326
        for update_key in all_remaining_keys:
2082
2349
        :param minikind: The type for the entry ('f' == 'file', 'd' ==
2083
2350
                'directory'), etc.
2084
2351
        :param executable: Should the executable bit be set?
2085
 
        :param fingerprint: Simple fingerprint for new entry.
2086
 
        :param packed_stat: packed stat value for new entry.
 
2352
        :param fingerprint: Simple fingerprint for new entry: sha1 for files, 
 
2353
            referenced revision id for subtrees, etc.
 
2354
        :param packed_stat: Packed stat value for new entry.
2087
2355
        :param size: Size information for new entry
2088
2356
        :param path_utf8: key[0] + '/' + key[1], just passed in to avoid doing
2089
2357
                extra computation.
 
2358
 
 
2359
        If packed_stat and fingerprint are not given, they're invalidated in
 
2360
        the entry.
2090
2361
        """
2091
2362
        block = self._find_block(key)[1]
2092
2363
        if packed_stat is None:
2093
2364
            packed_stat = DirState.NULLSTAT
 
2365
        # XXX: Some callers pass '' as the packed_stat, and it seems to be
 
2366
        # sometimes present in the dirstate - this seems oddly inconsistent.
 
2367
        # mbp 20071008
2094
2368
        entry_index, present = self._find_entry_index(key, block)
2095
2369
        new_details = (minikind, fingerprint, size, executable, packed_stat)
2096
2370
        id_index = self._get_id_index()
2133
2407
                        self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
2134
2408
                    assert present, 'could not find entry for %s' % (other_key,)
2135
2409
                    update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
2136
 
                    if update_details[0] in ('r', 'a'): # relocated, absent
 
2410
                    if update_details[0] in 'ar': # relocated, absent
2137
2411
                        # its a pointer or absent in lookup_index's tree, use
2138
2412
                        # it as is.
2139
2413
                        new_entry[1].append(update_details)
2234
2508
                    "dirblock for %r is not sorted:\n%s" % \
2235
2509
                    (dirblock[0], pformat(dirblock)))
2236
2510
 
2237
 
 
2238
2511
        def check_valid_parent():
2239
2512
            """Check that the current entry has a valid parent.
2240
2513
 
2275
2548
                "wrong number of entry details for row\n%s" \
2276
2549
                ",\nexpected %d" % \
2277
2550
                (pformat(entry), tree_count))
 
2551
            absent_positions = 0
2278
2552
            for tree_index, tree_state in enumerate(entry[1]):
2279
2553
                this_tree_map = id_path_maps[tree_index]
2280
2554
                minikind = tree_state[0]
 
2555
                if minikind in 'ar':
 
2556
                    absent_positions += 1
2281
2557
                # have we seen this id before in this column?
2282
2558
                if file_id in this_tree_map:
2283
 
                    previous_path = this_tree_map[file_id]
 
2559
                    previous_path, previous_loc = this_tree_map[file_id]
2284
2560
                    # any later mention of this file must be consistent with
2285
2561
                    # what was said before
2286
2562
                    if minikind == 'a':
2300
2576
                        # pointed to by a relocation, which must point here
2301
2577
                        if previous_path != this_path:
2302
2578
                            raise AssertionError(
2303
 
                            "entry %r inconsistent with previous path %r" % \
2304
 
                            (entry, previous_path))
 
2579
                                "entry %r inconsistent with previous path %r "
 
2580
                                "seen at %r" %
 
2581
                                (entry, previous_path, previous_loc))
2305
2582
                        check_valid_parent()
2306
2583
                else:
2307
2584
                    if minikind == 'a':
2308
2585
                        # absent; should not occur anywhere else
2309
 
                        this_tree_map[file_id] = None
 
2586
                        this_tree_map[file_id] = None, this_path
2310
2587
                    elif minikind == 'r':
2311
2588
                        # relocation, must occur at expected location 
2312
 
                        this_tree_map[file_id] = tree_state[1]
 
2589
                        this_tree_map[file_id] = tree_state[1], this_path
2313
2590
                    else:
2314
 
                        this_tree_map[file_id] = this_path
 
2591
                        this_tree_map[file_id] = this_path, this_path
2315
2592
                        check_valid_parent()
 
2593
            if absent_positions == tree_count:
 
2594
                raise AssertionError(
 
2595
                    "entry %r has no data for any tree." % (entry,))
2316
2596
 
2317
2597
    def _wipe_state(self):
2318
2598
        """Forget all state information about the dirstate."""
2322
2602
        self._ghosts = []
2323
2603
        self._dirblocks = []
2324
2604
        self._id_index = None
 
2605
        self._packed_stat_index = None
2325
2606
        self._end_of_header = None
2326
2607
        self._cutoff_time = None
2327
2608
        self._split_path_cache = {}
2328
2609
 
2329
2610
    def lock_read(self):
2330
 
        """Acquire a read lock on the dirstate"""
 
2611
        """Acquire a read lock on the dirstate."""
2331
2612
        if self._lock_token is not None:
2332
2613
            raise errors.LockContention(self._lock_token)
2333
2614
        # TODO: jam 20070301 Rather than wiping completely, if the blocks are
2340
2621
        self._wipe_state()
2341
2622
 
2342
2623
    def lock_write(self):
2343
 
        """Acquire a write lock on the dirstate"""
 
2624
        """Acquire a write lock on the dirstate."""
2344
2625
        if self._lock_token is not None:
2345
2626
            raise errors.LockContention(self._lock_token)
2346
2627
        # TODO: jam 20070301 Rather than wiping completely, if the blocks are
2353
2634
        self._wipe_state()
2354
2635
 
2355
2636
    def unlock(self):
2356
 
        """Drop any locks held on the dirstate"""
 
2637
        """Drop any locks held on the dirstate."""
2357
2638
        if self._lock_token is None:
2358
2639
            raise errors.LockNotHeld(self)
2359
2640
        # TODO: jam 20070301 Rather than wiping completely, if the blocks are
2367
2648
        self._split_path_cache = {}
2368
2649
 
2369
2650
    def _requires_lock(self):
2370
 
        """Checks that a lock is currently held by someone on the dirstate"""
 
2651
        """Check that a lock is currently held by someone on the dirstate."""
2371
2652
        if not self._lock_token:
2372
2653
            raise errors.ObjectNotLocked(self)
2373
2654
 
2374
2655
 
2375
 
def bisect_dirblock(dirblocks, dirname, lo=0, hi=None, cache={}):
2376
 
    """Return the index where to insert dirname into the dirblocks.
2377
 
 
2378
 
    The return value idx is such that all directories blocks in dirblock[:idx]
2379
 
    have names < dirname, and all blocks in dirblock[idx:] have names >=
2380
 
    dirname.
2381
 
 
2382
 
    Optional args lo (default 0) and hi (default len(dirblocks)) bound the
2383
 
    slice of a to be searched.
2384
 
    """
2385
 
    if hi is None:
2386
 
        hi = len(dirblocks)
2387
 
    try:
2388
 
        dirname_split = cache[dirname]
2389
 
    except KeyError:
2390
 
        dirname_split = dirname.split('/')
2391
 
        cache[dirname] = dirname_split
2392
 
    while lo < hi:
2393
 
        mid = (lo+hi)//2
2394
 
        # Grab the dirname for the current dirblock
2395
 
        cur = dirblocks[mid][0]
2396
 
        try:
2397
 
            cur_split = cache[cur]
2398
 
        except KeyError:
2399
 
            cur_split = cur.split('/')
2400
 
            cache[cur] = cur_split
2401
 
        if cur_split < dirname_split: lo = mid+1
2402
 
        else: hi = mid
2403
 
    return lo
 
2656
# Try to load the compiled form if possible
 
2657
try:
 
2658
    from bzrlib._dirstate_helpers_c import (
 
2659
        _read_dirblocks_c as _read_dirblocks,
 
2660
        bisect_dirblock_c as bisect_dirblock,
 
2661
        _bisect_path_left_c as _bisect_path_left,
 
2662
        _bisect_path_right_c as _bisect_path_right,
 
2663
        cmp_by_dirs_c as cmp_by_dirs,
 
2664
        )
 
2665
except ImportError:
 
2666
    from bzrlib._dirstate_helpers_py import (
 
2667
        _read_dirblocks_py as _read_dirblocks,
 
2668
        bisect_dirblock_py as bisect_dirblock,
 
2669
        _bisect_path_left_py as _bisect_path_left,
 
2670
        _bisect_path_right_py as _bisect_path_right,
 
2671
        cmp_by_dirs_py as cmp_by_dirs,
 
2672
        )