~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/vf_repository.py

  • Committer: Tarmac
  • Author(s): Vincent Ladeuil
  • Date: 2017-01-30 14:42:05 UTC
  • mfrom: (6620.1.1 trunk)
  • Revision ID: tarmac-20170130144205-r8fh2xpmiuxyozpv
Merge  2.7 into trunk including fix for bug #1657238 [r=vila]

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
"""Repository formats built around versioned files."""
18
18
 
 
19
from __future__ import absolute_import
 
20
 
19
21
 
20
22
from bzrlib.lazy_import import lazy_import
21
23
lazy_import(globals(), """
23
25
 
24
26
from bzrlib import (
25
27
    check,
 
28
    config as _mod_config,
26
29
    debug,
27
30
    fetch as _mod_fetch,
28
31
    fifo_cache,
38
41
    tsort,
39
42
    ui,
40
43
    versionedfile,
 
44
    vf_search,
41
45
    )
42
46
 
43
47
from bzrlib.recordcounter import RecordCounter
65
69
    CommitBuilder,
66
70
    InterRepository,
67
71
    MetaDirRepository,
68
 
    MetaDirRepositoryFormat,
 
72
    RepositoryFormatMetaDir,
69
73
    Repository,
70
74
    RepositoryFormat,
71
75
    )
80
84
 
81
85
    supports_full_versioned_files = True
82
86
    supports_versioned_directories = True
 
87
    supports_unreferenced_revisions = True
83
88
 
84
89
    # Should commit add an inventory, or an inventory delta to the repository.
85
90
    _commit_inv_deltas = True
104
109
    # the default CommitBuilder does not manage trees whose root is versioned.
105
110
    _versioned_root = False
106
111
 
107
 
    def __init__(self, repository, parents, config, timestamp=None,
 
112
    def __init__(self, repository, parents, config_stack, timestamp=None,
108
113
                 timezone=None, committer=None, revprops=None,
109
114
                 revision_id=None, lossy=False):
110
115
        super(VersionedFileCommitBuilder, self).__init__(repository,
111
 
            parents, config, timestamp, timezone, committer, revprops,
 
116
            parents, config_stack, timestamp, timezone, committer, revprops,
112
117
            revision_id, lossy)
113
118
        try:
114
119
            basis_id = self.parents[0]
195
200
                       revision_id=self._new_revision_id,
196
201
                       properties=self._revprops)
197
202
        rev.parent_ids = self.parents
198
 
        self.repository.add_revision(self._new_revision_id, rev,
199
 
            self.new_inventory, self._config)
 
203
        if self._config_stack.get('create_signatures') == _mod_config.SIGN_ALWAYS:
 
204
            testament = Testament(rev, self.revision_tree())
 
205
            plaintext = testament.as_short_text()
 
206
            self.repository.store_revision_signature(
 
207
                gpg.GPGStrategy(self._config_stack), plaintext,
 
208
                self._new_revision_id)
 
209
        self.repository._add_revision(rev)
200
210
        self._ensure_fallback_inventories()
201
211
        self.repository.commit_write_group()
202
212
        return self._new_revision_id
594
604
                        _mod_revision.NULL_REVISION))
595
605
        # The basis inventory from a repository 
596
606
        if revtrees:
597
 
            basis_inv = revtrees[0].inventory
 
607
            basis_tree = revtrees[0]
598
608
        else:
599
 
            basis_inv = self.repository.revision_tree(
600
 
                _mod_revision.NULL_REVISION).inventory
 
609
            basis_tree = self.repository.revision_tree(
 
610
                _mod_revision.NULL_REVISION)
 
611
        basis_inv = basis_tree.root_inventory
601
612
        if len(self.parents) > 0:
602
613
            if basis_revision_id != self.parents[0] and not ghost_basis:
603
614
                raise Exception(
604
615
                    "arbitrary basis parents not yet supported with merges")
605
616
            for revtree in revtrees[1:]:
606
 
                for change in revtree.inventory._make_delta(basis_inv):
 
617
                for change in revtree.root_inventory._make_delta(basis_inv):
607
618
                    if change[1] is None:
608
619
                        # Not present in this parent.
609
620
                        continue
1011
1022
            # return a new inventory, but as there is no revision tree cache in
1012
1023
            # repository this is safe for now - RBC 20081013
1013
1024
            if basis_inv is None:
1014
 
                basis_inv = basis_tree.inventory
 
1025
                basis_inv = basis_tree.root_inventory
1015
1026
            basis_inv.apply_delta(delta)
1016
1027
            basis_inv.revision_id = new_revision_id
1017
1028
            return (self.add_inventory(new_revision_id, basis_inv, parents),
1028
1039
        self.inventories._access.flush()
1029
1040
        return result
1030
1041
 
1031
 
    def add_revision(self, revision_id, rev, inv=None, config=None):
 
1042
    def add_revision(self, revision_id, rev, inv=None):
1032
1043
        """Add rev to the revision store as revision_id.
1033
1044
 
1034
1045
        :param revision_id: the revision id to use.
1035
1046
        :param rev: The revision object.
1036
1047
        :param inv: The inventory for the revision. if None, it will be looked
1037
1048
                    up in the inventory storer
1038
 
        :param config: If None no digital signature will be created.
1039
 
                       If supplied its signature_needed method will be used
1040
 
                       to determine if a signature should be made.
1041
1049
        """
1042
1050
        # TODO: jam 20070210 Shouldn't we check rev.revision_id and
1043
1051
        #       rev.parent_ids?
1044
1052
        _mod_revision.check_not_reserved_id(revision_id)
1045
 
        if config is not None and config.signature_needed():
1046
 
            if inv is None:
1047
 
                inv = self.get_inventory(revision_id)
1048
 
            tree = InventoryRevisionTree(self, inv, revision_id)
1049
 
            testament = Testament(rev, tree)
1050
 
            plaintext = testament.as_short_text()
1051
 
            self.store_revision_signature(
1052
 
                gpg.GPGStrategy(config), plaintext, revision_id)
1053
1053
        # check inventory present
1054
1054
        if not self.inventories.get_parent_map([(revision_id,)]):
1055
1055
            if inv is None:
1199
1199
        """Instantiate a VersionedFileRepository.
1200
1200
 
1201
1201
        :param _format: The format of the repository on disk.
1202
 
        :param a_bzrdir: The BzrDir of the repository.
 
1202
        :param controldir: The ControlDir of the repository.
1203
1203
        :param control_files: Control files to use for locking, etc.
1204
1204
        """
1205
1205
        # In the future we will have a single api for all stores for
1219
1219
        # rather copying them?
1220
1220
        self._safe_to_return_from_cache = False
1221
1221
 
 
1222
    def fetch(self, source, revision_id=None, find_ghosts=False,
 
1223
            fetch_spec=None):
 
1224
        """Fetch the content required to construct revision_id from source.
 
1225
 
 
1226
        If revision_id is None and fetch_spec is None, then all content is
 
1227
        copied.
 
1228
 
 
1229
        fetch() may not be used when the repository is in a write group -
 
1230
        either finish the current write group before using fetch, or use
 
1231
        fetch before starting the write group.
 
1232
 
 
1233
        :param find_ghosts: Find and copy revisions in the source that are
 
1234
            ghosts in the target (and not reachable directly by walking out to
 
1235
            the first-present revision in target from revision_id).
 
1236
        :param revision_id: If specified, all the content needed for this
 
1237
            revision ID will be copied to the target.  Fetch will determine for
 
1238
            itself which content needs to be copied.
 
1239
        :param fetch_spec: If specified, a SearchResult or
 
1240
            PendingAncestryResult that describes which revisions to copy.  This
 
1241
            allows copying multiple heads at once.  Mutually exclusive with
 
1242
            revision_id.
 
1243
        """
 
1244
        if fetch_spec is not None and revision_id is not None:
 
1245
            raise AssertionError(
 
1246
                "fetch_spec and revision_id are mutually exclusive.")
 
1247
        if self.is_in_write_group():
 
1248
            raise errors.InternalBzrError(
 
1249
                "May not fetch while in a write group.")
 
1250
        # fast path same-url fetch operations
 
1251
        # TODO: lift out to somewhere common with RemoteRepository
 
1252
        # <https://bugs.launchpad.net/bzr/+bug/401646>
 
1253
        if (self.has_same_location(source)
 
1254
            and fetch_spec is None
 
1255
            and self._has_same_fallbacks(source)):
 
1256
            # check that last_revision is in 'from' and then return a
 
1257
            # no-operation.
 
1258
            if (revision_id is not None and
 
1259
                not _mod_revision.is_null(revision_id)):
 
1260
                self.get_revision(revision_id)
 
1261
            return 0, []
 
1262
        inter = InterRepository.get(source, self)
 
1263
        if (fetch_spec is not None and
 
1264
            not getattr(inter, "supports_fetch_spec", False)):
 
1265
            raise errors.UnsupportedOperation(
 
1266
                "fetch_spec not supported for %r" % inter)
 
1267
        return inter.fetch(revision_id=revision_id,
 
1268
            find_ghosts=find_ghosts, fetch_spec=fetch_spec)
 
1269
 
1222
1270
    @needs_read_lock
1223
1271
    def gather_stats(self, revid=None, committers=None):
1224
1272
        """See Repository.gather_stats()."""
1233
1281
            # result['size'] = t
1234
1282
        return result
1235
1283
 
1236
 
    def get_commit_builder(self, branch, parents, config, timestamp=None,
 
1284
    def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
1237
1285
                           timezone=None, committer=None, revprops=None,
1238
1286
                           revision_id=None, lossy=False):
1239
1287
        """Obtain a CommitBuilder for this repository.
1240
1288
 
1241
1289
        :param branch: Branch to commit to.
1242
1290
        :param parents: Revision ids of the parents of the new revision.
1243
 
        :param config: Configuration to use.
 
1291
        :param config_stack: Configuration stack to use.
1244
1292
        :param timestamp: Optional timestamp recorded for commit.
1245
1293
        :param timezone: Optional timezone for timestamp.
1246
1294
        :param committer: Optional committer to set for commit.
1253
1301
            raise errors.BzrError("Cannot commit directly to a stacked branch"
1254
1302
                " in pre-2a formats. See "
1255
1303
                "https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1256
 
        result = self._commit_builder_class(self, parents, config,
 
1304
        result = self._commit_builder_class(self, parents, config_stack,
1257
1305
            timestamp, timezone, committer, revprops, revision_id,
1258
1306
            lossy)
1259
1307
        self.start_write_group()
1515
1563
            text_keys[(file_id, revision_id)] = callable_data
1516
1564
        for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1517
1565
            if record.storage_kind == 'absent':
1518
 
                raise errors.RevisionNotPresent(record.key, self)
 
1566
                raise errors.RevisionNotPresent(record.key[1], record.key[0])
1519
1567
            yield text_keys[record.key], record.get_bytes_as('chunked')
1520
1568
 
1521
1569
    def _generate_text_key_index(self, text_key_references=None,
1600
1648
                            try:
1601
1649
                                inv = inventory_cache[parent_id]
1602
1650
                            except KeyError:
1603
 
                                inv = self.revision_tree(parent_id).inventory
 
1651
                                inv = self.revision_tree(parent_id).root_inventory
1604
1652
                                inventory_cache[parent_id] = inv
1605
1653
                            try:
1606
1654
                                parent_entry = inv[text_key[0]]
1699
1747
        if ((None in revision_ids)
1700
1748
            or (_mod_revision.NULL_REVISION in revision_ids)):
1701
1749
            raise ValueError('cannot get null revision inventory')
1702
 
        return self._iter_inventories(revision_ids, ordering)
 
1750
        for inv, revid in self._iter_inventories(revision_ids, ordering):
 
1751
            if inv is None:
 
1752
                raise errors.NoSuchRevision(self, revid)
 
1753
            yield inv
1703
1754
 
1704
1755
    def _iter_inventories(self, revision_ids, ordering):
1705
1756
        """single-document based inventory iteration."""
1706
1757
        inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
1707
1758
        for text, revision_id in inv_xmls:
1708
 
            yield self._deserialise_inventory(revision_id, text)
 
1759
            if text is None:
 
1760
                yield None, revision_id
 
1761
            else:
 
1762
                yield self._deserialise_inventory(revision_id, text), revision_id
1709
1763
 
1710
1764
    def _iter_inventory_xmls(self, revision_ids, ordering):
1711
1765
        if ordering is None:
1729
1783
                else:
1730
1784
                    yield ''.join(chunks), record.key[-1]
1731
1785
            else:
1732
 
                raise errors.NoSuchRevision(self, record.key)
 
1786
                yield None, record.key[-1]
1733
1787
            if order_as_requested:
1734
1788
                # Yield as many results as we can while preserving order.
1735
1789
                while next_key in text_chunks:
1764
1818
    def _get_inventory_xml(self, revision_id):
1765
1819
        """Get serialized inventory as a string."""
1766
1820
        texts = self._iter_inventory_xmls([revision_id], 'unordered')
1767
 
        try:
1768
 
            text, revision_id = texts.next()
1769
 
        except StopIteration:
1770
 
            raise errors.HistoryMissing(self, 'inventory', revision_id)
 
1821
        text, revision_id = texts.next()
 
1822
        if text is None:
 
1823
            raise errors.NoSuchRevision(self, revision_id)
1771
1824
        return text
1772
1825
 
1773
1826
    @needs_read_lock
1848
1901
        """Return the graph walker for text revisions."""
1849
1902
        return graph.Graph(self.texts)
1850
1903
 
 
1904
    def revision_ids_to_search_result(self, result_set):
 
1905
        """Convert a set of revision ids to a graph SearchResult."""
 
1906
        result_parents = set()
 
1907
        for parents in self.get_graph().get_parent_map(
 
1908
            result_set).itervalues():
 
1909
            result_parents.update(parents)
 
1910
        included_keys = result_set.intersection(result_parents)
 
1911
        start_keys = result_set.difference(included_keys)
 
1912
        exclude_keys = result_parents.difference(result_set)
 
1913
        result = vf_search.SearchResult(start_keys, exclude_keys,
 
1914
            len(result_set), result_set)
 
1915
        return result
 
1916
 
1851
1917
    def _get_versioned_file_checker(self, text_key_references=None,
1852
1918
        ancestors=None):
1853
1919
        """Return an object suitable for checking versioned files.
1938
2004
            control_files)
1939
2005
 
1940
2006
 
1941
 
class MetaDirVersionedFileRepositoryFormat(MetaDirRepositoryFormat,
 
2007
class MetaDirVersionedFileRepositoryFormat(RepositoryFormatMetaDir,
1942
2008
        VersionedFileRepositoryFormat):
1943
2009
    """Base class for repository formats using versioned files in metadirs."""
1944
2010
 
2376
2442
        invs_sent_so_far = set([_mod_revision.NULL_REVISION])
2377
2443
        inventory_cache = lru_cache.LRUCache(50)
2378
2444
        null_inventory = from_repo.revision_tree(
2379
 
            _mod_revision.NULL_REVISION).inventory
 
2445
            _mod_revision.NULL_REVISION).root_inventory
2380
2446
        # XXX: ideally the rich-root/tree-refs flags would be per-revision, not
2381
2447
        # per-repo (e.g.  streaming a non-rich-root revision out of a rich-root
2382
2448
        # repo back into a non-rich-root repo ought to be allowed)
2489
2555
 
2490
2556
    _walk_to_common_revisions_batch_size = 50
2491
2557
 
 
2558
    supports_fetch_spec = True
 
2559
 
2492
2560
    @needs_write_lock
2493
2561
    def fetch(self, revision_id=None, find_ghosts=False,
2494
2562
            fetch_spec=None):
2570
2638
                searcher.stop_searching_any(stop_revs)
2571
2639
            if searcher_exhausted:
2572
2640
                break
2573
 
        return searcher.get_result()
 
2641
        (started_keys, excludes, included_keys) = searcher.get_state()
 
2642
        return vf_search.SearchResult(started_keys, excludes,
 
2643
            len(included_keys), included_keys)
2574
2644
 
2575
2645
    @needs_read_lock
2576
2646
    def search_missing_revision_ids(self,
2725
2795
        """
2726
2796
        deltas = []
2727
2797
        # Generate deltas against each tree, to find the shortest.
 
2798
        # FIXME: Support nested trees
2728
2799
        texts_possibly_new_in_tree = set()
2729
2800
        for basis_id, basis_tree in possible_trees:
2730
 
            delta = tree.inventory._make_delta(basis_tree.inventory)
 
2801
            delta = tree.root_inventory._make_delta(basis_tree.root_inventory)
2731
2802
            for old_path, new_path, file_id, new_entry in delta:
2732
2803
                if new_path is None:
2733
2804
                    # This file_id isn't present in the new rev, so we don't
2770
2841
            parents_parents = [key[-1] for key in parents_parents_keys]
2771
2842
            basis_id = _mod_revision.NULL_REVISION
2772
2843
            basis_tree = self.source.revision_tree(basis_id)
2773
 
            delta = parent_tree.inventory._make_delta(basis_tree.inventory)
 
2844
            delta = parent_tree.root_inventory._make_delta(
 
2845
                basis_tree.root_inventory)
2774
2846
            self.target.add_inventory_by_delta(
2775
2847
                basis_id, delta, current_revision_id, parents_parents)
2776
2848
            cache[current_revision_id] = parent_tree
2835
2907
                kind = entry.kind
2836
2908
                texts_possibly_new_in_tree.add((file_id, entry.revision))
2837
2909
            for basis_id, basis_tree in possible_trees:
2838
 
                basis_inv = basis_tree.inventory
 
2910
                basis_inv = basis_tree.root_inventory
2839
2911
                for file_key in list(texts_possibly_new_in_tree):
2840
2912
                    file_id, file_revision = file_key
2841
2913
                    try:
3073
3145
            parent_trees[p_id] = repository.revision_tree(
3074
3146
                                     _mod_revision.NULL_REVISION)
3075
3147
 
3076
 
    inv = revision_tree.inventory
 
3148
    # FIXME: Support nested trees
 
3149
    inv = revision_tree.root_inventory
3077
3150
    entries = inv.iter_entries()
3078
3151
    # backwards compatibility hack: skip the root id.
3079
3152
    if not repository.supports_rich_root():