230
237
"""Get a delta against the basis inventory for ie."""
231
238
if ie.file_id not in basis_inv:
233
return (None, path, ie.file_id, ie)
240
result = (None, path, ie.file_id, ie)
241
self._basis_delta.append(result)
234
243
elif ie != basis_inv[ie.file_id]:
235
244
# common but altered
236
245
# TODO: avoid tis id2path call.
237
return (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
246
result = (basis_inv.id2path(ie.file_id), path, ie.file_id, ie)
247
self._basis_delta.append(result)
239
250
# common, unaltered
253
def get_basis_delta(self):
254
"""Return the complete inventory delta versus the basis inventory.
256
This has been built up with the calls to record_delete and
257
record_entry_contents. The client must have already called
258
will_record_deletes() to indicate that they will be generating a
261
:return: An inventory delta, suitable for use with apply_delta, or
262
Repository.add_inventory_by_delta, etc.
264
if not self._recording_deletes:
265
raise AssertionError("recording deletes not activated.")
266
return self._basis_delta
268
def record_delete(self, path, file_id):
269
"""Record that a delete occured against a basis tree.
271
This is an optional API - when used it adds items to the basis_delta
272
being accumulated by the commit builder. It cannot be called unless the
273
method will_record_deletes() has been called to inform the builder that
274
a delta is being supplied.
276
:param path: The path of the thing deleted.
277
:param file_id: The file id that was deleted.
279
if not self._recording_deletes:
280
raise AssertionError("recording deletes not activated.")
281
delta = (path, None, file_id, None)
282
self._basis_delta.append(delta)
285
def will_record_deletes(self):
286
"""Tell the commit builder that deletes are being notified.
288
This enables the accumulation of an inventory delta; for the resulting
289
commit to be valid, deletes against the basis MUST be recorded via
290
builder.record_delete().
292
self._recording_deletes = True
242
294
def record_entry_contents(self, ie, parent_invs, path, tree,
243
295
content_summary):
244
296
"""Record the content of ie from tree into the commit if needed.
296
348
if ie.revision is not None:
297
349
if not self._versioned_root and path == '':
298
350
# 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.
351
# revision to the new commit even when no change occurs (more
352
# specifically, they do not record a revision on the root; and
353
# the rev id is assigned to the root during deserialisation -
354
# this masks when a change may have occurred against the basis.
355
# To match this we always issue a delta, because the revision
356
# of the root will always be changing.
302
357
if ie.file_id in basis_inv:
303
358
delta = (basis_inv.id2path(ie.file_id), path,
307
362
delta = (None, path, ie.file_id, ie)
363
self._basis_delta.append(delta)
308
364
return delta, False, None
310
366
# we don't need to commit this, because the caller already
615
671
return self._inventory_add_lines(revision_id, parents,
616
672
inv_lines, check_content=False)
674
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
676
"""Add a new inventory expressed as a delta against another revision.
678
:param basis_revision_id: The inventory id the delta was created
679
against. (This does not have to be a direct parent.)
680
:param delta: The inventory delta (see Inventory.apply_delta for
682
:param new_revision_id: The revision id that the inventory is being
684
:param parents: The revision ids of the parents that revision_id is
685
known to have and are in the repository already. These are supplied
686
for repositories that depend on the inventory graph for revision
687
graph access, as well as for those that pun ancestry with delta
690
:returns: (validator, new_inv)
691
The validator(which is a sha1 digest, though what is sha'd is
692
repository format specific) of the serialized inventory, and the
695
if not self.is_in_write_group():
696
raise AssertionError("%r not in write group" % (self,))
697
_mod_revision.check_not_reserved_id(new_revision_id)
698
basis_tree = self.revision_tree(basis_revision_id)
699
basis_tree.lock_read()
701
# Note that this mutates the inventory of basis_tree, which not all
702
# inventory implementations may support: A better idiom would be to
703
# return a new inventory, but as there is no revision tree cache in
704
# repository this is safe for now - RBC 20081013
705
basis_inv = basis_tree.inventory
706
basis_inv.apply_delta(delta)
707
basis_inv.revision_id = new_revision_id
708
return (self.add_inventory(new_revision_id, basis_inv, parents),
618
713
def _inventory_add_lines(self, revision_id, parents, lines,
619
714
check_content=True):
620
715
"""Store lines in inv_vf and return the sha1 of the inventory."""
3185
def _fetch_batch(self, revision_ids, basis_id, basis_tree):
3186
"""Fetch across a few revisions.
3188
:param revision_ids: The revisions to copy
3189
:param basis_id: The revision_id of basis_tree
3190
:param basis_tree: A tree that is not in revision_ids which should
3191
already exist in the target.
3192
:return: (basis_id, basis_tree) A new basis to use now that these trees
3195
# Walk though all revisions; get inventory deltas, copy referenced
3196
# texts that delta references, insert the delta, revision and
3200
pending_revisions = []
3201
for tree in self.source.revision_trees(revision_ids):
3202
current_revision_id = tree.get_revision_id()
3203
delta = tree.inventory._make_delta(basis_tree.inventory)
3204
for old_path, new_path, file_id, entry in delta:
3205
if new_path is not None:
3206
if not (new_path or self.target.supports_rich_root()):
3207
# We leave the inventory delta in, because that
3208
# will have the deserialised inventory root
3212
# "if entry.revision == current_revision_id" ?
3213
if entry.revision == current_revision_id:
3214
text_keys.add((file_id, entry.revision))
3215
revision = self.source.get_revision(current_revision_id)
3216
pending_deltas.append((basis_id, delta,
3217
current_revision_id, revision.parent_ids))
3218
pending_revisions.append(revision)
3219
basis_id = current_revision_id
3222
from_texts = self.source.texts
3223
to_texts = self.target.texts
3224
to_texts.insert_record_stream(from_texts.get_record_stream(
3225
text_keys, self.target._fetch_order,
3226
not self.target._fetch_uses_deltas))
3228
for delta in pending_deltas:
3229
self.target.add_inventory_by_delta(*delta)
3230
# insert signatures and revisions
3231
for revision in pending_revisions:
3233
signature = self.source.get_signature_text(
3234
revision.revision_id)
3235
self.target.add_signature_text(revision.revision_id,
3237
except errors.NoSuchRevision:
3239
self.target.add_revision(revision.revision_id, revision)
3240
return basis_id, basis_tree
3242
def _fetch_all_revisions(self, revision_ids, pb):
3243
"""Fetch everything for the list of revisions.
3245
:param revision_ids: The list of revisions to fetch. Must be in
3247
:param pb: A ProgressBar
3250
basis_id, basis_tree = self._get_basis(revision_ids[0])
3252
for offset in range(0, len(revision_ids), batch_size):
3253
self.target.start_write_group()
3255
pb.update('Transferring revisions', offset,
3257
batch = revision_ids[offset:offset+batch_size]
3258
basis_id, basis_tree = self._fetch_batch(batch,
3259
basis_id, basis_tree)
3261
self.target.abort_write_group()
3264
self.target.commit_write_group()
3265
pb.update('Transferring revisions', len(revision_ids),
3080
3268
@needs_write_lock
3081
3269
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3082
3270
"""See InterRepository.fetch()."""
3083
3271
revision_ids = self.target.search_missing_revision_ids(self.source,
3084
3272
revision_id, find_ghosts=find_ghosts).get_keys()
3273
if not revision_ids:
3085
3275
revision_ids = tsort.topo_sort(
3086
3276
self.source.get_graph().get_parent_map(revision_ids))
3087
def revisions_iterator():
3088
rev_ids = list(revision_ids)
3089
for offset in xrange(0, len(rev_ids), 100):
3090
current_revids = rev_ids[offset:offset+100]
3091
revisions = self.source.get_revisions(current_revids)
3092
trees = self.source.revision_trees(current_revids)
3093
keys = [(r,) for r in current_revids]
3094
sig_stream = self.source.signatures.get_record_stream(
3095
keys, 'unordered', True)
3097
for record in versionedfile.filter_absent(sig_stream):
3098
sigs[record.key[0]] = record.get_bytes_as('fulltext')
3099
for rev, tree in zip(revisions, trees):
3100
yield rev, tree, sigs.get(rev.revision_id, None)
3102
3278
my_pb = ui.ui_factory.nested_progress_bar()
3107
install_revisions(self.target, revisions_iterator(),
3108
len(revision_ids), pb)
3283
self._fetch_all_revisions(revision_ids, pb)
3110
3285
if my_pb is not None:
3111
3286
my_pb.finished()
3112
3287
return len(revision_ids), 0
3289
def _get_basis(self, first_revision_id):
3290
"""Get a revision and tree which exists in the target.
3292
This assumes that first_revision_id is selected for transmission
3293
because all other ancestors are already present. If we can't find an
3294
ancestor we fall back to NULL_REVISION since we know that is safe.
3296
:return: (basis_id, basis_tree)
3298
first_rev = self.source.get_revision(first_revision_id)
3300
basis_id = first_rev.parent_ids[0]
3301
# only valid as a basis if the target has it
3302
self.target.get_revision(basis_id)
3303
# Try to get a basis tree - if its a ghost it will hit the
3304
# NoSuchRevision case.
3305
basis_tree = self.source.revision_tree(basis_id)
3306
except (IndexError, errors.NoSuchRevision):
3307
basis_id = _mod_revision.NULL_REVISION
3308
basis_tree = self.source.revision_tree(basis_id)
3309
return basis_id, basis_tree
3115
3312
class InterOtherToRemote(InterRepository):
3116
3313
"""An InterRepository that simply delegates to the 'real' InterRepository