64
58
# and add in all file versions
67
@deprecated_function(zero_eight)
68
62
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
69
"""Legacy API, please see branch.fetch(from_branch, last_revision, pb)."""
70
63
f = Fetcher(to_branch, from_branch, revision, pb)
71
64
return f.count_copied, f.failed_revisions
76
class RepoFetcher(object):
77
"""Pull revisions and texts from one repository to another.
80
if set, try to limit to the data this revision references.
68
class Fetcher(object):
69
"""Pull revisions and texts from one branch to another.
71
This doesn't update the destination's history; that can be done
72
separately if desired.
75
If set, pull only up to this revision_id.
79
last_revision -- if last_revision
80
is given it will be that, otherwise the last revision of
83
83
count_copied -- number of revisions copied
85
This should not be used directory, its essential a object to encapsulate
86
the logic in InterRepository.fetch().
85
count_texts -- number of file texts copied
88
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
87
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
88
self.to_branch = to_branch
89
self.to_weaves = to_branch.weave_store
90
self.from_branch = from_branch
91
self.from_weaves = from_branch.weave_store
90
92
self.failed_revisions = []
91
93
self.count_copied = 0
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)
97
self.to_repository = to_repository
98
self.from_repository = from_repository
99
# must not mutate self._last_revision as its potentially a shared instance
100
self._last_revision = last_revision
102
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
103
self.nested_pb = self.pb
97
self.pb = bzrlib.ui.ui_factory.progress_bar()
106
self.nested_pb = None
107
self.from_repository.lock_read()
109
self.to_repository.lock_write()
113
if self.nested_pb is not None:
114
self.nested_pb.finished()
115
self.to_repository.unlock()
117
self.from_repository.unlock()
120
"""Primary worker function.
122
This initialises all the needed variables, and then fetches the
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
130
self.file_ids_names = {}
131
pp = ProgressPhase('Fetch phase', 4, self.pb)
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 _revids_to_fetch(self):
148
mutter('fetch up to rev {%s}', self._last_revision)
149
if self._last_revision is NULL_REVISION:
150
# explicit limit of no revisions needed
152
if (self._last_revision is not None and
153
self.to_repository.has_revision(self._last_revision)):
100
self.last_revision = self._find_last_revision(last_revision)
101
mutter('fetch up to rev {%s}', self.last_revision)
102
revs_to_fetch = self._compare_ancestries()
103
self._copy_revisions(revs_to_fetch)
104
self.new_ancestry = revs_to_fetch
108
def _find_last_revision(self, last_revision):
109
"""Find the limiting source revision.
111
Every ancestor of that revision will be merged across.
113
Returns the revision_id, or returns None if there's no history
114
in the source branch."""
115
self.pb.update('get source history')
116
from_history = self.from_branch.revision_history()
117
self.pb.update('get destination history')
119
if last_revision not in from_history:
120
raise NoSuchRevision(self.from_branch, last_revision)
124
return from_history[-1]
126
return None # no history in the source branch
157
return self.to_repository.missing_revision_ids(self.from_repository,
159
except errors.NoSuchRevision:
160
raise InstallFailed([self._last_revision])
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):
271
# may need to be a InterRevisionStore call here.
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)
286
class Inter1and2Helper(object):
287
"""Helper for operations that convert data from model 1 and 2
289
This is for use by fetchers and converters.
292
def __init__(self, source, target):
295
:param source: The repository data comes from
296
:param target: The repository data goes to
301
def iter_rev_trees(self, revs):
302
"""Iterate through RevisionTrees efficiently.
304
Additionally, the inventory's revision_id is set if unset.
306
Trees are retrieved in batches of 100, and then yielded in the order
309
:param revs: A list of revision ids
312
for tree in self.source.revision_trees(revs[:100]):
313
if tree.inventory.revision_id is None:
314
tree.inventory.revision_id = tree.get_revision_id()
318
def generate_root_texts(self, revs):
319
"""Generate VersionedFiles for all root ids.
321
:param revs: the revisions to include
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)
337
def regenerate_inventory(self, revs):
338
"""Generate a new inventory versionedfile in target, convertin data.
340
The inventory is retrieved from the source, (deserializing it), and
341
stored in the target (reserializing it in a different format).
342
:param revs: The revisions to include
344
inventory_weave = self.source.get_inventory_weave()
345
for tree in self.iter_rev_trees(revs):
346
parents = inventory_weave.get_parents(tree.get_revision_id())
347
self.target.add_inventory(tree.get_revision_id(), tree.inventory,
351
class Model1toKnit2Fetcher(GenericRepoFetcher):
352
"""Fetch from a Model1 repository into a Knit2 repository
354
def __init__(self, to_repository, from_repository, last_revision=None,
356
self.helper = Inter1and2Helper(from_repository, to_repository)
357
GenericRepoFetcher.__init__(self, to_repository, from_repository,
360
def _fetch_weave_texts(self, revs):
361
GenericRepoFetcher._fetch_weave_texts(self, revs)
362
# Now generate a weave for the tree root
363
self.helper.generate_root_texts(revs)
365
def _fetch_inventory_weave(self, revs):
366
self.helper.regenerate_inventory(revs)
369
class Knit1to2Fetcher(KnitRepoFetcher):
370
"""Fetch from a Knit1 repository into a Knit2 repository"""
372
def __init__(self, to_repository, from_repository, last_revision=None,
374
self.helper = Inter1and2Helper(from_repository, to_repository)
375
KnitRepoFetcher.__init__(self, to_repository, from_repository,
378
def _fetch_weave_texts(self, revs):
379
KnitRepoFetcher._fetch_weave_texts(self, revs)
380
# Now generate a weave for the tree root
381
self.helper.generate_root_texts(revs)
383
def _fetch_inventory_weave(self, revs):
384
self.helper.regenerate_inventory(revs)
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)
129
def _compare_ancestries(self):
130
"""Get a list of revisions that must be copied.
132
That is, every revision that's in the ancestry of the source
133
branch and not in the destination branch."""
134
self.pb.update('get source ancestry')
135
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
137
dest_last_rev = self.to_branch.last_revision()
138
self.pb.update('get destination ancestry')
140
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
143
ss = set(dest_ancestry)
145
for rev_id in self.from_ancestry:
147
to_fetch.append(rev_id)
148
mutter('need to get revision {%s}', rev_id)
149
mutter('need to get %d revisions in total', len(to_fetch))
150
self.count_total = len(to_fetch)
155
def _copy_revisions(self, revs_to_fetch):
157
for rev_id in revs_to_fetch:
159
if self.to_branch.has_revision(rev_id):
161
self.pb.update('fetch revision', i, self.count_total)
162
self._copy_one_revision(rev_id)
163
self.count_copied += 1
166
def _copy_one_revision(self, rev_id):
167
"""Copy revision and everything referenced by it."""
168
mutter('copying revision {%s}', rev_id)
169
rev_xml = self.from_branch.get_revision_xml(rev_id)
170
inv_xml = self.from_branch.get_inventory_xml(rev_id)
171
rev = serializer_v5.read_revision_from_string(rev_xml)
172
inv = serializer_v5.read_inventory_from_string(inv_xml)
173
assert rev.revision_id == rev_id
174
assert rev.inventory_sha1 == sha_string(inv_xml)
175
mutter(' commiter %s, %d parents',
178
self._copy_new_texts(rev_id, inv)
179
self._copy_inventory(rev_id, inv_xml, rev.parent_ids)
180
self._copy_ancestry(rev_id, rev.parent_ids)
181
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
184
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
185
self.to_weaves.add_text(INVENTORY_FILEID, rev_id,
186
split_lines(inv_xml), parent_ids)
189
def _copy_ancestry(self, rev_id, parent_ids):
190
ancestry_lines = self.from_weaves.get_lines(ANCESTRY_FILEID, rev_id)
191
self.to_weaves.add_text(ANCESTRY_FILEID, rev_id, ancestry_lines,
195
def _copy_new_texts(self, rev_id, inv):
196
"""Copy any new texts occuring in this revision."""
197
# TODO: Rather than writing out weaves every time, hold them
198
# in memory until everything's done? But this way is nicer
199
# if it's interrupted.
200
for path, ie in inv.iter_entries():
201
if ie.kind != 'file':
203
if ie.text_version != rev_id:
205
mutter('%s {%s} is changed in this revision',
207
self._copy_one_text(rev_id, ie.file_id)
210
def _copy_one_text(self, rev_id, file_id):
211
"""Copy one file text."""
212
mutter('copy text version {%s} of file {%s}',
214
from_weave = self.from_weaves.get_weave(file_id)
215
from_idx = from_weave.lookup(rev_id)
216
from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
217
text_lines = from_weave.get(from_idx)
218
to_weave = self.to_weaves.get_weave_or_empty(file_id)
219
to_parents = map(to_weave.lookup, from_parents)
220
# it's ok to add even if the text is already there
221
to_weave.add(rev_id, to_parents, text_lines)
222
self.to_weaves.put_weave(file_id, to_weave)
223
self.count_texts += 1