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_weaves -- number of file weaves 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
if to_branch == from_branch:
89
raise Exception("can't fetch from a branch to itself")
90
self.to_branch = to_branch
91
self.to_weaves = to_branch.weave_store
92
self.to_control = to_branch.control_weaves
93
self.from_branch = from_branch
94
self.from_weaves = from_branch.weave_store
95
self.from_control = from_branch.control_weaves
90
96
self.failed_revisions = []
91
97
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
100
self.copied_file_ids = set()
102
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
103
self.nested_pb = self.pb
102
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)):
106
self.last_revision = self._find_last_revision(last_revision)
107
except NoSuchRevision, e:
108
mutter('failed getting last revision: %s', e)
109
raise InstallFailed([last_revision])
110
mutter('fetch up to rev {%s}', self.last_revision)
112
revs_to_fetch = self._compare_ancestries()
114
raise InstallFailed([self.last_revision])
115
self._copy_revisions(revs_to_fetch)
116
self.new_ancestry = revs_to_fetch
119
def _find_last_revision(self, last_revision):
120
"""Find the limiting source revision.
122
Every ancestor of that revision will be merged across.
124
Returns the revision_id, or returns None if there's no history
125
in the source branch."""
126
self.pb.update('get source history')
127
from_history = self.from_branch.revision_history()
128
self.pb.update('get destination history')
130
self.from_branch.get_revision(last_revision)
133
return from_history[-1]
135
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)
138
def _compare_ancestries(self):
139
"""Get a list of revisions that must be copied.
141
That is, every revision that's in the ancestry of the source
142
branch and not in the destination branch."""
143
self.pb.update('get source ancestry')
144
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
146
dest_last_rev = self.to_branch.last_revision()
147
self.pb.update('get destination ancestry')
149
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
152
ss = set(dest_ancestry)
154
for rev_id in self.from_ancestry:
156
to_fetch.append(rev_id)
157
mutter('need to get revision {%s}', rev_id)
158
mutter('need to get %d revisions in total', len(to_fetch))
159
self.count_total = len(to_fetch)
162
def _copy_revisions(self, revs_to_fetch):
164
for rev_id in revs_to_fetch:
168
if self.to_branch.has_revision(rev_id):
170
self.pb.update('fetch revision', i, self.count_total)
171
self._copy_one_revision(rev_id)
172
self.count_copied += 1
175
def _copy_one_revision(self, rev_id):
176
"""Copy revision and everything referenced by it."""
177
mutter('copying revision {%s}', rev_id)
178
rev_xml = self.from_branch.get_revision_xml(rev_id)
179
inv_xml = self.from_branch.get_inventory_xml(rev_id)
180
rev = serializer_v5.read_revision_from_string(rev_xml)
181
inv = serializer_v5.read_inventory_from_string(inv_xml)
182
assert rev.revision_id == rev_id
183
assert rev.inventory_sha1 == sha_string(inv_xml)
184
mutter(' commiter %s, %d parents',
187
self._copy_new_texts(rev_id, inv)
188
parents = rev.parent_ids
189
for parent in parents:
190
if not self.to_branch.has_revision(parent):
191
parents.pop(parents.index(parent))
192
self._copy_inventory(rev_id, inv_xml, parents)
193
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
194
mutter('copied revision %s', rev_id)
197
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
198
self.to_control.add_text('inventory', rev_id,
199
split_lines(inv_xml), parent_ids)
201
def _copy_new_texts(self, rev_id, inv):
202
"""Copy any new texts occuring in this revision."""
203
# TODO: Rather than writing out weaves every time, hold them
204
# in memory until everything's done? But this way is nicer
205
# if it's interrupted.
206
for path, ie in inv.iter_entries():
207
if ie.revision != rev_id:
209
mutter('%s {%s} is changed in this revision',
211
self._copy_one_weave(rev_id, ie.file_id)
214
def _copy_one_weave(self, rev_id, file_id):
215
"""Copy one file weave."""
216
mutter('copy file {%s} modified in {%s}', file_id, rev_id)
217
if file_id in self.copied_file_ids:
218
mutter('file {%s} already copied', file_id)
220
from_weave = self.from_weaves.get_weave(file_id)
221
to_weave = self.to_weaves.get_weave_or_empty(file_id)
222
to_weave.join(from_weave)
223
self.to_weaves.put_weave(file_id, to_weave)
224
self.count_weaves += 1
225
self.copied_file_ids.add(file_id)
226
mutter('copied file {%s}', file_id)