73
83
count_copied -- number of revisions copied
75
This should not be used directly, it's essential a object to encapsulate
85
This should not be used directory, its essential a object to encapsulate
76
86
the logic in InterRepository.fetch().
79
def __init__(self, to_repository, from_repository, last_revision=None, pb=None,
81
"""Create a repo fetcher.
83
:param find_ghosts: If True search the entire history for ghosts.
88
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
85
89
# result variables.
86
90
self.failed_revisions = []
87
91
self.count_copied = 0
88
if to_repository.has_same_location(from_repository):
89
# repository.fetch should be taking care of this case.
90
raise errors.BzrError('RepoFetcher run '
91
'between two objects at the same location: '
92
'%r and %r' % (to_repository, from_repository))
92
if to_repository.control_files._transport.base == from_repository.control_files._transport.base:
93
# check that last_revision is in 'from' and then return a no-operation.
94
if last_revision not in (None, NULL_REVISION):
95
from_repository.get_revision(last_revision)
93
97
self.to_repository = to_repository
94
98
self.from_repository = from_repository
95
99
# must not mutate self._last_revision as its potentially a shared instance
96
100
self._last_revision = last_revision
97
self.find_ghosts = find_ghosts
99
102
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
100
103
self.nested_pb = self.pb
128
122
This initialises all the needed variables, and then fetches the
129
123
requested revisions, finally clearing the progress bar.
125
self.to_weaves = self.to_repository.weave_store
126
self.to_control = self.to_repository.control_weaves
127
self.from_weaves = self.from_repository.weave_store
128
self.from_control = self.from_repository.control_weaves
131
129
self.count_total = 0
132
130
self.file_ids_names = {}
133
pp = ProgressPhase('Transferring', 4, self.pb)
131
pp = ProgressPhase('Fetch phase', 4, self.pb)
136
search = self._revids_to_fetch()
139
if getattr(self, '_fetch_everything_for_search', None) is not None:
140
self._fetch_everything_for_search(search, pp)
142
# backward compatibility
143
self._fetch_everything_for_revisions(search.get_keys, pp)
134
revs = self._revids_to_fetch()
138
self._fetch_weave_texts(revs)
140
self._fetch_inventory_weave(revs)
142
self._fetch_revision_texts(revs)
143
self.count_copied += len(revs)
147
def _fetch_everything_for_search(self, search, pp):
148
"""Fetch all data for the given set of revisions."""
149
# The first phase is "file". We pass the progress bar for it directly
150
# into item_keys_introduced_by, which has more information about how
151
# that phase is progressing than we do. Progress updates for the other
152
# phases are taken care of in this function.
153
# XXX: there should be a clear owner of the progress reporting. Perhaps
154
# item_keys_introduced_by should have a richer API than it does at the
155
# moment, so that it can feed the progress information back to this
158
pb = bzrlib.ui.ui_factory.nested_progress_bar()
160
revs = search.get_keys()
161
graph = self.from_repository.get_graph()
162
revs = list(graph.iter_topo_order(revs))
163
data_to_fetch = self.from_repository.item_keys_introduced_by(revs,
166
for knit_kind, file_id, revisions in data_to_fetch:
167
if knit_kind != phase:
169
# Make a new progress bar for this phase
172
pb = bzrlib.ui.ui_factory.nested_progress_bar()
173
if knit_kind == "file":
174
# Accumulate file texts
175
text_keys.extend([(file_id, revision) for revision in
177
elif knit_kind == "inventory":
178
# Now copy the file texts.
179
to_texts = self.to_repository.texts
180
from_texts = self.from_repository.texts
181
to_texts.insert_record_stream(from_texts.get_record_stream(
182
text_keys, self.to_repository._fetch_order,
183
not self.to_repository._fetch_uses_deltas))
184
# Cause an error if a text occurs after we have done the
187
# Before we process the inventory we generate the root
188
# texts (if necessary) so that the inventories references
190
self._generate_root_texts(revs)
191
# NB: This currently reopens the inventory weave in source;
192
# using a single stream interface instead would avoid this.
193
self._fetch_inventory_weave(revs, pb)
194
elif knit_kind == "signatures":
195
# Nothing to do here; this will be taken care of when
196
# _fetch_revision_texts happens.
198
elif knit_kind == "revisions":
199
self._fetch_revision_texts(revs, pb)
201
raise AssertionError("Unknown knit kind %r" % knit_kind)
202
if self.to_repository._fetch_reconcile:
203
self.to_repository.reconcile()
207
self.count_copied += len(revs)
209
147
def _revids_to_fetch(self):
210
"""Determines the exact revisions needed from self.from_repository to
211
install self._last_revision in self.to_repository.
213
If no revisions need to be fetched, then this just returns None.
215
148
mutter('fetch up to rev {%s}', self._last_revision)
216
149
if self._last_revision is NULL_REVISION:
217
150
# explicit limit of no revisions needed
219
152
if (self._last_revision is not None and
220
153
self.to_repository.has_revision(self._last_revision)):
223
return self.to_repository.search_missing_revision_ids(
224
self.from_repository, self._last_revision,
225
find_ghosts=self.find_ghosts)
226
except errors.NoSuchRevision, e:
157
return self.to_repository.missing_revision_ids(self.from_repository,
159
except errors.NoSuchRevision:
227
160
raise InstallFailed([self._last_revision])
229
def _fetch_inventory_weave(self, revs, pb):
230
pb.update("fetch inventory", 0, 2)
231
to_weave = self.to_repository.inventories
232
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
234
# just merge, this is optimisable and its means we don't
235
# copy unreferenced data such as not-needed inventories.
236
pb.update("fetch inventory", 1, 3)
237
from_weave = self.from_repository.inventories
238
pb.update("fetch inventory", 2, 3)
239
# we fetch only the referenced inventories because we do not
240
# know for unselected inventories whether all their required
241
# texts are present in the other repository - it could be
243
to_weave.insert_record_stream(from_weave.get_record_stream(
244
[(rev_id,) for rev_id in revs],
245
self.to_repository._fetch_order,
246
not self.to_repository._fetch_uses_deltas))
250
def _fetch_revision_texts(self, revs, pb):
162
def _fetch_weave_texts(self, revs):
163
texts_pb = bzrlib.ui.ui_factory.nested_progress_bar()
165
# fileids_altered_by_revision_ids requires reading the inventory
166
# weave, we will need to read the inventory weave again when
167
# all this is done, so enable caching for that specific weave
168
inv_w = self.from_repository.get_inventory_weave()
170
file_ids = self.from_repository.fileids_altered_by_revision_ids(revs)
172
num_file_ids = len(file_ids)
173
for file_id, required_versions in file_ids.items():
174
texts_pb.update("fetch texts", count, num_file_ids)
176
to_weave = self.to_weaves.get_weave_or_empty(file_id,
177
self.to_repository.get_transaction())
178
from_weave = self.from_weaves.get_weave(file_id,
179
self.from_repository.get_transaction())
180
# we fetch all the texts, because texts do
181
# not reference anything, and its cheap enough
182
to_weave.join(from_weave, version_ids=required_versions)
183
# we don't need *all* of this data anymore, but we dont know
184
# what we do. This cache clearing will result in a new read
185
# of the knit data when we do the checkout, but probably we
186
# want to emit the needed data on the fly rather than at the
188
# the from weave should know not to cache data being joined,
189
# but its ok to ask it to clear.
190
from_weave.clear_cache()
191
to_weave.clear_cache()
195
def _fetch_inventory_weave(self, revs):
196
pb = bzrlib.ui.ui_factory.nested_progress_bar()
198
pb.update("fetch inventory", 0, 2)
199
to_weave = self.to_control.get_weave('inventory',
200
self.to_repository.get_transaction())
202
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
204
# just merge, this is optimisable and its means we don't
205
# copy unreferenced data such as not-needed inventories.
206
pb.update("fetch inventory", 1, 3)
207
from_weave = self.from_repository.get_inventory_weave()
208
pb.update("fetch inventory", 2, 3)
209
# we fetch only the referenced inventories because we do not
210
# know for unselected inventories whether all their required
211
# texts are present in the other repository - it could be
213
to_weave.join(from_weave, pb=child_pb, msg='merge inventory',
215
from_weave.clear_cache()
222
class GenericRepoFetcher(RepoFetcher):
223
"""This is a generic repo to repo fetcher.
225
This makes minimal assumptions about repo layout and contents.
226
It triggers a reconciliation after fetching to ensure integrity.
229
def _fetch_revision_texts(self, revs):
230
"""Fetch revision object texts"""
231
rev_pb = bzrlib.ui.ui_factory.nested_progress_bar()
233
to_txn = self.to_transaction = self.to_repository.get_transaction()
236
to_store = self.to_repository._revision_store
238
pb = bzrlib.ui.ui_factory.nested_progress_bar()
240
pb.update('copying revisions', count, total)
242
sig_text = self.from_repository.get_signature_text(rev)
243
to_store.add_revision_signature_text(rev, sig_text, to_txn)
244
except errors.NoSuchRevision:
247
to_store.add_revision(self.from_repository.get_revision(rev),
252
# fixup inventory if needed:
253
# this is expensive because we have no inverse index to current ghosts.
254
# but on local disk its a few seconds and sftp push is already insane.
256
# FIXME: repository should inform if this is needed.
257
self.to_repository.reconcile()
262
class KnitRepoFetcher(RepoFetcher):
263
"""This is a knit format repository specific fetcher.
265
This differs from the GenericRepoFetcher by not doing a
266
reconciliation after copying, and using knit joining to
270
def _fetch_revision_texts(self, revs):
251
271
# may need to be a InterRevisionStore call here.
252
to_sf = self.to_repository.signatures
253
from_sf = self.from_repository.signatures
254
# A missing signature is just skipped.
255
to_sf.insert_record_stream(filter_absent(from_sf.get_record_stream(
256
[(rev_id,) for rev_id in revs],
257
self.to_repository._fetch_order,
259
# Bug #261339, some knit repositories accidentally had deltas in their
260
# revision stream, when you weren't ever supposed to have deltas.
261
# So we now *force* fulltext copying for signatures and revisions
262
self._fetch_just_revision_texts(revs)
264
def _fetch_just_revision_texts(self, version_ids):
265
to_rf = self.to_repository.revisions
266
from_rf = self.from_repository.revisions
267
to_rf.insert_record_stream(from_rf.get_record_stream(
268
[(rev_id,) for rev_id in version_ids],
269
self.to_repository._fetch_order,
271
# Bug #261339, some knit repositories accidentally had deltas in their
272
# revision stream, when you weren't ever supposed to have deltas.
273
# So we now *force* fulltext copying for signatures and revisions
275
def _generate_root_texts(self, revs):
276
"""This will be called by __fetch between fetching weave texts and
277
fetching the inventory weave.
279
Subclasses should override this if they need to generate root texts
280
after fetching weave texts.
272
from_transaction = self.from_repository.get_transaction()
273
to_transaction = self.to_repository.get_transaction()
274
to_sf = self.to_repository._revision_store.get_signature_file(
276
from_sf = self.from_repository._revision_store.get_signature_file(
278
to_sf.join(from_sf, version_ids=revs, ignore_missing=True)
279
to_rf = self.to_repository._revision_store.get_revision_file(
281
from_rf = self.from_repository._revision_store.get_revision_file(
283
to_rf.join(from_rf, version_ids=revs)
285
286
class Inter1and2Helper(object):
317
316
revs = revs[100:]
319
def _find_root_ids(self, revs, parent_map, graph):
321
planned_versions = {}
322
for tree in self.iter_rev_trees(revs):
323
revision_id = tree.inventory.root.revision
324
root_id = tree.get_root_id()
325
planned_versions.setdefault(root_id, []).append(revision_id)
326
revision_root[revision_id] = root_id
327
# Find out which parents we don't already know root ids for
329
for revision_parents in parent_map.itervalues():
330
parents.update(revision_parents)
331
parents.difference_update(revision_root.keys() + [NULL_REVISION])
332
# Limit to revisions present in the versionedfile
333
parents = graph.get_parent_map(parents).keys()
334
for tree in self.iter_rev_trees(parents):
335
root_id = tree.get_root_id()
336
revision_root[tree.get_revision_id()] = root_id
337
return revision_root, planned_versions
339
318
def generate_root_texts(self, revs):
340
319
"""Generate VersionedFiles for all root ids.
342
321
:param revs: the revisions to include
344
to_texts = self.target.texts
345
graph = self.source.get_graph()
346
parent_map = graph.get_parent_map(revs)
347
rev_order = topo_sort(parent_map)
348
rev_id_to_root_id, root_id_to_rev_ids = self._find_root_ids(
349
revs, parent_map, graph)
350
root_id_order = [(rev_id_to_root_id[rev_id], rev_id) for rev_id in
352
# Guaranteed stable, this groups all the file id operations together
353
# retaining topological order within the revisions of a file id.
354
# File id splits and joins would invalidate this, but they don't exist
355
# yet, and are unlikely to in non-rich-root environments anyway.
356
root_id_order.sort(key=operator.itemgetter(0))
357
# Create a record stream containing the roots to create.
359
for key in root_id_order:
360
root_id, rev_id = key
361
rev_parents = parent_map[rev_id]
362
# We drop revision parents with different file-ids, because
363
# that represents a rename of the root to a different location
364
# - its not actually a parent for us. (We could look for that
365
# file id in the revision tree at considerably more expense,
366
# but for now this is sufficient (and reconcile will catch and
367
# correct this anyway).
368
# When a parent revision is a ghost, we guess that its root id
369
# was unchanged (rather than trimming it from the parent list).
370
parent_keys = tuple((root_id, parent) for parent in rev_parents
371
if parent != NULL_REVISION and
372
rev_id_to_root_id.get(parent, root_id) == root_id)
373
yield FulltextContentFactory(key, parent_keys, None, '')
374
to_texts.insert_record_stream(yield_roots())
323
inventory_weave = self.source.get_inventory_weave()
326
to_store = self.target.weave_store
327
for tree in self.iter_rev_trees(revs):
328
revision_id = tree.inventory.root.revision
329
root_id = tree.inventory.root.file_id
330
parents = inventory_weave.get_parents(revision_id)
331
if root_id not in versionedfile:
332
versionedfile[root_id] = to_store.get_weave_or_empty(root_id,
333
self.target.get_transaction())
334
parent_texts[root_id] = versionedfile[root_id].add_lines(
335
revision_id, parents, [], parent_texts)
376
337
def regenerate_inventory(self, revs):
377
338
"""Generate a new inventory versionedfile in target, convertin data.
380
341
stored in the target (reserializing it in a different format).
381
342
:param revs: The revisions to include
344
inventory_weave = self.source.get_inventory_weave()
383
345
for tree in self.iter_rev_trees(revs):
384
parents = tree.get_parent_ids()
346
parents = inventory_weave.get_parents(tree.get_revision_id())
385
347
self.target.add_inventory(tree.get_revision_id(), tree.inventory,
388
def fetch_revisions(self, revision_ids):
389
# TODO: should this batch them up rather than requesting 10,000
391
for revision in self.source.get_revisions(revision_ids):
392
self.target.add_revision(revision.revision_id, revision)
395
class Model1toKnit2Fetcher(RepoFetcher):
351
class Model1toKnit2Fetcher(GenericRepoFetcher):
396
352
"""Fetch from a Model1 repository into a Knit2 repository
398
def __init__(self, to_repository, from_repository, last_revision=None,
399
pb=None, find_ghosts=True):
354
def __init__(self, to_repository, from_repository, last_revision=None,
400
356
self.helper = Inter1and2Helper(from_repository, to_repository)
401
RepoFetcher.__init__(self, to_repository, from_repository,
402
last_revision, pb, find_ghosts)
357
GenericRepoFetcher.__init__(self, to_repository, from_repository,
404
def _generate_root_texts(self, revs):
360
def _fetch_weave_texts(self, revs):
361
GenericRepoFetcher._fetch_weave_texts(self, revs)
362
# Now generate a weave for the tree root
405
363
self.helper.generate_root_texts(revs)
407
def _fetch_inventory_weave(self, revs, pb):
365
def _fetch_inventory_weave(self, revs):
408
366
self.helper.regenerate_inventory(revs)
410
def _fetch_revision_texts(self, revs, pb):
411
"""Fetch revision object texts"""
415
pb.update('copying revisions', count, total)
417
sig_text = self.from_repository.get_signature_text(rev)
418
self.to_repository.add_signature_text(rev, sig_text)
419
except errors.NoSuchRevision:
422
self._copy_revision(rev)
425
def _copy_revision(self, rev):
426
self.helper.fetch_revisions([rev])
429
class Knit1to2Fetcher(RepoFetcher):
369
class Knit1to2Fetcher(KnitRepoFetcher):
430
370
"""Fetch from a Knit1 repository into a Knit2 repository"""
432
def __init__(self, to_repository, from_repository, last_revision=None,
433
pb=None, find_ghosts=True):
372
def __init__(self, to_repository, from_repository, last_revision=None,
434
374
self.helper = Inter1and2Helper(from_repository, to_repository)
435
RepoFetcher.__init__(self, to_repository, from_repository,
436
last_revision, pb, find_ghosts)
375
KnitRepoFetcher.__init__(self, to_repository, from_repository,
438
def _generate_root_texts(self, revs):
378
def _fetch_weave_texts(self, revs):
379
KnitRepoFetcher._fetch_weave_texts(self, revs)
380
# Now generate a weave for the tree root
439
381
self.helper.generate_root_texts(revs)
441
def _fetch_inventory_weave(self, revs, pb):
383
def _fetch_inventory_weave(self, revs):
442
384
self.helper.regenerate_inventory(revs)
444
def _fetch_just_revision_texts(self, version_ids):
445
self.helper.fetch_revisions(version_ids)
387
class Fetcher(object):
388
"""Backwards compatibility glue for branch.fetch()."""
390
@deprecated_method(zero_eight)
391
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
392
"""Please see branch.fetch()."""
393
to_branch.fetch(from_branch, last_revision, pb)