/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2011-05-18 01:55:17 UTC
  • mfrom: (5881.1.1 trunk)
  • Revision ID: pqm@pqm.ubuntu.com-20110518015517-en7p3zst2mdprrqx
(vila) Merge 2.3 into trunk (Vincent Ladeuil)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 Canonical Ltd
 
1
# Copyright (C) 2006-2011 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
220
220
    inventory,
221
221
    lock,
222
222
    osutils,
 
223
    static_tuple,
223
224
    trace,
224
225
    )
225
226
 
264
265
        # return '%X.%X' % (int(st.st_mtime), st.st_mode)
265
266
 
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
 
267
279
class SHA1Provider(object):
268
280
    """An interface for getting sha1s of a file."""
269
281
 
354
366
    NOT_IN_MEMORY = 0
355
367
    IN_MEMORY_UNMODIFIED = 1
356
368
    IN_MEMORY_MODIFIED = 2
 
369
    IN_MEMORY_HASH_MODIFIED = 3 # Only hash-cache updates
357
370
 
358
371
    # A pack_stat (the x's) that is just noise and will never match the output
359
372
    # of base64 encode.
363
376
    HEADER_FORMAT_2 = '#bazaar dirstate flat format 2\n'
364
377
    HEADER_FORMAT_3 = '#bazaar dirstate flat format 3\n'
365
378
 
366
 
    def __init__(self, path, sha1_provider):
 
379
    def __init__(self, path, sha1_provider, worth_saving_limit=0):
367
380
        """Create a  DirState object.
368
381
 
369
382
        :param path: The path at which the dirstate file on disk should live.
370
383
        :param sha1_provider: an object meeting the SHA1Provider interface.
 
384
        :param worth_saving_limit: when the exact number of hash changed
 
385
            entries is known, only bother saving the dirstate if more than
 
386
            this count of entries have changed.
 
387
            -1 means never save hash changes, 0 means always save hash changes.
371
388
        """
372
389
        # _header_state and _dirblock_state represent the current state
373
390
        # of the dirstate metadata and the per-row data respectiely.
410
427
        # during commit.
411
428
        self._last_block_index = None
412
429
        self._last_entry_index = None
 
430
        # The set of known hash changes
 
431
        self._known_hash_changes = set()
 
432
        # How many hash changed entries can we have without saving
 
433
        self._worth_saving_limit = worth_saving_limit
413
434
 
414
435
    def __repr__(self):
415
436
        return "%s(%r)" % \
416
437
            (self.__class__.__name__, self._filename)
417
438
 
 
439
    def _mark_modified(self, hash_changed_entries=None, header_modified=False):
 
440
        """Mark this dirstate as modified.
 
441
 
 
442
        :param hash_changed_entries: if non-None, mark just these entries as
 
443
            having their hash modified.
 
444
        :param header_modified: mark the header modified as well, not just the
 
445
            dirblocks.
 
446
        """
 
447
        #trace.mutter_callsite(3, "modified hash entries: %s", hash_changed_entries)
 
448
        if hash_changed_entries:
 
449
            self._known_hash_changes.update([e[0] for e in hash_changed_entries])
 
450
            if self._dirblock_state in (DirState.NOT_IN_MEMORY,
 
451
                                        DirState.IN_MEMORY_UNMODIFIED):
 
452
                # If the dirstate is already marked a IN_MEMORY_MODIFIED, then
 
453
                # that takes precedence.
 
454
                self._dirblock_state = DirState.IN_MEMORY_HASH_MODIFIED
 
455
        else:
 
456
            # TODO: Since we now have a IN_MEMORY_HASH_MODIFIED state, we
 
457
            #       should fail noisily if someone tries to set
 
458
            #       IN_MEMORY_MODIFIED but we don't have a write-lock!
 
459
            # We don't know exactly what changed so disable smart saving
 
460
            self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
461
        if header_modified:
 
462
            self._header_state = DirState.IN_MEMORY_MODIFIED
 
463
 
 
464
    def _mark_unmodified(self):
 
465
        """Mark this dirstate as unmodified."""
 
466
        self._header_state = DirState.IN_MEMORY_UNMODIFIED
 
467
        self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
468
        self._known_hash_changes = set()
 
469
 
418
470
    def add(self, path, file_id, kind, stat, fingerprint):
419
471
        """Add a path to be tracked.
420
472
 
546
598
        if kind == 'directory':
547
599
           # insert a new dirblock
548
600
           self._ensure_block(block_index, entry_index, utf8path)
549
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
601
        self._mark_modified()
550
602
        if self._id_index:
551
 
            self._id_index.setdefault(entry_key[2], set()).add(entry_key)
 
603
            self._add_to_id_index(self._id_index, entry_key)
552
604
 
553
605
    def _bisect(self, paths):
554
606
        """Bisect through the disk structure for specific rows.
1018
1070
 
1019
1071
        self._ghosts = []
1020
1072
        self._parents = [parents[0]]
1021
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1022
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
1073
        self._mark_modified(header_modified=True)
1023
1074
 
1024
1075
    def _empty_parent_info(self):
1025
1076
        return [DirState.NULL_PARENT_DETAILS] * (len(self._parents) -
1275
1326
            raise
1276
1327
        return result
1277
1328
 
 
1329
    def _check_delta_is_valid(self, delta):
 
1330
        return list(inventory._check_delta_unique_ids(
 
1331
                    inventory._check_delta_unique_old_paths(
 
1332
                    inventory._check_delta_unique_new_paths(
 
1333
                    inventory._check_delta_ids_match_entry(
 
1334
                    inventory._check_delta_ids_are_valid(
 
1335
                    inventory._check_delta_new_path_entry_both_or_None(delta)))))))
 
1336
 
1278
1337
    def update_by_delta(self, delta):
1279
1338
        """Apply an inventory delta to the dirstate for tree 0
1280
1339
 
1298
1357
        new_ids = set()
1299
1358
        # This loop transforms the delta to single atomic operations that can
1300
1359
        # be executed and validated.
1301
 
        for old_path, new_path, file_id, inv_entry in sorted(
1302
 
            inventory._check_delta_unique_old_paths(
1303
 
            inventory._check_delta_unique_new_paths(
1304
 
            inventory._check_delta_ids_match_entry(
1305
 
            inventory._check_delta_ids_are_valid(
1306
 
            inventory._check_delta_new_path_entry_both_or_None(delta))))),
1307
 
            reverse=True):
 
1360
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
 
1361
        for old_path, new_path, file_id, inv_entry in delta:
1308
1362
            if (file_id in insertions) or (file_id in removals):
1309
1363
                raise errors.InconsistentDelta(old_path or new_path, file_id,
1310
1364
                    "repeated file_id")
1424
1478
        Note that an exception during the operation of this method will leave
1425
1479
        the dirstate in a corrupt state where it should not be saved.
1426
1480
 
1427
 
        Finally, we expect all changes to be synchronising the basis tree with
1428
 
        the working tree.
1429
 
 
1430
1481
        :param new_revid: The new revision id for the trees parent.
1431
1482
        :param delta: An inventory delta (see apply_inventory_delta) describing
1432
1483
            the changes from the current left most parent revision to new_revid.
1444
1495
 
1445
1496
        self._parents[0] = new_revid
1446
1497
 
1447
 
        delta = sorted(delta, reverse=True)
 
1498
        delta = sorted(self._check_delta_is_valid(delta), reverse=True)
1448
1499
        adds = []
1449
1500
        changes = []
1450
1501
        deletes = []
1555
1606
            # the active tree.
1556
1607
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1557
1608
 
1558
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1559
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
 
1609
        self._mark_modified(header_modified=True)
1560
1610
        self._id_index = None
1561
1611
        return
1562
1612
 
1566
1616
            return
1567
1617
        id_index = self._get_id_index()
1568
1618
        for file_id in new_ids:
1569
 
            for key in id_index.get(file_id, []):
 
1619
            for key in id_index.get(file_id, ()):
1570
1620
                block_i, entry_i, d_present, f_present = \
1571
1621
                    self._get_block_entry_index(key[0], key[1], tree_index)
1572
1622
                if not f_present:
1603
1653
        for old_path, new_path, file_id, new_details, real_add in adds:
1604
1654
            # the entry for this file_id must be in tree 0.
1605
1655
            entry = self._get_entry(0, file_id, new_path)
1606
 
            if entry[0] is None or entry[0][2] != file_id:
 
1656
            if entry[0] is None:
 
1657
                # new_path is not versioned in the active WT state,
 
1658
                # but we are adding it to the basis tree state, we
 
1659
                # need to create a new entry record for it.
 
1660
                dirname, basename = osutils.split(new_path)
 
1661
                entry_key = (dirname, basename, file_id)
 
1662
                _, block = self._find_block(entry_key, add_if_missing=True)
 
1663
                index, _ = self._find_entry_index(entry_key, block)
 
1664
                entry = (entry_key, [DirState.NULL_PARENT_DETAILS]*2)
 
1665
                block.insert(index, entry)
 
1666
            elif entry[0][2] != file_id:
1607
1667
                self._changes_aborted = True
1608
1668
                raise errors.InconsistentDelta(new_path, file_id,
1609
1669
                    'working tree does not contain new entry')
1669
1729
                raise errors.InconsistentDelta(old_path, file_id,
1670
1730
                    'mismatched file_id in tree 1')
1671
1731
            if real_delete:
1672
 
                if entry[1][0][0] != 'a':
1673
 
                    self._changes_aborted = True
1674
 
                    raise errors.InconsistentDelta(old_path, file_id,
1675
 
                            'This was marked as a real delete, but the WT state'
1676
 
                            ' claims that it still exists and is versioned.')
1677
 
                del self._dirblocks[block_index][1][entry_index]
 
1732
                if entry[1][0][0] == 'a':
 
1733
                    # The file was marked as deleted in the active
 
1734
                    # state, and it is now deleted in the basis state,
 
1735
                    # so just remove the record entirely
 
1736
                    del self._dirblocks[block_index][1][entry_index]
 
1737
                else:
 
1738
                    # The basis entry needs to be marked deleted
 
1739
                    entry[1][1] = null
 
1740
                # If we are deleting a directory, we need to make sure
 
1741
                # that all of its children are already deleted
 
1742
                block_i, entry_i, d_present, f_present = \
 
1743
                    self._get_block_entry_index(old_path, '', 0)
 
1744
                if d_present:
 
1745
                    # The dir block is still present in the dirstate; this could
 
1746
                    # be due to it being in a parent tree, or a corrupt delta.
 
1747
                    for child_entry in self._dirblocks[block_i][1]:
 
1748
                        if child_entry[1][1][0] not in ('r', 'a'):
 
1749
                            self._changes_aborted = True
 
1750
                            raise errors.InconsistentDelta(old_path, entry[0][2],
 
1751
                                "The file id was deleted but its children were "
 
1752
                                "not deleted.")
1678
1753
            else:
1679
1754
                if entry[1][0][0] == 'a':
1680
1755
                    self._changes_aborted = True
1690
1765
                    del self._dirblocks[block_index][1][entry_index]
1691
1766
                else:
1692
1767
                    # it is being resurrected here, so blank it out temporarily.
 
1768
                    # should be equivalent to entry[1][1] = null
1693
1769
                    self._dirblocks[block_index][1][entry_index][1][1] = null
1694
1770
 
1695
1771
    def _after_delta_check_parents(self, parents, index):
1733
1809
                self._sha_cutoff_time()
1734
1810
            if (stat_value.st_mtime < self._cutoff_time
1735
1811
                and stat_value.st_ctime < self._cutoff_time):
1736
 
                entry[1][0] = ('f', sha1, entry[1][0][2], entry[1][0][3],
1737
 
                    packed_stat)
1738
 
                self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
1812
                entry[1][0] = ('f', sha1, stat_value.st_size, entry[1][0][3],
 
1813
                               packed_stat)
 
1814
                self._mark_modified([entry])
1739
1815
 
1740
1816
    def _sha_cutoff_time(self):
1741
1817
        """Return cutoff time.
1799
1875
        """Serialise the entire dirstate to a sequence of lines."""
1800
1876
        if (self._header_state == DirState.IN_MEMORY_UNMODIFIED and
1801
1877
            self._dirblock_state == DirState.IN_MEMORY_UNMODIFIED):
1802
 
            # read whats on disk.
 
1878
            # read what's on disk.
1803
1879
            self._state_file.seek(0)
1804
1880
            return self._state_file.readlines()
1805
1881
        lines = []
1806
1882
        lines.append(self._get_parents_line(self.get_parent_ids()))
1807
1883
        lines.append(self._get_ghosts_line(self._ghosts))
1808
 
        # append the root line which is special cased
1809
 
        lines.extend(map(self._entry_to_line, self._iter_entries()))
 
1884
        lines.extend(self._get_entry_lines())
1810
1885
        return self._get_output_lines(lines)
1811
1886
 
1812
1887
    def _get_ghosts_line(self, ghost_ids):
1817
1892
        """Create a line for the state file for parents information."""
1818
1893
        return '\0'.join([str(len(parent_ids))] + parent_ids)
1819
1894
 
 
1895
    def _get_entry_lines(self):
 
1896
        """Create lines for entries."""
 
1897
        return map(self._entry_to_line, self._iter_entries())
 
1898
 
1820
1899
    def _get_fields_to_entry(self):
1821
1900
        """Get a function which converts entry fields into a entry record.
1822
1901
 
1980
2059
                                          ' tree_index, file_id and path')
1981
2060
            return entry
1982
2061
        else:
1983
 
            possible_keys = self._get_id_index().get(fileid_utf8, None)
 
2062
            possible_keys = self._get_id_index().get(fileid_utf8, ())
1984
2063
            if not possible_keys:
1985
2064
                return None, None
1986
2065
            for key in possible_keys:
2088
2167
            executable = False
2089
2168
        else:
2090
2169
            raise Exception("can't pack %s" % inv_entry)
2091
 
        return (minikind, fingerprint, size, executable, tree_data)
 
2170
        return static_tuple.StaticTuple(minikind, fingerprint, size,
 
2171
                                        executable, tree_data)
2092
2172
 
2093
2173
    def _iter_child_entries(self, tree_index, path_utf8):
2094
2174
        """Iterate over all the entries that are children of path_utf.
2143
2223
                yield entry
2144
2224
 
2145
2225
    def _get_id_index(self):
2146
 
        """Get an id index of self._dirblocks."""
 
2226
        """Get an id index of self._dirblocks.
 
2227
        
 
2228
        This maps from file_id => [(directory, name, file_id)] entries where
 
2229
        that file_id appears in one of the trees.
 
2230
        """
2147
2231
        if self._id_index is None:
2148
2232
            id_index = {}
2149
2233
            for key, tree_details in self._iter_entries():
2150
 
                id_index.setdefault(key[2], set()).add(key)
 
2234
                self._add_to_id_index(id_index, key)
2151
2235
            self._id_index = id_index
2152
2236
        return self._id_index
2153
2237
 
 
2238
    def _add_to_id_index(self, id_index, entry_key):
 
2239
        """Add this entry to the _id_index mapping."""
 
2240
        # This code used to use a set for every entry in the id_index. However,
 
2241
        # it is *rare* to have more than one entry. So a set is a large
 
2242
        # overkill. And even when we do, we won't ever have more than the
 
2243
        # number of parent trees. Which is still a small number (rarely >2). As
 
2244
        # such, we use a simple tuple, and do our own uniqueness checks. While
 
2245
        # the 'in' check is O(N) since N is nicely bounded it shouldn't ever
 
2246
        # cause quadratic failure.
 
2247
        # TODO: This should use StaticTuple
 
2248
        file_id = entry_key[2]
 
2249
        entry_key = static_tuple.StaticTuple.from_sequence(entry_key)
 
2250
        if file_id not in id_index:
 
2251
            id_index[file_id] = static_tuple.StaticTuple(entry_key,)
 
2252
        else:
 
2253
            entry_keys = id_index[file_id]
 
2254
            if entry_key not in entry_keys:
 
2255
                id_index[file_id] = entry_keys + (entry_key,)
 
2256
 
 
2257
    def _remove_from_id_index(self, id_index, entry_key):
 
2258
        """Remove this entry from the _id_index mapping.
 
2259
 
 
2260
        It is an programming error to call this when the entry_key is not
 
2261
        already present.
 
2262
        """
 
2263
        file_id = entry_key[2]
 
2264
        entry_keys = list(id_index[file_id])
 
2265
        entry_keys.remove(entry_key)
 
2266
        id_index[file_id] = static_tuple.StaticTuple.from_sequence(entry_keys)
 
2267
 
2154
2268
    def _get_output_lines(self, lines):
2155
2269
        """Format lines for final output.
2156
2270
 
2176
2290
        """The number of parent entries in each record row."""
2177
2291
        return len(self._parents) - len(self._ghosts)
2178
2292
 
2179
 
    @staticmethod
2180
 
    def on_file(path, sha1_provider=None):
 
2293
    @classmethod
 
2294
    def on_file(cls, path, sha1_provider=None, worth_saving_limit=0):
2181
2295
        """Construct a DirState on the file at path "path".
2182
2296
 
2183
2297
        :param path: The path at which the dirstate file on disk should live.
2184
2298
        :param sha1_provider: an object meeting the SHA1Provider interface.
2185
2299
            If None, a DefaultSHA1Provider is used.
 
2300
        :param worth_saving_limit: when the exact number of hash changed
 
2301
            entries is known, only bother saving the dirstate if more than
 
2302
            this count of entries have changed. -1 means never save.
2186
2303
        :return: An unlocked DirState object, associated with the given path.
2187
2304
        """
2188
2305
        if sha1_provider is None:
2189
2306
            sha1_provider = DefaultSHA1Provider()
2190
 
        result = DirState(path, sha1_provider)
 
2307
        result = cls(path, sha1_provider,
 
2308
                     worth_saving_limit=worth_saving_limit)
2191
2309
        return result
2192
2310
 
2193
2311
    def _read_dirblocks_if_needed(self):
2285
2403
            trace.mutter('Not saving DirState because '
2286
2404
                    '_changes_aborted is set.')
2287
2405
            return
2288
 
        if (self._header_state == DirState.IN_MEMORY_MODIFIED or
2289
 
            self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
2290
 
 
 
2406
        # TODO: Since we now distinguish IN_MEMORY_MODIFIED from
 
2407
        #       IN_MEMORY_HASH_MODIFIED, we should only fail quietly if we fail
 
2408
        #       to save an IN_MEMORY_HASH_MODIFIED, and fail *noisily* if we
 
2409
        #       fail to save IN_MEMORY_MODIFIED
 
2410
        if self._worth_saving():
2291
2411
            grabbed_write_lock = False
2292
2412
            if self._lock_state != 'w':
2293
2413
                grabbed_write_lock, new_lock = self._lock_token.temporary_write_lock()
2301
2421
                    # We couldn't grab a write lock, so we switch back to a read one
2302
2422
                    return
2303
2423
            try:
 
2424
                lines = self.get_lines()
2304
2425
                self._state_file.seek(0)
2305
 
                self._state_file.writelines(self.get_lines())
 
2426
                self._state_file.writelines(lines)
2306
2427
                self._state_file.truncate()
2307
2428
                self._state_file.flush()
2308
 
                self._header_state = DirState.IN_MEMORY_UNMODIFIED
2309
 
                self._dirblock_state = DirState.IN_MEMORY_UNMODIFIED
 
2429
                self._mark_unmodified()
2310
2430
            finally:
2311
2431
                if grabbed_write_lock:
2312
2432
                    self._lock_token = self._lock_token.restore_read_lock()
2315
2435
                    #       not changed contents. Since restore_read_lock may
2316
2436
                    #       not be an atomic operation.
2317
2437
 
 
2438
    def _worth_saving(self):
 
2439
        """Is it worth saving the dirstate or not?"""
 
2440
        if (self._header_state == DirState.IN_MEMORY_MODIFIED
 
2441
            or self._dirblock_state == DirState.IN_MEMORY_MODIFIED):
 
2442
            return True
 
2443
        if self._dirblock_state == DirState.IN_MEMORY_HASH_MODIFIED:
 
2444
            if self._worth_saving_limit == -1:
 
2445
                # We never save hash changes when the limit is -1
 
2446
                return False
 
2447
            # If we're using smart saving and only a small number of
 
2448
            # entries have changed their hash, don't bother saving. John has
 
2449
            # suggested using a heuristic here based on the size of the
 
2450
            # changed files and/or tree. For now, we go with a configurable
 
2451
            # number of changes, keeping the calculation time
 
2452
            # as low overhead as possible. (This also keeps all existing
 
2453
            # tests passing as the default is 0, i.e. always save.)
 
2454
            if len(self._known_hash_changes) >= self._worth_saving_limit:
 
2455
                return True
 
2456
        return False
 
2457
 
2318
2458
    def _set_data(self, parent_ids, dirblocks):
2319
2459
        """Set the full dirstate data in memory.
2320
2460
 
2328
2468
        """
2329
2469
        # our memory copy is now authoritative.
2330
2470
        self._dirblocks = dirblocks
2331
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
2332
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2471
        self._mark_modified(header_modified=True)
2333
2472
        self._parents = list(parent_ids)
2334
2473
        self._id_index = None
2335
2474
        self._packed_stat_index = None
2355
2494
        self._make_absent(entry)
2356
2495
        self.update_minimal(('', '', new_id), 'd',
2357
2496
            path_utf8='', packed_stat=entry[1][0][4])
2358
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2497
        self._mark_modified()
 
2498
        # XXX: This was added by Ian, we need to make sure there
 
2499
        #      are tests for it, because it isn't in bzr.dev TRUNK
 
2500
        #      It looks like the only place it is called is in setting the root
 
2501
        #      id of the tree. So probably we never had an _id_index when we
 
2502
        #      don't even have a root yet.
 
2503
        if self._id_index is not None:
 
2504
            self._add_to_id_index(self._id_index, entry[0])
2359
2505
 
2360
2506
    def set_parent_trees(self, trees, ghosts):
2361
2507
        """Set the parent trees for the dirstate.
2406
2552
        parent_trees = [tree for rev_id, tree in trees if rev_id not in ghosts]
2407
2553
        # how many trees do we end up with
2408
2554
        parent_count = len(parent_trees)
 
2555
        st = static_tuple.StaticTuple
2409
2556
 
2410
2557
        # one: the current tree
2411
2558
        for entry in self._iter_entries():
2414
2561
                continue
2415
2562
            by_path[entry[0]] = [entry[1][0]] + \
2416
2563
                [DirState.NULL_PARENT_DETAILS] * parent_count
2417
 
            id_index[entry[0][2]] = set([entry[0]])
 
2564
            # TODO: Possibly inline this, since we know it isn't present yet
 
2565
            #       id_index[entry[0][2]] = (entry[0],)
 
2566
            self._add_to_id_index(id_index, entry[0])
2418
2567
 
2419
2568
        # now the parent trees:
2420
2569
        for tree_index, tree in enumerate(parent_trees):
2426
2575
            # the suffix is from tree_index+1:parent_count+1.
2427
2576
            new_location_suffix = [DirState.NULL_PARENT_DETAILS] * (parent_count - tree_index)
2428
2577
            # now stitch in all the entries from this tree
2429
 
            for path, entry in tree.inventory.iter_entries_by_dir():
 
2578
            last_dirname = None
 
2579
            for path, entry in tree.iter_entries_by_dir():
2430
2580
                # here we process each trees details for each item in the tree.
2431
2581
                # we first update any existing entries for the id at other paths,
2432
2582
                # then we either create or update the entry for the id at the
2439
2589
                file_id = entry.file_id
2440
2590
                path_utf8 = path.encode('utf8')
2441
2591
                dirname, basename = osutils.split(path_utf8)
2442
 
                new_entry_key = (dirname, basename, file_id)
 
2592
                if dirname == last_dirname:
 
2593
                    # Try to re-use objects as much as possible
 
2594
                    dirname = last_dirname
 
2595
                else:
 
2596
                    last_dirname = dirname
 
2597
                new_entry_key = st(dirname, basename, file_id)
2443
2598
                # tree index consistency: All other paths for this id in this tree
2444
2599
                # index must point to the correct path.
2445
 
                for entry_key in id_index.setdefault(file_id, set()):
 
2600
                entry_keys = id_index.get(file_id, ())
 
2601
                for entry_key in entry_keys:
2446
2602
                    # TODO:PROFILING: It might be faster to just update
2447
2603
                    # rather than checking if we need to, and then overwrite
2448
2604
                    # the one we are located at.
2451
2607
                        # other trees, so put absent pointers there
2452
2608
                        # This is the vertical axis in the matrix, all pointing
2453
2609
                        # to the real path.
2454
 
                        by_path[entry_key][tree_index] = ('r', path_utf8, 0, False, '')
2455
 
                # by path consistency: Insert into an existing path record (trivial), or
2456
 
                # add a new one with relocation pointers for the other tree indexes.
2457
 
                if new_entry_key in id_index[file_id]:
2458
 
                    # there is already an entry where this data belongs, just insert it.
 
2610
                        by_path[entry_key][tree_index] = st('r', path_utf8, 0,
 
2611
                                                            False, '')
 
2612
                # by path consistency: Insert into an existing path record
 
2613
                # (trivial), or add a new one with relocation pointers for the
 
2614
                # other tree indexes.
 
2615
                if new_entry_key in entry_keys:
 
2616
                    # there is already an entry where this data belongs, just
 
2617
                    # insert it.
2459
2618
                    by_path[new_entry_key][tree_index] = \
2460
2619
                        self._inv_entry_to_details(entry)
2461
2620
                else:
2465
2624
                    new_details = []
2466
2625
                    for lookup_index in xrange(tree_index):
2467
2626
                        # boundary case: this is the first occurence of file_id
2468
 
                        # so there are no id_indexs, possibly take this out of
 
2627
                        # so there are no id_indexes, possibly take this out of
2469
2628
                        # the loop?
2470
 
                        if not len(id_index[file_id]):
 
2629
                        if not len(entry_keys):
2471
2630
                            new_details.append(DirState.NULL_PARENT_DETAILS)
2472
2631
                        else:
2473
2632
                            # grab any one entry, use it to find the right path.
2474
 
                            # TODO: optimise this to reduce memory use in highly
2475
 
                            # fragmented situations by reusing the relocation
2476
 
                            # records.
2477
 
                            a_key = iter(id_index[file_id]).next()
 
2633
                            a_key = iter(entry_keys).next()
2478
2634
                            if by_path[a_key][lookup_index][0] in ('r', 'a'):
2479
 
                                # its a pointer or missing statement, use it as is.
 
2635
                                # its a pointer or missing statement, use it as
 
2636
                                # is.
2480
2637
                                new_details.append(by_path[a_key][lookup_index])
2481
2638
                            else:
2482
2639
                                # we have the right key, make a pointer to it.
2483
2640
                                real_path = ('/'.join(a_key[0:2])).strip('/')
2484
 
                                new_details.append(('r', real_path, 0, False, ''))
 
2641
                                new_details.append(st('r', real_path, 0, False,
 
2642
                                                      ''))
2485
2643
                    new_details.append(self._inv_entry_to_details(entry))
2486
2644
                    new_details.extend(new_location_suffix)
2487
2645
                    by_path[new_entry_key] = new_details
2488
 
                    id_index[file_id].add(new_entry_key)
 
2646
                    self._add_to_id_index(id_index, new_entry_key)
2489
2647
        # --- end generation of full tree mappings
2490
2648
 
2491
2649
        # sort and output all the entries
2493
2651
        self._entries_to_current_state(new_entries)
2494
2652
        self._parents = [rev_id for rev_id, tree in trees]
2495
2653
        self._ghosts = list(ghosts)
2496
 
        self._header_state = DirState.IN_MEMORY_MODIFIED
2497
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2654
        self._mark_modified(header_modified=True)
2498
2655
        self._id_index = id_index
2499
2656
 
2500
2657
    def _sort_entries(self, entry_list):
2504
2661
        try to keep everything in sorted blocks all the time, but sometimes
2505
2662
        it's easier to sort after the fact.
2506
2663
        """
2507
 
        def _key(entry):
 
2664
        # When sorting, we usually have 10x more entries than directories. (69k
 
2665
        # total entries, 4k directories). So cache the results of splitting.
 
2666
        # Saving time and objects. Also, use StaticTuple to avoid putting all
 
2667
        # of these object into python's garbage collector.
 
2668
        split_dirs = {}
 
2669
        def _key(entry, _split_dirs=split_dirs, _st=static_tuple.StaticTuple):
2508
2670
            # sort by: directory parts, file name, file id
2509
 
            return entry[0][0].split('/'), entry[0][1], entry[0][2]
 
2671
            dirpath, fname, file_id = entry[0]
 
2672
            try:
 
2673
                split = _split_dirs[dirpath]
 
2674
            except KeyError:
 
2675
                split = _st.from_sequence(dirpath.split('/'))
 
2676
                _split_dirs[dirpath] = split
 
2677
            return _st(split, fname, file_id)
2510
2678
        return sorted(entry_list, key=_key)
2511
2679
 
2512
2680
    def set_state_from_inventory(self, new_inv):
2637
2805
                        current_old[0][1].decode('utf8'))
2638
2806
                self._make_absent(current_old)
2639
2807
                current_old = advance(old_iterator)
2640
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2808
        self._mark_modified()
2641
2809
        self._id_index = None
2642
2810
        self._packed_stat_index = None
2643
2811
        if tracing:
2644
2812
            trace.mutter("set_state_from_inventory complete.")
2645
2813
 
 
2814
    def set_state_from_scratch(self, working_inv, parent_trees, parent_ghosts):
 
2815
        """Wipe the currently stored state and set it to something new.
 
2816
 
 
2817
        This is a hard-reset for the data we are working with.
 
2818
        """
 
2819
        # Technically, we really want a write lock, but until we write, we
 
2820
        # don't really need it.
 
2821
        self._requires_lock()
 
2822
        # root dir and root dir contents with no children. We have to have a
 
2823
        # root for set_state_from_inventory to work correctly.
 
2824
        empty_root = (('', '', inventory.ROOT_ID),
 
2825
                      [('d', '', 0, False, DirState.NULLSTAT)])
 
2826
        empty_tree_dirblocks = [('', [empty_root]), ('', [])]
 
2827
        self._set_data([], empty_tree_dirblocks)
 
2828
        self.set_state_from_inventory(working_inv)
 
2829
        self.set_parent_trees(parent_trees, parent_ghosts)
 
2830
 
2646
2831
    def _make_absent(self, current_old):
2647
2832
        """Mark current_old - an entry - as absent for tree 0.
2648
2833
 
2673
2858
            block[1].pop(entry_index)
2674
2859
            # if we have an id_index in use, remove this key from it for this id.
2675
2860
            if self._id_index is not None:
2676
 
                self._id_index[current_old[0][2]].remove(current_old[0])
 
2861
                self._remove_from_id_index(self._id_index, current_old[0])
2677
2862
        # update all remaining keys for this id to record it as absent. The
2678
2863
        # existing details may either be the record we are marking as deleted
2679
2864
        # (if there were other trees with the id present at this path), or may
2692
2877
            if update_tree_details[0][0] == 'a': # absent
2693
2878
                raise AssertionError('bad row %r' % (update_tree_details,))
2694
2879
            update_tree_details[0] = DirState.NULL_PARENT_DETAILS
2695
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
2880
        self._mark_modified()
2696
2881
        return last_reference
2697
2882
 
2698
2883
    def update_minimal(self, key, minikind, executable=False, fingerprint='',
2748
2933
                    else:
2749
2934
                        break
2750
2935
            # new entry, synthesis cross reference here,
2751
 
            existing_keys = id_index.setdefault(key[2], set())
 
2936
            existing_keys = id_index.get(key[2], ())
2752
2937
            if not existing_keys:
2753
2938
                # not currently in the state, simplest case
2754
2939
                new_entry = key, [new_details] + self._empty_parent_info()
2785
2970
                    # loop.
2786
2971
                    other_entry = other_block[other_entry_index]
2787
2972
                    other_entry[1][0] = ('r', path_utf8, 0, False, '')
2788
 
                    self._maybe_remove_row(other_block, other_entry_index,
2789
 
                        id_index)
 
2973
                    if self._maybe_remove_row(other_block, other_entry_index,
 
2974
                                              id_index):
 
2975
                        # If the row holding this was removed, we need to
 
2976
                        # recompute where this entry goes
 
2977
                        entry_index, _ = self._find_entry_index(key, block)
2790
2978
 
2791
2979
                # This loop:
2792
2980
                # adds a tuple to the new details for each column
2794
2982
                #  - or by creating a new pointer to the right row inside that column
2795
2983
                num_present_parents = self._num_present_parents()
2796
2984
                if num_present_parents:
 
2985
                    # TODO: This re-evaluates the existing_keys set, do we need
 
2986
                    #       to do that ourselves?
2797
2987
                    other_key = list(existing_keys)[0]
2798
2988
                for lookup_index in xrange(1, num_present_parents + 1):
2799
2989
                    # grab any one entry, use it to find the right path.
2818
3008
                        pointer_path = osutils.pathjoin(*other_key[0:2])
2819
3009
                        new_entry[1].append(('r', pointer_path, 0, False, ''))
2820
3010
            block.insert(entry_index, new_entry)
2821
 
            existing_keys.add(key)
 
3011
            self._add_to_id_index(id_index, key)
2822
3012
        else:
2823
3013
            # Does the new state matter?
2824
3014
            block[entry_index][1][0] = new_details
2833
3023
            # converted to relocated.
2834
3024
            if path_utf8 is None:
2835
3025
                raise AssertionError('no path')
2836
 
            for entry_key in id_index.setdefault(key[2], set()):
 
3026
            existing_keys = id_index.get(key[2], ())
 
3027
            if key not in existing_keys:
 
3028
                raise AssertionError('We found the entry in the blocks, but'
 
3029
                    ' the key is not in the id_index.'
 
3030
                    ' key: %s, existing_keys: %s' % (key, existing_keys))
 
3031
            for entry_key in existing_keys:
2837
3032
                # TODO:PROFILING: It might be faster to just update
2838
3033
                # rather than checking if we need to, and then overwrite
2839
3034
                # the one we are located at.
2857
3052
            if not present:
2858
3053
                self._dirblocks.insert(block_index, (subdir_key[0], []))
2859
3054
 
2860
 
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
3055
        self._mark_modified()
2861
3056
 
2862
3057
    def _maybe_remove_row(self, block, index, id_index):
2863
3058
        """Remove index if it is absent or relocated across the row.
2864
3059
        
2865
3060
        id_index is updated accordingly.
 
3061
        :return: True if we removed the row, False otherwise
2866
3062
        """
2867
3063
        present_in_row = False
2868
3064
        entry = block[index]
2872
3068
                break
2873
3069
        if not present_in_row:
2874
3070
            block.pop(index)
2875
 
            id_index[entry[0][2]].remove(entry[0])
 
3071
            self._remove_from_id_index(id_index, entry[0])
 
3072
            return True
 
3073
        return False
2876
3074
 
2877
3075
    def _validate(self):
2878
3076
        """Check that invariants on the dirblock are correct.
3020
3218
                        raise AssertionError(
3021
3219
                            'file_id %r did not match entry key %s'
3022
3220
                            % (file_id, entry_key))
 
3221
                if len(entry_keys) != len(set(entry_keys)):
 
3222
                    raise AssertionError(
 
3223
                        'id_index contained non-unique data for %s'
 
3224
                        % (entry_keys,))
3023
3225
 
3024
3226
    def _wipe_state(self):
3025
3227
        """Forget all state information about the dirstate."""
3122
3324
    # If we have gotten this far, that means that we need to actually
3123
3325
    # process this entry.
3124
3326
    link_or_sha1 = None
 
3327
    worth_saving = True
3125
3328
    if minikind == 'f':
3126
3329
        executable = state._is_executable(stat_value.st_mode,
3127
3330
                                         saved_executable)
3143
3346
        else:
3144
3347
            entry[1][0] = ('f', '', stat_value.st_size,
3145
3348
                           executable, DirState.NULLSTAT)
 
3349
            worth_saving = False
3146
3350
    elif minikind == 'd':
3147
3351
        link_or_sha1 = None
3148
3352
        entry[1][0] = ('d', '', 0, False, packed_stat)
3154
3358
                state._get_block_entry_index(entry[0][0], entry[0][1], 0)
3155
3359
            state._ensure_block(block_index, entry_index,
3156
3360
                               osutils.pathjoin(entry[0][0], entry[0][1]))
 
3361
        else:
 
3362
            worth_saving = False
3157
3363
    elif minikind == 'l':
 
3364
        if saved_minikind == 'l':
 
3365
            worth_saving = False
3158
3366
        link_or_sha1 = state._read_link(abspath, saved_link_or_sha1)
3159
3367
        if state._cutoff_time is None:
3160
3368
            state._sha_cutoff_time()
3165
3373
        else:
3166
3374
            entry[1][0] = ('l', '', stat_value.st_size,
3167
3375
                           False, DirState.NULLSTAT)
3168
 
    state._dirblock_state = DirState.IN_MEMORY_MODIFIED
 
3376
    if worth_saving:
 
3377
        state._mark_modified([entry])
3169
3378
    return link_or_sha1
3170
3379
 
3171
3380