109
103
# the default CommitBuilder does not manage trees whose root is versioned.
110
104
_versioned_root = False
112
def __init__(self, repository, parents, config_stack, timestamp=None,
106
def __init__(self, repository, parents, config, timestamp=None,
113
107
timezone=None, committer=None, revprops=None,
114
108
revision_id=None, lossy=False):
115
109
super(VersionedFileCommitBuilder, self).__init__(repository,
116
parents, config_stack, timestamp, timezone, committer, revprops,
110
parents, config, timestamp, timezone, committer, revprops,
117
111
revision_id, lossy)
119
113
basis_id = self.parents[0]
200
194
revision_id=self._new_revision_id,
201
195
properties=self._revprops)
202
196
rev.parent_ids = self.parents
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)
197
self.repository.add_revision(self._new_revision_id, rev,
198
self.new_inventory, self._config)
210
199
self._ensure_fallback_inventories()
211
200
self.repository.commit_write_group()
212
201
return self._new_revision_id
430
419
return None, False, None
431
420
# XXX: Friction: parent_candidates should return a list not a dict
432
421
# so that we don't have to walk the inventories again.
433
parent_candidate_entries = ie.parent_candidates(parent_invs)
434
head_set = self._heads(ie.file_id, parent_candidate_entries.keys())
422
parent_candiate_entries = ie.parent_candidates(parent_invs)
423
head_set = self._heads(ie.file_id, parent_candiate_entries.keys())
436
425
for inv in parent_invs:
437
426
if inv.has_id(ie.file_id):
454
443
# There is a single head, look it up for comparison
455
parent_entry = parent_candidate_entries[heads[0]]
444
parent_entry = parent_candiate_entries[heads[0]]
456
445
# if the non-content specific data has changed, we'll be writing a
458
447
if (parent_entry.parent_id != ie.parent_id or
570
559
:param iter_changes: An iter_changes iterator with the changes to apply
571
560
to basis_revision_id. The iterator must not include any items with
572
561
a current kind of None - missing items must be either filtered out
573
or errored-on before record_iter_changes sees the item.
562
or errored-on beefore record_iter_changes sees the item.
574
563
:param _entry_factory: Private method to bind entry_factory locally for
576
565
:return: A generator of (file_id, relpath, fs_hash) tuples for use with
604
593
_mod_revision.NULL_REVISION))
605
594
# The basis inventory from a repository
607
basis_tree = revtrees[0]
596
basis_inv = revtrees[0].inventory
609
basis_tree = self.repository.revision_tree(
610
_mod_revision.NULL_REVISION)
611
basis_inv = basis_tree.root_inventory
598
basis_inv = self.repository.revision_tree(
599
_mod_revision.NULL_REVISION).inventory
612
600
if len(self.parents) > 0:
613
601
if basis_revision_id != self.parents[0] and not ghost_basis:
615
603
"arbitrary basis parents not yet supported with merges")
616
604
for revtree in revtrees[1:]:
617
for change in revtree.root_inventory._make_delta(basis_inv):
605
for change in revtree.inventory._make_delta(basis_inv):
618
606
if change[1] is None:
619
607
# Not present in this parent.
931
919
if not self._format.supports_external_lookups:
932
920
raise errors.UnstackableRepositoryFormat(self._format, self.base)
933
# This can raise an exception, so should be done before we lock the
934
# fallback repository.
935
self._check_fallback_repository(repository)
936
921
if self.is_locked():
937
922
# This repository will call fallback.unlock() when we transition to
938
923
# the unlocked state, so we make sure to increment the lock count
939
924
repository.lock_read()
925
self._check_fallback_repository(repository)
940
926
self._fallback_repositories.append(repository)
941
927
self.texts.add_fallback_versioned_files(repository.texts)
942
928
self.inventories.add_fallback_versioned_files(repository.inventories)
1022
1008
# return a new inventory, but as there is no revision tree cache in
1023
1009
# repository this is safe for now - RBC 20081013
1024
1010
if basis_inv is None:
1025
basis_inv = basis_tree.root_inventory
1011
basis_inv = basis_tree.inventory
1026
1012
basis_inv.apply_delta(delta)
1027
1013
basis_inv.revision_id = new_revision_id
1028
1014
return (self.add_inventory(new_revision_id, basis_inv, parents),
1039
1025
self.inventories._access.flush()
1042
def add_revision(self, revision_id, rev, inv=None):
1028
def add_revision(self, revision_id, rev, inv=None, config=None):
1043
1029
"""Add rev to the revision store as revision_id.
1045
1031
:param revision_id: the revision id to use.
1046
1032
:param rev: The revision object.
1047
1033
:param inv: The inventory for the revision. if None, it will be looked
1048
1034
up in the inventory storer
1035
:param config: If None no digital signature will be created.
1036
If supplied its signature_needed method will be used
1037
to determine if a signature should be made.
1050
1039
# TODO: jam 20070210 Shouldn't we check rev.revision_id and
1051
1040
# rev.parent_ids?
1052
1041
_mod_revision.check_not_reserved_id(revision_id)
1042
if config is not None and config.signature_needed():
1044
inv = self.get_inventory(revision_id)
1045
tree = InventoryRevisionTree(self, inv, revision_id)
1046
testament = Testament(rev, tree)
1047
plaintext = testament.as_short_text()
1048
self.store_revision_signature(
1049
gpg.GPGStrategy(config), plaintext, revision_id)
1053
1050
# check inventory present
1054
1051
if not self.inventories.get_parent_map([(revision_id,)]):
1055
1052
if inv is None:
1088
1085
keys = {'chk_bytes':set(), 'inventories':set(), 'texts':set()}
1089
1086
kinds = ['chk_bytes', 'texts']
1090
1087
count = len(checker.pending_keys)
1091
bar.update(gettext("inventories"), 0, 2)
1088
bar.update("inventories", 0, 2)
1092
1089
current_keys = checker.pending_keys
1093
1090
checker.pending_keys = {}
1094
1091
# Accumulate current checks.
1183
1180
'sha1 mismatch: %s has sha1 %s expected %s referenced by %s' %
1184
1181
(record.key, sha1, item_data[1], item_data[2]))
1187
def _eliminate_revisions_not_present(self, revision_ids):
1188
"""Check every revision id in revision_ids to see if we have it.
1190
Returns a set of the present revisions.
1193
graph = self.get_graph()
1194
parent_map = graph.get_parent_map(revision_ids)
1195
# The old API returned a list, should this actually be a set?
1196
return parent_map.keys()
1198
1183
def __init__(self, _format, a_bzrdir, control_files):
1199
1184
"""Instantiate a VersionedFileRepository.
1201
1186
:param _format: The format of the repository on disk.
1202
:param controldir: The ControlDir of the repository.
1187
:param a_bzrdir: The BzrDir of the repository.
1203
1188
:param control_files: Control files to use for locking, etc.
1205
1190
# In the future we will have a single api for all stores for
1207
1192
# this construct will accept instances of those things.
1208
1193
super(VersionedFileRepository, self).__init__(_format, a_bzrdir,
1210
self._transport = control_files._transport
1211
self.base = self._transport.base
1213
1196
self._reconcile_does_inventory_gc = True
1214
1197
self._reconcile_fixes_text_parents = False
1219
1202
# rather copying them?
1220
1203
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
1205
@needs_read_lock
1271
1206
def gather_stats(self, revid=None, committers=None):
1272
1207
"""See Repository.gather_stats()."""
1281
1216
# result['size'] = t
1284
def get_commit_builder(self, branch, parents, config_stack, timestamp=None,
1219
def get_commit_builder(self, branch, parents, config, timestamp=None,
1285
1220
timezone=None, committer=None, revprops=None,
1286
1221
revision_id=None, lossy=False):
1287
1222
"""Obtain a CommitBuilder for this repository.
1289
1224
:param branch: Branch to commit to.
1290
1225
:param parents: Revision ids of the parents of the new revision.
1291
:param config_stack: Configuration stack to use.
1226
:param config: Configuration to use.
1292
1227
:param timestamp: Optional timestamp recorded for commit.
1293
1228
:param timezone: Optional timezone for timestamp.
1294
1229
:param committer: Optional committer to set for commit.
1301
1236
raise errors.BzrError("Cannot commit directly to a stacked branch"
1302
1237
" in pre-2a formats. See "
1303
1238
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1304
result = self._commit_builder_class(self, parents, config_stack,
1239
result = self._commit_builder_class(self, parents, config,
1305
1240
timestamp, timezone, committer, revprops, revision_id,
1307
1242
self.start_write_group()
1563
1498
text_keys[(file_id, revision_id)] = callable_data
1564
1499
for record in self.texts.get_record_stream(text_keys, 'unordered', True):
1565
1500
if record.storage_kind == 'absent':
1566
raise errors.RevisionNotPresent(record.key[1], record.key[0])
1501
raise errors.RevisionNotPresent(record.key, self)
1567
1502
yield text_keys[record.key], record.get_bytes_as('chunked')
1569
1504
def _generate_text_key_index(self, text_key_references=None,
1619
1554
batch_size = 10 # should be ~150MB on a 55K path tree
1620
1555
batch_count = len(revision_order) / batch_size + 1
1621
1556
processed_texts = 0
1622
pb.update(gettext("Calculating text parents"), processed_texts, text_count)
1557
pb.update("Calculating text parents", processed_texts, text_count)
1623
1558
for offset in xrange(batch_count):
1624
1559
to_query = revision_order[offset * batch_size:(offset + 1) *
1628
1563
for revision_id in to_query:
1629
1564
parent_ids = ancestors[revision_id]
1630
1565
for text_key in revision_keys[revision_id]:
1631
pb.update(gettext("Calculating text parents"), processed_texts)
1566
pb.update("Calculating text parents", processed_texts)
1632
1567
processed_texts += 1
1633
1568
candidate_parents = []
1634
1569
for parent_id in parent_ids:
1704
1639
num_file_ids = len(file_ids)
1705
1640
for file_id, altered_versions in file_ids.iteritems():
1706
1641
if pb is not None:
1707
pb.update(gettext("Fetch texts"), count, num_file_ids)
1642
pb.update("Fetch texts", count, num_file_ids)
1709
1644
yield ("file", file_id, altered_versions)
1747
1682
if ((None in revision_ids)
1748
1683
or (_mod_revision.NULL_REVISION in revision_ids)):
1749
1684
raise ValueError('cannot get null revision inventory')
1750
for inv, revid in self._iter_inventories(revision_ids, ordering):
1752
raise errors.NoSuchRevision(self, revid)
1685
return self._iter_inventories(revision_ids, ordering)
1755
1687
def _iter_inventories(self, revision_ids, ordering):
1756
1688
"""single-document based inventory iteration."""
1757
1689
inv_xmls = self._iter_inventory_xmls(revision_ids, ordering)
1758
1690
for text, revision_id in inv_xmls:
1760
yield None, revision_id
1762
yield self._deserialise_inventory(revision_id, text), revision_id
1691
yield self._deserialise_inventory(revision_id, text)
1764
1693
def _iter_inventory_xmls(self, revision_ids, ordering):
1765
1694
if ordering is None:
1784
1713
yield ''.join(chunks), record.key[-1]
1786
yield None, record.key[-1]
1715
raise errors.NoSuchRevision(self, record.key)
1787
1716
if order_as_requested:
1788
1717
# Yield as many results as we can while preserving order.
1789
1718
while next_key in text_chunks:
1818
1747
def _get_inventory_xml(self, revision_id):
1819
1748
"""Get serialized inventory as a string."""
1820
1749
texts = self._iter_inventory_xmls([revision_id], 'unordered')
1821
text, revision_id = texts.next()
1823
raise errors.NoSuchRevision(self, revision_id)
1751
text, revision_id = texts.next()
1752
except StopIteration:
1753
raise errors.HistoryMissing(self, 'inventory', revision_id)
1826
1756
@needs_read_lock
1901
1831
"""Return the graph walker for text revisions."""
1902
1832
return graph.Graph(self.texts)
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)
1917
1834
def _get_versioned_file_checker(self, text_key_references=None,
1918
1835
ancestors=None):
1919
1836
"""Return an object suitable for checking versioned files.
2442
2359
invs_sent_so_far = set([_mod_revision.NULL_REVISION])
2443
2360
inventory_cache = lru_cache.LRUCache(50)
2444
2361
null_inventory = from_repo.revision_tree(
2445
_mod_revision.NULL_REVISION).root_inventory
2362
_mod_revision.NULL_REVISION).inventory
2446
2363
# XXX: ideally the rich-root/tree-refs flags would be per-revision, not
2447
2364
# per-repo (e.g. streaming a non-rich-root revision out of a rich-root
2448
2365
# repo back into a non-rich-root repo ought to be allowed)
2533
2450
self.text_index.iterkeys()])
2534
2451
# text keys is now grouped by file_id
2535
2452
n_versions = len(self.text_index)
2536
progress_bar.update(gettext('loading text store'), 0, n_versions)
2453
progress_bar.update('loading text store', 0, n_versions)
2537
2454
parent_map = self.repository.texts.get_parent_map(self.text_index)
2538
2455
# On unlistable transports this could well be empty/error...
2539
2456
text_keys = self.repository.texts.keys()
2540
2457
unused_keys = frozenset(text_keys) - set(self.text_index)
2541
2458
for num, key in enumerate(self.text_index.iterkeys()):
2542
progress_bar.update(gettext('checking text graph'), num, n_versions)
2459
progress_bar.update('checking text graph', num, n_versions)
2543
2460
correct_parents = self.calculate_file_version_parents(key)
2545
2462
knit_parents = parent_map[key]
2568
2483
content is copied.
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)
2486
ui.ui_factory.warn_experimental_format_fetch(self)
2575
2487
from bzrlib.fetch import RepoFetcher
2576
2488
# See <https://launchpad.net/bugs/456077> asking for a warning here
2577
2489
if self.source._format.network_name() != self.target._format.network_name():
2638
2550
searcher.stop_searching_any(stop_revs)
2639
2551
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)
2553
return searcher.get_result()
2645
2555
@needs_read_lock
2646
2556
def search_missing_revision_ids(self,
2797
2707
# Generate deltas against each tree, to find the shortest.
2798
# FIXME: Support nested trees
2799
2708
texts_possibly_new_in_tree = set()
2800
2709
for basis_id, basis_tree in possible_trees:
2801
delta = tree.root_inventory._make_delta(basis_tree.root_inventory)
2710
delta = tree.inventory._make_delta(basis_tree.inventory)
2802
2711
for old_path, new_path, file_id, new_entry in delta:
2803
2712
if new_path is None:
2804
2713
# This file_id isn't present in the new rev, so we don't
2841
2750
parents_parents = [key[-1] for key in parents_parents_keys]
2842
2751
basis_id = _mod_revision.NULL_REVISION
2843
2752
basis_tree = self.source.revision_tree(basis_id)
2844
delta = parent_tree.root_inventory._make_delta(
2845
basis_tree.root_inventory)
2753
delta = parent_tree.inventory._make_delta(basis_tree.inventory)
2846
2754
self.target.add_inventory_by_delta(
2847
2755
basis_id, delta, current_revision_id, parents_parents)
2848
2756
cache[current_revision_id] = parent_tree
2907
2815
kind = entry.kind
2908
2816
texts_possibly_new_in_tree.add((file_id, entry.revision))
2909
2817
for basis_id, basis_tree in possible_trees:
2910
basis_inv = basis_tree.root_inventory
2818
basis_inv = basis_tree.inventory
2911
2819
for file_key in list(texts_possibly_new_in_tree):
2912
2820
file_id, file_revision = file_key
2995
2903
for offset in range(0, len(revision_ids), batch_size):
2996
2904
self.target.start_write_group()
2998
pb.update(gettext('Transferring revisions'), offset,
2906
pb.update('Transferring revisions', offset,
2999
2907
len(revision_ids))
3000
2908
batch = revision_ids[offset:offset+batch_size]
3001
2909
basis_id = self._fetch_batch(batch, basis_id, cache)
3020
2928
revision_ids = fetch_spec.get_keys()
3022
2930
revision_ids = None
3023
if self.source._format.experimental:
3024
ui.ui_factory.show_user_warning('experimental_format_fetch',
3025
from_format=self.source._format,
3026
to_format=self.target._format)
2931
ui.ui_factory.warn_experimental_format_fetch(self)
3027
2932
if (not self.source.supports_rich_root()
3028
2933
and self.target.supports_rich_root()):
3029
2934
self._converting_to_rich_root = True
3124
3029
_install_revision(repository, revision, revision_tree, signature,
3125
3030
inventory_cache)
3126
3031
if pb is not None:
3127
pb.update(gettext('Transferring revisions'), n + 1, num_revisions)
3032
pb.update('Transferring revisions', n + 1, num_revisions)
3129
3034
repository.abort_write_group()
3145
3050
parent_trees[p_id] = repository.revision_tree(
3146
3051
_mod_revision.NULL_REVISION)
3148
# FIXME: Support nested trees
3149
inv = revision_tree.root_inventory
3053
inv = revision_tree.inventory
3150
3054
entries = inv.iter_entries()
3151
3055
# backwards compatibility hack: skip the root id.
3152
3056
if not repository.supports_rich_root():