61
64
# and add in all file versions
67
@deprecated_function(zero_eight)
65
68
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
69
"""Legacy API, please see branch.fetch(from_branch, last_revision, pb)."""
66
70
f = Fetcher(to_branch, from_branch, revision, pb)
67
71
return f.count_copied, f.failed_revisions
71
class Fetcher(object):
72
"""Pull revisions and texts from one branch to another.
74
This doesn't update the destination's history; that can be done
75
separately if desired.
78
If set, pull only up to this revision_id.
82
last_revision -- if last_revision
83
is given it will be that, otherwise the last revision of
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.
86
83
count_copied -- number of revisions copied
88
count_weaves -- number of file weaves copied
85
This should not be used directory, its essential a object to encapsulate
86
the logic in InterRepository.fetch().
90
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
91
if to_branch == from_branch:
92
raise Exception("can't fetch from a branch to itself")
93
self.to_branch = to_branch
94
self.to_weaves = to_branch.weave_store
95
self.to_control = to_branch.control_weaves
96
self.from_branch = from_branch
97
self.from_weaves = from_branch.weave_store
98
self.from_control = from_branch.control_weaves
88
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
99
90
self.failed_revisions = []
100
91
self.count_copied = 0
102
self.count_weaves = 0
103
self.copied_file_ids = set()
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
105
self.pb = bzrlib.ui.ui_factory.progress_bar()
102
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
103
self.nested_pb = self.pb
108
self.from_branch.lock_read()
110
self._fetch_revisions(last_revision)
112
self.from_branch.unlock()
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)
115
def _fetch_revisions(self, last_revision):
116
self.last_revision = self._find_last_revision(last_revision)
117
mutter('fetch up to rev {%s}', self.last_revision)
118
if (self.last_revision is not None and
119
self.to_branch.has_revision(self.last_revision)):
122
revs_to_fetch = self._compare_ancestries()
124
raise InstallFailed([self.last_revision])
125
self._copy_revisions(revs_to_fetch)
126
self.new_ancestry = revs_to_fetch
128
def _find_last_revision(self, last_revision):
129
"""Find the limiting source revision.
131
Every ancestor of that revision will be merged across.
133
Returns the revision_id, or returns None if there's no history
134
in the source branch."""
137
self.pb.update('get source history')
138
from_history = self.from_branch.revision_history()
139
self.pb.update('get destination history')
141
return from_history[-1]
143
return None # no history in the source branch
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)):
146
def _compare_ancestries(self):
147
"""Get a list of revisions that must be copied.
149
That is, every revision that's in the ancestry of the source
150
branch and not in the destination branch."""
151
self.pb.update('get source ancestry')
152
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
154
dest_last_rev = self.to_branch.last_revision()
155
self.pb.update('get destination ancestry')
157
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
160
ss = set(dest_ancestry)
162
for rev_id in self.from_ancestry:
164
to_fetch.append(rev_id)
165
mutter('need to get revision {%s}', rev_id)
166
mutter('need to get %d revisions in total', len(to_fetch))
167
self.count_total = len(to_fetch)
170
def _copy_revisions(self, revs_to_fetch):
172
for rev_id in revs_to_fetch:
176
if self.to_branch.has_revision(rev_id):
178
self.pb.update('fetch revision', i, self.count_total)
179
self._copy_one_revision(rev_id)
180
self.count_copied += 1
183
def _copy_one_revision(self, rev_id):
184
"""Copy revision and everything referenced by it."""
185
mutter('copying revision {%s}', rev_id)
186
rev_xml = self.from_branch.get_revision_xml(rev_id)
187
inv_xml = self.from_branch.get_inventory_xml(rev_id)
188
rev = serializer_v5.read_revision_from_string(rev_xml)
189
inv = serializer_v5.read_inventory_from_string(inv_xml)
190
assert rev.revision_id == rev_id
191
assert rev.inventory_sha1 == sha_string(inv_xml)
192
mutter(' commiter %s, %d parents',
195
self._copy_new_texts(rev_id, inv)
196
parents = rev.parent_ids
197
new_parents = copy(parents)
198
for parent in parents:
199
if not self.to_branch.has_revision(parent):
200
new_parents.pop(new_parents.index(parent))
201
self._copy_inventory(rev_id, inv_xml, new_parents)
202
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
203
mutter('copied revision %s', rev_id)
205
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
206
self.to_control.add_text('inventory', rev_id,
207
split_lines(inv_xml), parent_ids,
208
self.to_branch.get_transaction())
210
def _copy_new_texts(self, rev_id, inv):
211
"""Copy any new texts occuring in this revision."""
212
# TODO: Rather than writing out weaves every time, hold them
213
# in memory until everything's done? But this way is nicer
214
# if it's interrupted.
215
for path, ie in inv.iter_entries():
216
self._copy_one_weave(rev_id, ie.file_id, ie.revision)
218
def _copy_one_weave(self, rev_id, file_id, text_revision):
219
"""Copy one file weave, esuring the result contains text_revision."""
220
to_weave = self.to_weaves.get_weave_or_empty(file_id,
221
self.to_branch.get_transaction())
222
if text_revision in to_weave:
224
from_weave = self.from_weaves.get_weave(file_id,
225
self.from_branch.get_transaction())
226
if text_revision not in from_weave:
227
raise MissingText(self.from_branch, text_revision, file_id)
228
mutter('copy file {%s} modified in {%s}', file_id, rev_id)
230
to_weave.join(from_weave)
231
except errors.WeaveParentMismatch:
232
to_weave.reweave(from_weave)
233
self.to_weaves.put_weave(file_id, to_weave,
234
self.to_branch.get_transaction())
235
self.count_weaves += 1
236
self.copied_file_ids.add(file_id)
237
mutter('copied file {%s}', file_id)
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)