118
121
self._generate_revision_if_needed()
119
122
self.__heads = graph.HeadsCache(repository.get_graph()).heads
123
self._basis_delta = []
124
# API compatibility, older code that used CommitBuilder did not call
125
# .record_delete(), which means the delta that is computed would not be
126
# valid. Callers that will call record_delete() should call
127
# .will_record_deletes() to indicate that.
128
self._recording_deletes = False
130
def _validate_unicode_text(self, text, context):
131
"""Verify things like commit messages don't have bogus characters."""
133
raise ValueError('Invalid value for %s: %r' % (context, text))
135
def _validate_revprops(self, revprops):
136
for key, value in revprops.iteritems():
137
# We know that the XML serializers do not round trip '\r'
138
# correctly, so refuse to accept them
139
if not isinstance(value, basestring):
140
raise ValueError('revision property (%s) is not a valid'
141
' (unicode) string: %r' % (key, value))
142
self._validate_unicode_text(value,
143
'revision property (%s)' % (key,))
121
145
def commit(self, message):
122
146
"""Make the actual commit.
124
148
:return: The revision id of the recorded revision.
150
self._validate_unicode_text(message, 'commit message')
126
151
rev = _mod_revision.Revision(
127
152
timestamp=self._timestamp,
128
153
timezone=self._timezone,
212
237
"""Get a delta against the basis inventory for ie."""
213
238
if ie.file_id not in basis_inv:
215
return (None, path, ie.file_id, ie)
240
result = (None, path, ie.file_id, ie)
241
self._basis_delta.append(result)
216
243
elif ie != basis_inv[ie.file_id]:
217
244
# common but altered
218
245
# TODO: avoid tis id2path call.
219
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)
221
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
224
294
def record_entry_contents(self, ie, parent_invs, path, tree,
225
295
content_summary):
226
296
"""Record the content of ie from tree into the commit if needed.
278
348
if ie.revision is not None:
279
349
if not self._versioned_root and path == '':
280
350
# repositories that do not version the root set the root's
281
# revision to the new commit even when no change occurs, and
282
# this masks when a change may have occurred against the basis,
283
# 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.
284
357
if ie.file_id in basis_inv:
285
358
delta = (basis_inv.id2path(ie.file_id), path,
289
362
delta = (None, path, ie.file_id, ie)
363
self._basis_delta.append(delta)
290
364
return delta, False, None
292
366
# we don't need to commit this, because the caller already
596
671
return self._inventory_add_lines(revision_id, parents,
597
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),
599
713
def _inventory_add_lines(self, revision_id, parents, lines,
600
714
check_content=True):
601
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),
3062
3268
@needs_write_lock
3063
3269
def fetch(self, revision_id=None, pb=None, find_ghosts=False):
3064
3270
"""See InterRepository.fetch()."""
3065
3271
revision_ids = self.target.search_missing_revision_ids(self.source,
3066
3272
revision_id, find_ghosts=find_ghosts).get_keys()
3273
if not revision_ids:
3067
3275
revision_ids = tsort.topo_sort(
3068
3276
self.source.get_graph().get_parent_map(revision_ids))
3069
def revisions_iterator():
3070
for current_revision_id in revision_ids:
3071
revision = self.source.get_revision(current_revision_id)
3072
tree = self.source.revision_tree(current_revision_id)
3074
signature = self.source.get_signature_text(
3075
current_revision_id)
3076
except errors.NoSuchRevision:
3078
yield revision, tree, signature
3080
3278
my_pb = ui.ui_factory.nested_progress_bar()
3085
install_revisions(self.target, revisions_iterator(),
3086
len(revision_ids), pb)
3283
self._fetch_all_revisions(revision_ids, pb)
3088
3285
if my_pb is not None:
3089
3286
my_pb.finished()
3090
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
3093
3312
class InterOtherToRemote(InterRepository):
3094
3313
"""An InterRepository that simply delegates to the 'real' InterRepository