1039
1021
self.inventories._access.flush()
1042
def add_revision(self, revision_id, rev, inv=None):
1024
def add_revision(self, revision_id, rev, inv=None, config=None):
1043
1025
"""Add rev to the revision store as revision_id.
1045
1027
:param revision_id: the revision id to use.
1046
1028
:param rev: The revision object.
1047
1029
:param inv: The inventory for the revision. if None, it will be looked
1048
1030
up in the inventory storer
1031
:param config: If None no digital signature will be created.
1032
If supplied its signature_needed method will be used
1033
to determine if a signature should be made.
1050
1035
# TODO: jam 20070210 Shouldn't we check rev.revision_id and
1051
1036
# rev.parent_ids?
1052
1037
_mod_revision.check_not_reserved_id(revision_id)
1038
if config is not None and config.signature_needed():
1040
inv = self.get_inventory(revision_id)
1041
tree = InventoryRevisionTree(self, inv, revision_id)
1042
testament = Testament(rev, tree)
1043
plaintext = testament.as_short_text()
1044
self.store_revision_signature(
1045
gpg.GPGStrategy(config), plaintext, revision_id)
1053
1046
# check inventory present
1054
1047
if not self.inventories.get_parent_map([(revision_id,)]):
1055
1048
if inv is None:
1219
1198
# rather copying them?
1220
1199
self._safe_to_return_from_cache = False
1222
def fetch(self, source, revision_id=None, find_ghosts=False,
1224
"""Fetch the content required to construct revision_id from source.
1226
If revision_id is None and fetch_spec is None, then all content is
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.
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
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
1258
if (revision_id is not None and
1259
not _mod_revision.is_null(revision_id)):
1260
self.get_revision(revision_id)
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)
1270
1201
@needs_read_lock
1271
1202
def gather_stats(self, revid=None, committers=None):
1272
1203
"""See Repository.gather_stats()."""
2551
2464
return wrong_parents, unused_keys
2554
class InterVersionedFileRepository(InterRepository):
2556
_walk_to_common_revisions_batch_size = 50
2558
supports_fetch_spec = True
2561
def fetch(self, revision_id=None, find_ghosts=False,
2563
"""Fetch the content required to construct revision_id.
2565
The content is copied from self.source to self.target.
2567
:param revision_id: if None all content is copied, if NULL_REVISION no
2571
if self.target._format.experimental:
2572
ui.ui_factory.show_user_warning('experimental_format_fetch',
2573
from_format=self.source._format,
2574
to_format=self.target._format)
2575
from bzrlib.fetch import RepoFetcher
2576
# See <https://launchpad.net/bugs/456077> asking for a warning here
2577
if self.source._format.network_name() != self.target._format.network_name():
2578
ui.ui_factory.show_user_warning('cross_format_fetch',
2579
from_format=self.source._format,
2580
to_format=self.target._format)
2581
f = RepoFetcher(to_repository=self.target,
2582
from_repository=self.source,
2583
last_revision=revision_id,
2584
fetch_spec=fetch_spec,
2585
find_ghosts=find_ghosts)
2587
def _walk_to_common_revisions(self, revision_ids, if_present_ids=None):
2588
"""Walk out from revision_ids in source to revisions target has.
2590
:param revision_ids: The start point for the search.
2591
:return: A set of revision ids.
2593
target_graph = self.target.get_graph()
2594
revision_ids = frozenset(revision_ids)
2596
all_wanted_revs = revision_ids.union(if_present_ids)
2598
all_wanted_revs = revision_ids
2599
missing_revs = set()
2600
source_graph = self.source.get_graph()
2601
# ensure we don't pay silly lookup costs.
2602
searcher = source_graph._make_breadth_first_searcher(all_wanted_revs)
2603
null_set = frozenset([_mod_revision.NULL_REVISION])
2604
searcher_exhausted = False
2608
# Iterate the searcher until we have enough next_revs
2609
while len(next_revs) < self._walk_to_common_revisions_batch_size:
2611
next_revs_part, ghosts_part = searcher.next_with_ghosts()
2612
next_revs.update(next_revs_part)
2613
ghosts.update(ghosts_part)
2614
except StopIteration:
2615
searcher_exhausted = True
2617
# If there are ghosts in the source graph, and the caller asked for
2618
# them, make sure that they are present in the target.
2619
# We don't care about other ghosts as we can't fetch them and
2620
# haven't been asked to.
2621
ghosts_to_check = set(revision_ids.intersection(ghosts))
2622
revs_to_get = set(next_revs).union(ghosts_to_check)
2624
have_revs = set(target_graph.get_parent_map(revs_to_get))
2625
# we always have NULL_REVISION present.
2626
have_revs = have_revs.union(null_set)
2627
# Check if the target is missing any ghosts we need.
2628
ghosts_to_check.difference_update(have_revs)
2630
# One of the caller's revision_ids is a ghost in both the
2631
# source and the target.
2632
raise errors.NoSuchRevision(
2633
self.source, ghosts_to_check.pop())
2634
missing_revs.update(next_revs - have_revs)
2635
# Because we may have walked past the original stop point, make
2636
# sure everything is stopped
2637
stop_revs = searcher.find_seen_ancestors(have_revs)
2638
searcher.stop_searching_any(stop_revs)
2639
if searcher_exhausted:
2641
(started_keys, excludes, included_keys) = searcher.get_state()
2642
return vf_search.SearchResult(started_keys, excludes,
2643
len(included_keys), included_keys)
2646
def search_missing_revision_ids(self,
2647
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2648
find_ghosts=True, revision_ids=None, if_present_ids=None,
2650
"""Return the revision ids that source has that target does not.
2652
:param revision_id: only return revision ids included by this
2654
:param revision_ids: return revision ids included by these
2655
revision_ids. NoSuchRevision will be raised if any of these
2656
revisions are not present.
2657
:param if_present_ids: like revision_ids, but will not cause
2658
NoSuchRevision if any of these are absent, instead they will simply
2659
not be in the result. This is useful for e.g. finding revisions
2660
to fetch for tags, which may reference absent revisions.
2661
:param find_ghosts: If True find missing revisions in deep history
2662
rather than just finding the surface difference.
2663
:return: A bzrlib.graph.SearchResult.
2665
if symbol_versioning.deprecated_passed(revision_id):
2666
symbol_versioning.warn(
2667
'search_missing_revision_ids(revision_id=...) was '
2668
'deprecated in 2.4. Use revision_ids=[...] instead.',
2669
DeprecationWarning, stacklevel=2)
2670
if revision_ids is not None:
2671
raise AssertionError(
2672
'revision_ids is mutually exclusive with revision_id')
2673
if revision_id is not None:
2674
revision_ids = [revision_id]
2676
# stop searching at found target revisions.
2677
if not find_ghosts and (revision_ids is not None or if_present_ids is
2679
result = self._walk_to_common_revisions(revision_ids,
2680
if_present_ids=if_present_ids)
2683
result_set = result.get_keys()
2685
# generic, possibly worst case, slow code path.
2686
target_ids = set(self.target.all_revision_ids())
2687
source_ids = self._present_source_revisions_for(
2688
revision_ids, if_present_ids)
2689
result_set = set(source_ids).difference(target_ids)
2690
if limit is not None:
2691
topo_ordered = self.source.get_graph().iter_topo_order(result_set)
2692
result_set = set(itertools.islice(topo_ordered, limit))
2693
return self.source.revision_ids_to_search_result(result_set)
2695
def _present_source_revisions_for(self, revision_ids, if_present_ids=None):
2696
"""Returns set of all revisions in ancestry of revision_ids present in
2699
:param revision_ids: if None, all revisions in source are returned.
2700
:param if_present_ids: like revision_ids, but if any/all of these are
2701
absent no error is raised.
2703
if revision_ids is not None or if_present_ids is not None:
2704
# First, ensure all specified revisions exist. Callers expect
2705
# NoSuchRevision when they pass absent revision_ids here.
2706
if revision_ids is None:
2707
revision_ids = set()
2708
if if_present_ids is None:
2709
if_present_ids = set()
2710
revision_ids = set(revision_ids)
2711
if_present_ids = set(if_present_ids)
2712
all_wanted_ids = revision_ids.union(if_present_ids)
2713
graph = self.source.get_graph()
2714
present_revs = set(graph.get_parent_map(all_wanted_ids))
2715
missing = revision_ids.difference(present_revs)
2717
raise errors.NoSuchRevision(self.source, missing.pop())
2718
found_ids = all_wanted_ids.intersection(present_revs)
2719
source_ids = [rev_id for (rev_id, parents) in
2720
graph.iter_ancestry(found_ids)
2721
if rev_id != _mod_revision.NULL_REVISION
2722
and parents is not None]
2724
source_ids = self.source.all_revision_ids()
2725
return set(source_ids)
2728
def _get_repo_format_to_test(self):
2732
def is_compatible(cls, source, target):
2733
# The default implementation is compatible with everything
2734
return (source._format.supports_full_versioned_files and
2735
target._format.supports_full_versioned_files)
2738
class InterDifferingSerializer(InterVersionedFileRepository):
2467
class InterDifferingSerializer(InterRepository):
2741
2470
def _get_repo_format_to_test(self):