~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/repository.py

Abbreviate pack_stat struct format to '>6L'

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
from bzrlib.lazy_import import lazy_import
18
18
lazy_import(globals(), """
 
19
import itertools
19
20
import time
20
21
 
21
22
from bzrlib import (
22
23
    bzrdir,
23
 
    check,
24
24
    config,
25
25
    controldir,
26
26
    debug,
32
32
    revision as _mod_revision,
33
33
    testament as _mod_testament,
34
34
    tsort,
 
35
    gpg,
35
36
    )
36
37
from bzrlib.bundle import serializer
 
38
from bzrlib.i18n import gettext
37
39
""")
38
40
 
39
41
from bzrlib import (
279
281
                raise
280
282
            mutter('abort_write_group failed')
281
283
            log_exception_quietly()
282
 
            note('bzr: ERROR (ignored): %s', exc)
 
284
            note(gettext('bzr: ERROR (ignored): %s'), exc)
283
285
        self._write_group = None
284
286
 
285
287
    def _abort_write_group(self):
339
341
        """
340
342
        self.control_files.break_lock()
341
343
 
342
 
    @needs_read_lock
343
 
    def _eliminate_revisions_not_present(self, revision_ids):
344
 
        """Check every revision id in revision_ids to see if we have it.
345
 
 
346
 
        Returns a set of the present revisions.
347
 
        """
348
 
        result = []
349
 
        graph = self.get_graph()
350
 
        parent_map = graph.get_parent_map(revision_ids)
351
 
        # The old API returned a list, should this actually be a set?
352
 
        return parent_map.keys()
353
 
 
354
344
    @staticmethod
355
345
    def create(a_bzrdir):
356
346
        """Construct the current default format repository in a_bzrdir."""
371
361
        # the following are part of the public API for Repository:
372
362
        self.bzrdir = a_bzrdir
373
363
        self.control_files = control_files
374
 
        self._transport = control_files._transport
375
 
        self.base = self._transport.base
376
364
        # for tests
377
365
        self._write_group = None
378
366
        # Additional places to query for data.
416
404
        """
417
405
        if self.__class__ is not other.__class__:
418
406
            return False
419
 
        return (self._transport.base == other._transport.base)
 
407
        return (self.control_url == other.control_url)
420
408
 
421
409
    def is_in_write_group(self):
422
410
        """Return True if there is an open write group.
522
510
        if revid and committers:
523
511
            result['committers'] = 0
524
512
        if revid and revid != _mod_revision.NULL_REVISION:
 
513
            graph = self.get_graph()
525
514
            if committers:
526
515
                all_committers = set()
527
 
            revisions = self.get_ancestry(revid)
528
 
            # pop the leading None
529
 
            revisions.pop(0)
530
 
            first_revision = None
 
516
            revisions = [r for (r, p) in graph.iter_ancestry([revid])
 
517
                        if r != _mod_revision.NULL_REVISION]
 
518
            last_revision = None
531
519
            if not committers:
532
520
                # ignore the revisions in the middle - just grab first and last
533
521
                revisions = revisions[0], revisions[-1]
534
522
            for revision in self.get_revisions(revisions):
535
 
                if not first_revision:
536
 
                    first_revision = revision
 
523
                if not last_revision:
 
524
                    last_revision = revision
537
525
                if committers:
538
526
                    all_committers.add(revision.committer)
539
 
            last_revision = revision
 
527
            first_revision = revision
540
528
            if committers:
541
529
                result['committers'] = len(all_committers)
542
530
            result['firstrev'] = (first_revision.timestamp,
585
573
    @needs_read_lock
586
574
    def search_missing_revision_ids(self, other,
587
575
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
588
 
            find_ghosts=True, revision_ids=None, if_present_ids=None):
 
576
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
577
            limit=None):
589
578
        """Return the revision ids that other has that this does not.
590
579
 
591
580
        These are returned in topological order.
604
593
                revision_ids = [revision_id]
605
594
        return InterRepository.get(other, self).search_missing_revision_ids(
606
595
            find_ghosts=find_ghosts, revision_ids=revision_ids,
607
 
            if_present_ids=if_present_ids)
 
596
            if_present_ids=if_present_ids, limit=limit)
608
597
 
609
598
    @staticmethod
610
599
    def open(base):
927
916
        """
928
917
        raise NotImplementedError(self.add_signature_text)
929
918
 
930
 
    def find_text_key_references(self):
931
 
        """Find the text key references within the repository.
932
 
 
933
 
        :return: A dictionary mapping text keys ((fileid, revision_id) tuples)
934
 
            to whether they were referred to by the tree of the
935
 
            revision_id that they contain. 
936
 
        """
937
 
        raise NotImplementedError(self.find_text_key_references)
938
 
 
939
919
    def _find_parent_ids_of_revisions(self, revision_ids):
940
920
        """Find all parent ids that are mentioned in the revision graph.
941
921
 
1010
990
            raise AssertionError('_iter_for_revno returned too much history')
1011
991
        return (True, partial_history[-1])
1012
992
 
 
993
    @symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1013
994
    def iter_reverse_revision_history(self, revision_id):
1014
995
        """Iterate backwards through revision ids in the lefthand history
1015
996
 
1063
1044
        raise NotImplementedError(self.revision_trees)
1064
1045
 
1065
1046
    @needs_read_lock
 
1047
    @symbol_versioning.deprecated_method(
 
1048
        symbol_versioning.deprecated_in((2, 4, 0)))
1066
1049
    def get_ancestry(self, revision_id, topo_sorted=True):
1067
1050
        """Return a list of revision-ids integrated by a revision.
1068
1051
 
1072
1055
 
1073
1056
        This is topologically sorted.
1074
1057
        """
 
1058
        if 'evil' in debug.debug_flags:
 
1059
            mutter_callsite(2, "get_ancestry is linear with history.")
1075
1060
        if _mod_revision.is_null(revision_id):
1076
1061
            return [None]
1077
1062
        if not self.has_revision(revision_id):
1160
1145
        """
1161
1146
        raise NotImplementedError(self.get_known_graph_ancestry)
1162
1147
 
 
1148
    def get_file_graph(self):
 
1149
        """Return the graph walker for files."""
 
1150
        raise NotImplementedError(self.get_file_graph)
 
1151
 
1163
1152
    def get_graph(self, other_repository=None):
1164
1153
        """Return the graph walker for this repository format"""
1165
1154
        parents_provider = self._make_parents_provider()
1204
1193
        plaintext = testament.as_short_text()
1205
1194
        self.store_revision_signature(gpg_strategy, plaintext, revision_id)
1206
1195
 
 
1196
    @needs_read_lock
 
1197
    def verify_revision(self, revision_id, gpg_strategy):
 
1198
        """Verify the signature on a revision.
 
1199
        
 
1200
        :param revision_id: the revision to verify
 
1201
        :gpg_strategy: the GPGStrategy object to used
 
1202
        
 
1203
        :return: gpg.SIGNATURE_VALID or a failed SIGNATURE_ value
 
1204
        """
 
1205
        if not self.has_signature_for_revision_id(revision_id):
 
1206
            return gpg.SIGNATURE_NOT_SIGNED, None
 
1207
        signature = self.get_signature_text(revision_id)
 
1208
 
 
1209
        testament = _mod_testament.Testament.from_revision(self, revision_id)
 
1210
        plaintext = testament.as_short_text()
 
1211
 
 
1212
        return gpg_strategy.verify(signature, plaintext)
 
1213
 
1207
1214
    def has_signature_for_revision_id(self, revision_id):
1208
1215
        """Query for a revision signature for revision_id in the repository."""
1209
1216
        raise NotImplementedError(self.has_signature_for_revision_id)
1212
1219
        """Return the text for a signature."""
1213
1220
        raise NotImplementedError(self.get_signature_text)
1214
1221
 
1215
 
    @needs_read_lock
1216
1222
    def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1217
1223
        """Check consistency of all history of given revision_ids.
1218
1224
 
1226
1232
        :param check_repo: If False do not check the repository contents, just 
1227
1233
            calculate the data callback_refs requires and call them back.
1228
1234
        """
1229
 
        return self._check(revision_ids, callback_refs=callback_refs,
 
1235
        return self._check(revision_ids=revision_ids, callback_refs=callback_refs,
1230
1236
            check_repo=check_repo)
1231
1237
 
1232
 
    def _check(self, revision_ids, callback_refs, check_repo):
1233
 
        result = check.Check(self, check_repo=check_repo)
1234
 
        result.check(callback_refs)
1235
 
        return result
 
1238
    def _check(self, revision_ids=None, callback_refs=None, check_repo=True):
 
1239
        raise NotImplementedError(self.check)
1236
1240
 
1237
1241
    def _warn_if_deprecated(self, branch=None):
1238
1242
        if not self._format.is_deprecated():
1411
1415
    revision_graph_can_have_wrong_parents = None
1412
1416
    # Does this format support rich root data?
1413
1417
    rich_root_data = None
 
1418
    # Does this format support explicitly versioned directories?
 
1419
    supports_versioned_directories = None
 
1420
    # Can other repositories be nested into one of this format?
 
1421
    supports_nesting_repositories = None
1414
1422
 
1415
1423
    def __repr__(self):
1416
1424
        return "%s()" % self.__class__.__name__
1542
1550
    supports_tree_reference = False
1543
1551
    supports_external_lookups = False
1544
1552
    supports_leaving_lock = True
 
1553
    supports_nesting_repositories = True
1545
1554
 
1546
1555
    @property
1547
1556
    def _matchingbzrdir(self):
1685
1694
    InterRepository.get(other).method_name(parameters).
1686
1695
    """
1687
1696
 
1688
 
    _walk_to_common_revisions_batch_size = 50
1689
1697
    _optimisers = []
1690
1698
    """The available optimised InterRepository types."""
1691
1699
 
1716
1724
                            content is copied.
1717
1725
        :return: None.
1718
1726
        """
1719
 
        ui.ui_factory.warn_experimental_format_fetch(self)
1720
 
        from bzrlib.fetch import RepoFetcher
1721
 
        # See <https://launchpad.net/bugs/456077> asking for a warning here
1722
 
        if self.source._format.network_name() != self.target._format.network_name():
1723
 
            ui.ui_factory.show_user_warning('cross_format_fetch',
1724
 
                from_format=self.source._format,
1725
 
                to_format=self.target._format)
1726
 
        f = RepoFetcher(to_repository=self.target,
1727
 
                               from_repository=self.source,
1728
 
                               last_revision=revision_id,
1729
 
                               fetch_spec=fetch_spec,
1730
 
                               find_ghosts=find_ghosts)
1731
 
 
1732
 
    def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
1733
 
        """Walk out from revision_ids in source to revisions target has.
1734
 
 
1735
 
        :param revision_ids: The start point for the search.
1736
 
        :return: A set of revision ids.
1737
 
        """
1738
 
        target_graph = self.target.get_graph()
1739
 
        revision_ids = frozenset(revision_ids)
1740
 
        if if_present_ids:
1741
 
            all_wanted_revs = revision_ids.union(if_present_ids)
1742
 
        else:
1743
 
            all_wanted_revs = revision_ids
1744
 
        missing_revs = set()
1745
 
        source_graph = self.source.get_graph()
1746
 
        # ensure we don't pay silly lookup costs.
1747
 
        searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
1748
 
        null_set = frozenset([_mod_revision.NULL_REVISION])
1749
 
        searcher_exhausted = False
1750
 
        while True:
1751
 
            next_revs = set()
1752
 
            ghosts = set()
1753
 
            # Iterate the searcher until we have enough next_revs
1754
 
            while len(next_revs) < self._walk_to_common_revisions_batch_size:
1755
 
                try:
1756
 
                    next_revs_part, ghosts_part = searcher.next_with_ghosts()
1757
 
                    next_revs.update(next_revs_part)
1758
 
                    ghosts.update(ghosts_part)
1759
 
                except StopIteration:
1760
 
                    searcher_exhausted = True
1761
 
                    break
1762
 
            # If there are ghosts in the source graph, and the caller asked for
1763
 
            # them, make sure that they are present in the target.
1764
 
            # We don't care about other ghosts as we can't fetch them and
1765
 
            # haven't been asked to.
1766
 
            ghosts_to_check = set(revision_ids.intersection(ghosts))
1767
 
            revs_to_get = set(next_revs).union(ghosts_to_check)
1768
 
            if revs_to_get:
1769
 
                have_revs = set(target_graph.get_parent_map(revs_to_get))
1770
 
                # we always have NULL_REVISION present.
1771
 
                have_revs = have_revs.union(null_set)
1772
 
                # Check if the target is missing any ghosts we need.
1773
 
                ghosts_to_check.difference_update(have_revs)
1774
 
                if ghosts_to_check:
1775
 
                    # One of the caller's revision_ids is a ghost in both the
1776
 
                    # source and the target.
1777
 
                    raise errors.NoSuchRevision(
1778
 
                        self.source, ghosts_to_check.pop())
1779
 
                missing_revs.update(next_revs - have_revs)
1780
 
                # Because we may have walked past the original stop point, make
1781
 
                # sure everything is stopped
1782
 
                stop_revs = searcher.find_seen_ancestors(have_revs)
1783
 
                searcher.stop_searching_any(stop_revs)
1784
 
            if searcher_exhausted:
1785
 
                break
1786
 
        return searcher.get_result()
 
1727
        raise NotImplementedError(self.fetch)
1787
1728
 
1788
1729
    @needs_read_lock
1789
1730
    def search_missing_revision_ids(self,
1790
1731
            revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1791
 
            find_ghosts=True, revision_ids=None, if_present_ids=None):
 
1732
            find_ghosts=True, revision_ids=None, if_present_ids=None,
 
1733
            limit=None):
1792
1734
        """Return the revision ids that source has that target does not.
1793
1735
 
1794
1736
        :param revision_id: only return revision ids included by this
1802
1744
            to fetch for tags, which may reference absent revisions.
1803
1745
        :param find_ghosts: If True find missing revisions in deep history
1804
1746
            rather than just finding the surface difference.
 
1747
        :param limit: Maximum number of revisions to return, topologically
 
1748
            ordered
1805
1749
        :return: A bzrlib.graph.SearchResult.
1806
1750
        """
1807
 
        if symbol_versioning.deprecated_passed(revision_id):
1808
 
            symbol_versioning.warn(
1809
 
                'search_missing_revision_ids(revision_id=...) was '
1810
 
                'deprecated in 2.4.  Use revision_ids=[...] instead.',
1811
 
                DeprecationWarning, stacklevel=2)
1812
 
            if revision_ids is not None:
1813
 
                raise AssertionError(
1814
 
                    'revision_ids is mutually exclusive with revision_id')
1815
 
            if revision_id is not None:
1816
 
                revision_ids = [revision_id]
1817
 
        del revision_id
1818
 
        # stop searching at found target revisions.
1819
 
        if not find_ghosts and (revision_ids is not None or if_present_ids is
1820
 
                not None):
1821
 
            return self._walk_to_common_revisions(revision_ids,
1822
 
                    if_present_ids=if_present_ids)
1823
 
        # generic, possibly worst case, slow code path.
1824
 
        target_ids = set(self.target.all_revision_ids())
1825
 
        source_ids = self._present_source_revisions_for(
1826
 
            revision_ids, if_present_ids)
1827
 
        result_set = set(source_ids).difference(target_ids)
1828
 
        return self.source.revision_ids_to_search_result(result_set)
1829
 
 
1830
 
    def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
1831
 
        """Returns set of all revisions in ancestry of revision_ids present in
1832
 
        the source repo.
1833
 
 
1834
 
        :param revision_ids: if None, all revisions in source are returned.
1835
 
        :param if_present_ids: like revision_ids, but if any/all of these are
1836
 
            absent no error is raised.
1837
 
        """
1838
 
        if revision_ids is not None or if_present_ids is not None:
1839
 
            # First, ensure all specified revisions exist.  Callers expect
1840
 
            # NoSuchRevision when they pass absent revision_ids here.
1841
 
            if revision_ids is None:
1842
 
                revision_ids = set()
1843
 
            if if_present_ids is None:
1844
 
                if_present_ids = set()
1845
 
            revision_ids = set(revision_ids)
1846
 
            if_present_ids = set(if_present_ids)
1847
 
            all_wanted_ids = revision_ids.union(if_present_ids)
1848
 
            graph = self.source.get_graph()
1849
 
            present_revs = set(graph.get_parent_map(all_wanted_ids))
1850
 
            missing = revision_ids.difference(present_revs)
1851
 
            if missing:
1852
 
                raise errors.NoSuchRevision(self.source, missing.pop())
1853
 
            found_ids = all_wanted_ids.intersection(present_revs)
1854
 
            source_ids = [rev_id for (rev_id, parents) in
1855
 
                          graph.iter_ancestry(found_ids)
1856
 
                          if rev_id != _mod_revision.NULL_REVISION
1857
 
                          and parents is not None]
1858
 
        else:
1859
 
            source_ids = self.source.all_revision_ids()
1860
 
        return set(source_ids)
 
1751
        raise NotImplementedError(self.search_missing_revision_ids)
1861
1752
 
1862
1753
    @staticmethod
1863
1754
    def _same_model(source, target):
1883
1774
            raise errors.IncompatibleRepositories(source, target,
1884
1775
                "different serializers")
1885
1776
 
1886
 
    @classmethod
1887
 
    def _get_repo_format_to_test(self):
1888
 
        return None
1889
 
 
1890
 
    @classmethod
1891
 
    def is_compatible(cls, source, target):
1892
 
        # The default implementation is compatible with everything
1893
 
        return True
1894
 
 
1895
 
 
1896
 
InterRepository.register_optimiser(InterRepository)
1897
 
 
1898
1777
 
1899
1778
class CopyConverter(object):
1900
1779
    """A repository conversion tool which just performs a copy of the content.
1922
1801
        # trigger an assertion if not such
1923
1802
        repo._format.get_format_string()
1924
1803
        self.repo_dir = repo.bzrdir
1925
 
        pb.update('Moving repository to repository.backup')
 
1804
        pb.update(gettext('Moving repository to repository.backup'))
1926
1805
        self.repo_dir.transport.move('repository', 'repository.backup')
1927
1806
        backup_transport =  self.repo_dir.transport.clone('repository.backup')
1928
1807
        repo._format.check_conversion_target(self.target_format)
1929
1808
        self.source_repo = repo._format.open(self.repo_dir,
1930
1809
            _found=True,
1931
1810
            _override_transport=backup_transport)
1932
 
        pb.update('Creating new repository')
 
1811
        pb.update(gettext('Creating new repository'))
1933
1812
        converted = self.target_format.initialize(self.repo_dir,
1934
1813
                                                  self.source_repo.is_shared())
1935
1814
        converted.lock_write()
1936
1815
        try:
1937
 
            pb.update('Copying content')
 
1816
            pb.update(gettext('Copying content'))
1938
1817
            self.source_repo.copy_content_into(converted)
1939
1818
        finally:
1940
1819
            converted.unlock()
1941
 
        pb.update('Deleting old repository content')
 
1820
        pb.update(gettext('Deleting old repository content'))
1942
1821
        self.repo_dir.transport.delete_tree('repository.backup')
1943
 
        ui.ui_factory.note('repository converted')
 
1822
        ui.ui_factory.note(gettext('repository converted'))
1944
1823
        pb.finished()
1945
1824
 
1946
1825
 
1970
1849
        it is encountered, history extension will stop.
1971
1850
    """
1972
1851
    start_revision = partial_history_cache[-1]
1973
 
    iterator = repo.iter_reverse_revision_history(start_revision)
 
1852
    graph = repo.get_graph()
 
1853
    iterator = graph.iter_lefthand_ancestry(start_revision,
 
1854
        (_mod_revision.NULL_REVISION,))
1974
1855
    try:
1975
 
        #skip the last revision in the list
 
1856
        # skip the last revision in the list
1976
1857
        iterator.next()
1977
1858
        while True:
1978
1859
            if (stop_index is not None and
2010
1891
        for list_part in self.list_parts:
2011
1892
            full_list.extend(list_part)
2012
1893
        return iter(full_list)
 
1894
 
 
1895
    def __repr__(self):
 
1896
        return "%s.%s(%s)" % (self.__module__, self.__class__.__name__,
 
1897
                              self.list_parts)