~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/dirstate.py

  • Committer: John Arbash Meinel
  • Date: 2009-07-24 18:26:21 UTC
  • mfrom: (4567 +trunk)
  • mto: This revision was merged to the branch mainline in revision 4568.
  • Revision ID: john@arbash-meinel.com-20090724182621-68s2jhoqf3pn72n7
Merge bzr.dev 4567 to resolve NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
204
204
import bisect
205
205
import binascii
206
206
import errno
 
207
import operator
207
208
import os
208
209
from stat import S_IEXEC
209
210
import stat
1277
1278
    def update_by_delta(self, delta):
1278
1279
        """Apply an inventory delta to the dirstate for tree 0
1279
1280
 
 
1281
        This is the workhorse for apply_inventory_delta in dirstate based
 
1282
        trees.
 
1283
 
1280
1284
        :param delta: An inventory delta.  See Inventory.apply_delta for
1281
1285
            details.
1282
1286
        """
1283
1287
        self._read_dirblocks_if_needed()
 
1288
        encode = cache_utf8.encode
1284
1289
        insertions = {}
1285
1290
        removals = {}
1286
 
        for old_path, new_path, file_id, inv_entry in sorted(delta, reverse=True):
 
1291
        # Accumulate parent references (path_utf8, id), to check for parentless
 
1292
        # items or items placed under files/links/tree-references. We get
 
1293
        # references from every item in the delta that is not a deletion and
 
1294
        # is not itself the root.
 
1295
        parents = set()
 
1296
        # Added ids must not be in the dirstate already. This set holds those
 
1297
        # ids.
 
1298
        new_ids = set()
 
1299
        # This loop transforms the delta to single atomic operations that can
 
1300
        # 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):
1287
1308
            if (file_id in insertions) or (file_id in removals):
1288
1309
                raise errors.InconsistentDelta(old_path or new_path, file_id,
1289
1310
                    "repeated file_id")
1290
1311
            if old_path is not None:
1291
1312
                old_path = old_path.encode('utf-8')
1292
1313
                removals[file_id] = old_path
 
1314
            else:
 
1315
                new_ids.add(file_id)
1293
1316
            if new_path is not None:
 
1317
                if inv_entry is None:
 
1318
                    raise errors.InconsistentDelta(new_path, file_id,
 
1319
                        "new_path with no entry")
1294
1320
                new_path = new_path.encode('utf-8')
1295
 
                dirname, basename = osutils.split(new_path)
1296
 
                key = (dirname, basename, file_id)
 
1321
                dirname_utf8, basename = osutils.split(new_path)
 
1322
                if basename:
 
1323
                    parents.add((dirname_utf8, inv_entry.parent_id))
 
1324
                key = (dirname_utf8, basename, file_id)
1297
1325
                minikind = DirState._kind_to_minikind[inv_entry.kind]
1298
1326
                if minikind == 't':
1299
1327
                    fingerprint = inv_entry.reference_revision
1321
1349
                                                  child_basename)
1322
1350
                    insertions[child[0][2]] = (key, minikind, executable,
1323
1351
                                               fingerprint, new_child_path)
1324
 
        self._apply_removals(removals.values())
1325
 
        self._apply_insertions(insertions.values())
 
1352
        self._check_delta_ids_absent(new_ids, delta, 0)
 
1353
        try:
 
1354
            self._apply_removals(removals.iteritems())
 
1355
            self._apply_insertions(insertions.values())
 
1356
            # Validate parents
 
1357
            self._after_delta_check_parents(parents, 0)
 
1358
        except errors.BzrError, e:
 
1359
            self._changes_aborted = True
 
1360
            if 'integrity error' not in str(e):
 
1361
                raise
 
1362
            # _get_entry raises BzrError when a request is inconsistent; we
 
1363
            # want such errors to be shown as InconsistentDelta - and that 
 
1364
            # fits the behaviour we trigger.
 
1365
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1326
1366
 
1327
1367
    def _apply_removals(self, removals):
1328
 
        for path in sorted(removals, reverse=True):
 
1368
        for file_id, path in sorted(removals, reverse=True,
 
1369
            key=operator.itemgetter(1)):
1329
1370
            dirname, basename = osutils.split(path)
1330
1371
            block_i, entry_i, d_present, f_present = \
1331
1372
                self._get_block_entry_index(dirname, basename, 0)
1332
 
            entry = self._dirblocks[block_i][1][entry_i]
 
1373
            try:
 
1374
                entry = self._dirblocks[block_i][1][entry_i]
 
1375
            except IndexError:
 
1376
                self._changes_aborted = True
 
1377
                raise errors.InconsistentDelta(path, file_id,
 
1378
                    "Wrong path for old path.")
 
1379
            if not f_present or entry[1][0][0] in 'ar':
 
1380
                self._changes_aborted = True
 
1381
                raise errors.InconsistentDelta(path, file_id,
 
1382
                    "Wrong path for old path.")
 
1383
            if file_id != entry[0][2]:
 
1384
                self._changes_aborted = True
 
1385
                raise errors.InconsistentDelta(path, file_id,
 
1386
                    "Attempt to remove path has wrong id - found %r."
 
1387
                    % entry[0][2])
1333
1388
            self._make_absent(entry)
1334
1389
            # See if we have a malformed delta: deleting a directory must not
1335
1390
            # leave crud behind. This increases the number of bisects needed
1343
1398
                # be due to it being in a parent tree, or a corrupt delta.
1344
1399
                for child_entry in self._dirblocks[block_i][1]:
1345
1400
                    if child_entry[1][0][0] not in ('r', 'a'):
 
1401
                        self._changes_aborted = True
1346
1402
                        raise errors.InconsistentDelta(path, entry[0][2],
1347
1403
                            "The file id was deleted but its children were "
1348
1404
                            "not deleted.")
1349
1405
 
1350
1406
    def _apply_insertions(self, adds):
1351
 
        for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
1352
 
            self.update_minimal(key, minikind, executable, fingerprint,
1353
 
                                path_utf8=path_utf8)
 
1407
        try:
 
1408
            for key, minikind, executable, fingerprint, path_utf8 in sorted(adds):
 
1409
                self.update_minimal(key, minikind, executable, fingerprint,
 
1410
                                    path_utf8=path_utf8)
 
1411
        except errors.NotVersionedError:
 
1412
            self._changes_aborted = True
 
1413
            raise errors.InconsistentDelta(path_utf8.decode('utf8'), key[2],
 
1414
                "Missing parent")
1354
1415
 
1355
1416
    def update_basis_by_delta(self, delta, new_revid):
1356
1417
        """Update the parents of this tree after a commit.
1400
1461
        # At the same time, to reduce interface friction we convert the input
1401
1462
        # inventory entries to dirstate.
1402
1463
        root_only = ('', '')
1403
 
        # Accumulate parent references (path and id), to check for parentless
 
1464
        # Accumulate parent references (path_utf8, id), to check for parentless
1404
1465
        # items or items placed under files/links/tree-references. We get
1405
1466
        # references from every item in the delta that is not a deletion and
1406
1467
        # is not itself the root.
1407
1468
        parents = set()
 
1469
        # Added ids must not be in the dirstate already. This set holds those
 
1470
        # ids.
 
1471
        new_ids = set()
1408
1472
        for old_path, new_path, file_id, inv_entry in delta:
1409
1473
            if inv_entry is not None and file_id != inv_entry.file_id:
1410
1474
                raise errors.InconsistentDelta(new_path, file_id,
1411
1475
                    "mismatched entry file_id %r" % inv_entry)
1412
1476
            if new_path is not None:
 
1477
                if inv_entry is None:
 
1478
                    raise errors.InconsistentDelta(new_path, file_id,
 
1479
                        "new_path with no entry")
1413
1480
                new_path_utf8 = encode(new_path)
1414
1481
                # note the parent for validation
1415
1482
                dirname_utf8, basename_utf8 = osutils.split(new_path_utf8)
1418
1485
            if old_path is None:
1419
1486
                adds.append((None, encode(new_path), file_id,
1420
1487
                    inv_to_entry(inv_entry), True))
 
1488
                new_ids.add(file_id)
1421
1489
            elif new_path is None:
1422
1490
                deletes.append((encode(old_path), None, file_id, None, True))
1423
1491
            elif (old_path, new_path) != root_only:
1466
1534
                # of everything.
1467
1535
                changes.append((encode(old_path), encode(new_path), file_id,
1468
1536
                    inv_to_entry(inv_entry)))
1469
 
 
 
1537
        self._check_delta_ids_absent(new_ids, delta, 1)
1470
1538
        try:
1471
1539
            # Finish expunging deletes/first half of renames.
1472
1540
            self._update_basis_apply_deletes(deletes)
1475
1543
            # Apply in-situ changes.
1476
1544
            self._update_basis_apply_changes(changes)
1477
1545
            # Validate parents
1478
 
            self._update_basis_check_parents(parents)
 
1546
            self._after_delta_check_parents(parents, 1)
1479
1547
        except errors.BzrError, e:
 
1548
            self._changes_aborted = True
1480
1549
            if 'integrity error' not in str(e):
1481
1550
                raise
1482
1551
            # _get_entry raises BzrError when a request is inconsistent; we
1484
1553
            # fits the behaviour we trigger. Partof this is driven by dirstate
1485
1554
            # only supporting deltas that turn the basis into a closer fit to
1486
1555
            # the active tree.
1487
 
            self._changes_aborted = True
1488
1556
            raise errors.InconsistentDeltaDelta(delta, "error from _get_entry.")
1489
1557
 
1490
1558
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
1492
1560
        self._id_index = None
1493
1561
        return
1494
1562
 
 
1563
    def _check_delta_ids_absent(self, new_ids, delta, tree_index):
 
1564
        """Check that none of the file_ids in new_ids are present in a tree."""
 
1565
        if not new_ids:
 
1566
            return
 
1567
        id_index = self._get_id_index()
 
1568
        for file_id in new_ids:
 
1569
            for key in id_index.get(file_id, []):
 
1570
                block_i, entry_i, d_present, f_present = \
 
1571
                    self._get_block_entry_index(key[0], key[1], tree_index)
 
1572
                if not f_present:
 
1573
                    # In a different tree
 
1574
                    continue
 
1575
                entry = self._dirblocks[block_i][1][entry_i]
 
1576
                if entry[0][2] != file_id:
 
1577
                    # Different file_id, so not what we want.
 
1578
                    continue
 
1579
                # NB: No changes made before this helper is called, so no need
 
1580
                # to set the _changes_aborted flag.
 
1581
                raise errors.InconsistentDelta(
 
1582
                    ("%s/%s" % key[0:2]).decode('utf8'), file_id,
 
1583
                    "This file_id is new in the delta but already present in "
 
1584
                    "the target")
 
1585
 
1495
1586
    def _update_basis_apply_adds(self, adds):
1496
1587
        """Apply a sequence of adds to tree 1 during update_basis_by_delta.
1497
1588
 
1562
1653
        null = DirState.NULL_PARENT_DETAILS
1563
1654
        for old_path, new_path, file_id, _, real_delete in deletes:
1564
1655
            if real_delete != (new_path is None):
 
1656
                self._changes_aborted = True
1565
1657
                raise AssertionError("bad delete delta")
1566
1658
            # the entry for this file_id must be in tree 1.
1567
1659
            dirname, basename = osutils.split(old_path)
1600
1692
                    # it is being resurrected here, so blank it out temporarily.
1601
1693
                    self._dirblocks[block_index][1][entry_index][1][1] = null
1602
1694
 
1603
 
    def _update_basis_check_parents(self, parents):
1604
 
        """Check that parents required by the delta are all intact."""
 
1695
    def _after_delta_check_parents(self, parents, index):
 
1696
        """Check that parents required by the delta are all intact.
 
1697
        
 
1698
        :param parents: An iterable of (path_utf8, file_id) tuples which are
 
1699
            required to be present in tree 'index' at path_utf8 with id file_id
 
1700
            and be a directory.
 
1701
        :param index: The column in the dirstate to check for parents in.
 
1702
        """
1605
1703
        for dirname_utf8, file_id in parents:
1606
1704
            # Get the entry - the ensures that file_id, dirname_utf8 exists and
1607
1705
            # has the right file id.
1608
 
            entry = self._get_entry(1, file_id, dirname_utf8)
 
1706
            entry = self._get_entry(index, file_id, dirname_utf8)
1609
1707
            if entry[1] is None:
1610
1708
                self._changes_aborted = True
1611
1709
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1612
1710
                    file_id, "This parent is not present.")
1613
1711
            # Parents of things must be directories
1614
 
            if entry[1][1][0] != 'd':
 
1712
            if entry[1][index][0] != 'd':
1615
1713
                self._changes_aborted = True
1616
1714
                raise errors.InconsistentDelta(dirname_utf8.decode('utf8'),
1617
1715
                    file_id, "This parent is not a directory.")
2422
2520
        if 'evil' in debug.debug_flags:
2423
2521
            trace.mutter_callsite(1,
2424
2522
                "set_state_from_inventory called; please mutate the tree instead")
 
2523
        tracing = 'dirstate' in debug.debug_flags
 
2524
        if tracing:
 
2525
            trace.mutter("set_state_from_inventory trace:")
2425
2526
        self._read_dirblocks_if_needed()
2426
2527
        # sketch:
2427
2528
        # Two iterators: current data and new data, both in dirblock order.
2436
2537
        new_iterator = new_inv.iter_entries_by_dir()
2437
2538
        # we will be modifying the dirstate, so we need a stable iterator. In
2438
2539
        # future we might write one, for now we just clone the state into a
2439
 
        # list - which is a shallow copy.
 
2540
        # list using a copy so that we see every original item and don't have
 
2541
        # to adjust the position when items are inserted or deleted in the
 
2542
        # underlying dirstate.
2440
2543
        old_iterator = iter(list(self._iter_entries()))
2441
2544
        # both must have roots so this is safe:
2442
2545
        current_new = new_iterator.next()
2476
2579
            # we make both end conditions explicit
2477
2580
            if not current_old:
2478
2581
                # old is finished: insert current_new into the state.
 
2582
                if tracing:
 
2583
                    trace.mutter("Appending from new '%s'.",
 
2584
                        new_path_utf8.decode('utf8'))
2479
2585
                self.update_minimal(new_entry_key, current_new_minikind,
2480
2586
                    executable=current_new[1].executable,
2481
 
                    path_utf8=new_path_utf8, fingerprint=fingerprint)
 
2587
                    path_utf8=new_path_utf8, fingerprint=fingerprint,
 
2588
                    fullscan=True)
2482
2589
                current_new = advance(new_iterator)
2483
2590
            elif not current_new:
2484
2591
                # new is finished
 
2592
                if tracing:
 
2593
                    trace.mutter("Truncating from old '%s/%s'.",
 
2594
                        current_old[0][0].decode('utf8'),
 
2595
                        current_old[0][1].decode('utf8'))
2485
2596
                self._make_absent(current_old)
2486
2597
                current_old = advance(old_iterator)
2487
2598
            elif new_entry_key == current_old[0]:
2494
2605
                # kind has changed.
2495
2606
                if (current_old[1][0][3] != current_new[1].executable or
2496
2607
                    current_old[1][0][0] != current_new_minikind):
 
2608
                    if tracing:
 
2609
                        trace.mutter("Updating in-place change '%s'.",
 
2610
                            new_path_utf8.decode('utf8'))
2497
2611
                    self.update_minimal(current_old[0], current_new_minikind,
2498
2612
                        executable=current_new[1].executable,
2499
 
                        path_utf8=new_path_utf8, fingerprint=fingerprint)
 
2613
                        path_utf8=new_path_utf8, fingerprint=fingerprint,
 
2614
                        fullscan=True)
2500
2615
                # both sides are dealt with, move on
2501
2616
                current_old = advance(old_iterator)
2502
2617
                current_new = advance(new_iterator)
2505
2620
                      and new_entry_key[1:] < current_old[0][1:])):
2506
2621
                # new comes before:
2507
2622
                # add a entry for this and advance new
 
2623
                if tracing:
 
2624
                    trace.mutter("Inserting from new '%s'.",
 
2625
                        new_path_utf8.decode('utf8'))
2508
2626
                self.update_minimal(new_entry_key, current_new_minikind,
2509
2627
                    executable=current_new[1].executable,
2510
 
                    path_utf8=new_path_utf8, fingerprint=fingerprint)
 
2628
                    path_utf8=new_path_utf8, fingerprint=fingerprint,
 
2629
                    fullscan=True)
2511
2630
                current_new = advance(new_iterator)
2512
2631
            else:
2513
2632
                # we've advanced past the place where the old key would be,
2514
2633
                # without seeing it in the new list.  so it must be gone.
 
2634
                if tracing:
 
2635
                    trace.mutter("Deleting from old '%s/%s'.",
 
2636
                        current_old[0][0].decode('utf8'),
 
2637
                        current_old[0][1].decode('utf8'))
2515
2638
                self._make_absent(current_old)
2516
2639
                current_old = advance(old_iterator)
2517
2640
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2518
2641
        self._id_index = None
2519
2642
        self._packed_stat_index = None
 
2643
        if tracing:
 
2644
            trace.mutter("set_state_from_inventory complete.")
2520
2645
 
2521
2646
    def _make_absent(self, current_old):
2522
2647
        """Mark current_old - an entry - as absent for tree 0.
2571
2696
        return last_reference
2572
2697
 
2573
2698
    def update_minimal(self, key, minikind, executable=False, fingerprint='',
2574
 
                       packed_stat=None, size=0, path_utf8=None):
 
2699
        packed_stat=None, size=0, path_utf8=None, fullscan=False):
2575
2700
        """Update an entry to the state in tree 0.
2576
2701
 
2577
2702
        This will either create a new entry at 'key' or update an existing one.
2588
2713
        :param size: Size information for new entry
2589
2714
        :param path_utf8: key[0] + '/' + key[1], just passed in to avoid doing
2590
2715
                extra computation.
 
2716
        :param fullscan: If True then a complete scan of the dirstate is being
 
2717
            done and checking for duplicate rows should not be done. This
 
2718
            should only be set by set_state_from_inventory and similar methods.
2591
2719
 
2592
2720
        If packed_stat and fingerprint are not given, they're invalidated in
2593
2721
        the entry.
2602
2730
        new_details = (minikind, fingerprint, size, executable, packed_stat)
2603
2731
        id_index = self._get_id_index()
2604
2732
        if not present:
 
2733
            # New record. Check there isn't a entry at this path already.
 
2734
            if not fullscan:
 
2735
                low_index, _ = self._find_entry_index(key[0:2] + ('',), block)
 
2736
                while low_index < len(block):
 
2737
                    entry = block[low_index]
 
2738
                    if entry[0][0:2] == key[0:2]:
 
2739
                        if entry[1][0][0] not in 'ar':
 
2740
                            # This entry has the same path (but a different id) as
 
2741
                            # the new entry we're adding, and is present in ths
 
2742
                            # tree.
 
2743
                            raise errors.InconsistentDelta(
 
2744
                                ("%s/%s" % key[0:2]).decode('utf8'), key[2],
 
2745
                                "Attempt to add item at path already occupied by "
 
2746
                                "id %r" % entry[0][2])
 
2747
                        low_index += 1
 
2748
                    else:
 
2749
                        break
2605
2750
            # new entry, synthesis cross reference here,
2606
2751
            existing_keys = id_index.setdefault(key[2], set())
2607
2752
            if not existing_keys:
2612
2757
                # grab one of them and use it to generate parent
2613
2758
                # relocation/absent entries.
2614
2759
                new_entry = key, [new_details]
2615
 
                for other_key in existing_keys:
 
2760
                # existing_keys can be changed as we iterate.
 
2761
                for other_key in tuple(existing_keys):
2616
2762
                    # change the record at other to be a pointer to this new
2617
2763
                    # record. The loop looks similar to the change to
2618
2764
                    # relocations when updating an existing record but its not:
2619
2765
                    # the test for existing kinds is different: this can be
2620
2766
                    # factored out to a helper though.
2621
 
                    other_block_index, present = self._find_block_index_from_key(other_key)
2622
 
                    if not present:
2623
 
                        raise AssertionError('could not find block for %s' % (other_key,))
2624
 
                    other_entry_index, present = self._find_entry_index(other_key,
2625
 
                                            self._dirblocks[other_block_index][1])
2626
 
                    if not present:
2627
 
                        raise AssertionError('could not find entry for %s' % (other_key,))
 
2767
                    other_block_index, present = self._find_block_index_from_key(
 
2768
                        other_key)
 
2769
                    if not present:
 
2770
                        raise AssertionError('could not find block for %s' % (
 
2771
                            other_key,))
 
2772
                    other_block = self._dirblocks[other_block_index][1]
 
2773
                    other_entry_index, present = self._find_entry_index(
 
2774
                        other_key, other_block)
 
2775
                    if not present:
 
2776
                        raise AssertionError(
 
2777
                            'update_minimal: could not find other entry for %s'
 
2778
                            % (other_key,))
2628
2779
                    if path_utf8 is None:
2629
2780
                        raise AssertionError('no path')
2630
 
                    self._dirblocks[other_block_index][1][other_entry_index][1][0] = \
2631
 
                        ('r', path_utf8, 0, False, '')
 
2781
                    # Turn this other location into a reference to the new
 
2782
                    # location. This also updates the aliased iterator
 
2783
                    # (current_old in set_state_from_inventory) so that the old
 
2784
                    # entry, if not already examined, is skipped over by that
 
2785
                    # loop.
 
2786
                    other_entry = other_block[other_entry_index]
 
2787
                    other_entry[1][0] = ('r', path_utf8, 0, False, '')
 
2788
                    self._maybe_remove_row(other_block, other_entry_index,
 
2789
                        id_index)
2632
2790
 
 
2791
                # This loop:
 
2792
                # adds a tuple to the new details for each column
 
2793
                #  - either by copying an existing relocation pointer inside that column
 
2794
                #  - or by creating a new pointer to the right row inside that column
2633
2795
                num_present_parents = self._num_present_parents()
 
2796
                if num_present_parents:
 
2797
                    other_key = list(existing_keys)[0]
2634
2798
                for lookup_index in xrange(1, num_present_parents + 1):
2635
2799
                    # grab any one entry, use it to find the right path.
2636
2800
                    # TODO: optimise this to reduce memory use in highly
2643
2807
                    update_entry_index, present = \
2644
2808
                        self._find_entry_index(other_key, self._dirblocks[update_block_index][1])
2645
2809
                    if not present:
2646
 
                        raise AssertionError('could not find entry for %s' % (other_key,))
 
2810
                        raise AssertionError('update_minimal: could not find entry for %s' % (other_key,))
2647
2811
                    update_details = self._dirblocks[update_block_index][1][update_entry_index][1][lookup_index]
2648
2812
                    if update_details[0] in 'ar': # relocated, absent
2649
2813
                        # its a pointer or absent in lookup_index's tree, use
2695
2859
 
2696
2860
        self._dirblock_state = DirState.IN_MEMORY_MODIFIED
2697
2861
 
 
2862
    def _maybe_remove_row(self, block, index, id_index):
 
2863
        """Remove index if it is absent or relocated across the row.
 
2864
        
 
2865
        id_index is updated accordingly.
 
2866
        """
 
2867
        present_in_row = False
 
2868
        entry = block[index]
 
2869
        for column in entry[1]:
 
2870
            if column[0] not in 'ar':
 
2871
                present_in_row = True
 
2872
                break
 
2873
        if not present_in_row:
 
2874
            block.pop(index)
 
2875
            id_index[entry[0][2]].remove(entry[0])
 
2876
 
2698
2877
    def _validate(self):
2699
2878
        """Check that invariants on the dirblock are correct.
2700
2879