230
236
"""Get a delta against the basis inventory for ie."""
231
237
if ie.file_id not in basis_inv:
233
return (None, path, ie.file_id, ie)
239
result = (None, path, ie.file_id, ie)
240
self._basis_delta.append(result)
234
242
elif ie != basis_inv[ie.file_id]:
235
243
# common but altered
236
244
# TODO: avoid tis id2path call.
237
return (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
245
result = (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
246
self._basis_delta.append(result)
239
249
# common, unaltered
252
def get_basis_delta(self):
253
"""Return the complete inventory delta versus the basis inventory.
255
This has been built up with the calls to record_delete and
256
record_entry_contents. The client must have already called
257
will_record_deletes() to indicate that they will be generating a
260
:return: An inventory delta, suitable for use with apply_delta, or
261
Repository.add_inventory_by_delta, etc.
263
if not self._recording_deletes:
264
raise AssertionError("recording deletes not activated.")
265
return self._basis_delta
267
def record_delete(self, path, file_id):
268
"""Record that a delete occured against a basis tree.
270
This is an optional API - when used it adds items to the basis_delta
271
being accumulated by the commit builder. It cannot be called unless the
272
method will_record_deletes() has been called to inform the builder that
273
a delta is being supplied.
275
:param path: The path of the thing deleted.
276
:param file_id: The file id that was deleted.
278
if not self._recording_deletes:
279
raise AssertionError("recording deletes not activated.")
280
delta = (path, None, file_id, None)
281
self._basis_delta.append(delta)
284
def will_record_deletes(self):
285
"""Tell the commit builder that deletes are being notified.
287
This enables the accumulation of an inventory delta; for the resulting
288
commit to be valid, deletes against the basis MUST be recorded via
289
builder.record_delete().
291
self._recording_deletes = True
242
293
def record_entry_contents(self, ie, parent_invs, path, tree,
243
294
content_summary):
244
295
"""Record the content of ie from tree into the commit if needed.
296
347
if ie.revision is not None:
297
348
if not self._versioned_root and path == '':
298
349
# repositories that do not version the root set the root's
299
# revision to the new commit even when no change occurs, and
300
# this masks when a change may have occurred against the basis,
301
# so calculate if one happened.
350
# revision to the new commit even when no change occurs (more
351
# specifically, they do not record a revision on the root; and
352
# the rev id is assigned to the root during deserialisation -
353
# this masks when a change may have occurred against the basis.
354
# To match this we always issue a delta, because the revision
355
# of the root will always be changing.
302
356
if ie.file_id in basis_inv:
303
357
delta = (basis_inv.id2path(ie.file_id), path,
307
361
delta = (None, path, ie.file_id, ie)
362
self._basis_delta.append(delta)
308
363
return delta, False, None
310
365
# we don't need to commit this, because the caller already
615
670
return self._inventory_add_lines(revision_id, parents,
616
671
inv_lines, check_content=False)
673
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
675
"""Add a new inventory expressed as a delta against another revision.
677
:param basis_revision_id: The inventory id the delta was created
678
against. (This does not have to be a direct parent.)
679
:param delta: The inventory delta (see Inventory.apply_delta for
681
:param new_revision_id: The revision id that the inventory is being
683
:param parents: The revision ids of the parents that revision_id is
684
known to have and are in the repository already. These are supplied
685
for repositories that depend on the inventory graph for revision
686
graph access, as well as for those that pun ancestry with delta
689
:returns: (validator, new_inv)
690
The validator(which is a sha1 digest, though what is sha'd is
691
repository format specific) of the serialized inventory, and the
694
if not self.is_in_write_group():
695
raise AssertionError("%r not in write group" % (self,))
696
_mod_revision.check_not_reserved_id(new_revision_id)
697
basis_tree = self.revision_tree(basis_revision_id)
698
basis_tree.lock_read()
700
# Note that this mutates the inventory of basis_tree, which not all
701
# inventory implementations may support: A better idiom would be to
702
# return a new inventory, but as there is no revision tree cache in
703
# repository this is safe for now - RBC 20081013
704
basis_inv = basis_tree.inventory
705
basis_inv.apply_delta(delta)
706
basis_inv.revision_id = new_revision_id
707
return (self.add_inventory(new_revision_id, basis_inv, parents),
618
712
def _inventory_add_lines(self, revision_id, parents, lines,
619
713
check_content=True):
620
714
"""Store lines in inv_vf and return the sha1 of the inventory."""
3179
def _fetch_batch(self, revision_ids, basis_id, basis_tree):
3180
"""Fetch across a few revisions.
3182
:param revision_ids: The revisions to copy
3183
:param basis_id: The revision_id of basis_tree
3184
:param basis_tree: A tree that is not in revision_ids which should
3185
already exist in the target.
3186
:return: (basis_id, basis_tree) A new basis to use now that these trees
3189
# Walk though all revisions; get inventory deltas, copy referenced
3190
# texts that delta references, insert the delta, revision and
3194
pending_revisions = []
3195
for tree in self.source.revision_trees(revision_ids):
3196
current_revision_id = tree.get_revision_id()
3197
delta = tree.inventory._make_delta(basis_tree.inventory)
3198
for old_path, new_path, file_id, entry in delta:
3199
if new_path is not None:
3200
if not (new_path or self.target.supports_rich_root()):
3201
# We leave the inventory delta in, because that
3202
# will have the deserialised inventory root
3206
# "if entry.revision == current_revision_id" ?
3207
if entry.revision == current_revision_id:
3208
text_keys.add((file_id, entry.revision))
3209
revision = self.source.get_revision(current_revision_id)
3210
pending_deltas.append((basis_id, delta,
3211
current_revision_id, revision.parent_ids))
3212
pending_revisions.append(revision)
3213
basis_id = current_revision_id
3216
from_texts = self.source.texts
3217
to_texts = self.target.texts
3218
to_texts.insert_record_stream(from_texts.get_record_stream(
3219
text_keys, self.target._fetch_order,
3220
not self.target._fetch_uses_deltas))
3222
for delta in pending_deltas:
3223
self.target.add_inventory_by_delta(*delta)
3224
# insert signatures and revisions
3225
for revision in pending_revisions:
3227
signature = self.source.get_signature_text(
3228
revision.revision_id)
3229
self.target.add_signature_text(revision.revision_id,
3231
except errors.NoSuchRevision:
3233
self.target.add_revision(revision.revision_id, revision)
3234
return basis_id, basis_tree
3236
def _fetch_all_revisions(self, revision_ids, pb):
3237
"""Fetch everything for the list of revisions.
3239
:param revision_ids: The list of revisions to fetch. Must be in
3241
:param pb: A ProgressBar
3244
basis_id, basis_tree = self._get_basis(revision_ids[0])
3246
for offset in range(0, len(revision_ids), batch_size):
3247
self.target.start_write_group()
3249
pb.update('Transferring revisions', offset,
3251
batch = revision_ids[offset:offset+batch_size]
3252
basis_id, basis_tree = self._fetch_batch(batch,
3253
basis_id, basis_tree)
3255
self.target.abort_write_group()
3258
self.target.commit_write_group()
3259
pb.update('Transferring revisions', len(revision_ids),
3085
3262
@needs_write_lock
3086
3263
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3087
3264
"""See InterRepository.fetch()."""
3088
3265
revision_ids = self.target.search_missing_revision_ids(self.source,
3089
3266
revision_id, find_ghosts=find_ghosts).get_keys()
3267
if not revision_ids:
3090
3269
revision_ids = tsort.topo_sort(
3091
3270
self.source.get_graph().get_parent_map(revision_ids))
3092
def revisions_iterator():
3093
rev_ids = list(revision_ids)
3094
for offset in xrange(0, len(rev_ids), 100):
3095
current_revids = rev_ids[offset:offset+100]
3096
revisions = self.source.get_revisions(current_revids)
3097
trees = self.source.revision_trees(current_revids)
3098
keys = [(r,) for r in current_revids]
3099
sig_stream = self.source.signatures.get_record_stream(
3100
keys, 'unordered', True)
3102
for record in versionedfile.filter_absent(sig_stream):
3103
sigs[record.key[0]] = record.get_bytes_as('fulltext')
3104
for rev, tree in zip(revisions, trees):
3105
yield rev, tree, sigs.get(rev.revision_id, None)
3107
3272
my_pb = ui.ui_factory.nested_progress_bar()
3112
install_revisions(self.target, revisions_iterator(),
3113
len(revision_ids), pb)
3277
self._fetch_all_revisions(revision_ids, pb)
3115
3279
if my_pb is not None:
3116
3280
my_pb.finished()
3117
3281
return len(revision_ids), 0
3283
def _get_basis(self, first_revision_id):
3284
"""Get a revision and tree which exists in the target.
3286
This assumes that first_revision_id is selected for transmission
3287
because all other ancestors are already present. If we can't find an
3288
ancestor we fall back to NULL_REVISION since we know that is safe.
3290
:return: (basis_id, basis_tree)
3292
first_rev = self.source.get_revision(first_revision_id)
3294
basis_id = first_rev.parent_ids[0]
3295
# only valid as a basis if the target has it
3296
self.target.get_revision(basis_id)
3297
# Try to get a basis tree - if its a ghost it will hit the
3298
# NoSuchRevision case.
3299
basis_tree = self.source.revision_tree(basis_id)
3300
except (IndexError, errors.NoSuchRevision):
3301
basis_id = _mod_revision.NULL_REVISION
3302
basis_tree = self.source.revision_tree(basis_id)
3303
return basis_id, basis_tree
3120
3306
class InterOtherToRemote(InterRepository):
3121
3307
"""An InterRepository that simply delegates to the 'real' InterRepository