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_repository = to_branch.repository
88
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
90
self.failed_revisions = []
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
to_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
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.
95
125
self.to_weaves = self.to_repository.weave_store
96
126
self.to_control = self.to_repository.control_weaves
97
self.from_branch = from_branch
98
self.from_repository = from_branch.repository
99
127
self.from_weaves = self.from_repository.weave_store
100
128
self.from_control = self.from_repository.control_weaves
101
self.failed_revisions = []
102
self.count_copied = 0
103
129
self.count_total = 0
104
self.count_weaves = 0
105
self.copied_file_ids = set()
106
130
self.file_ids_names = {}
108
self.pb = bzrlib.ui.ui_factory.progress_bar()
111
self.from_branch.lock_read()
131
pp = ProgressPhase('Fetch phase', 4, self.pb)
113
self._fetch_revisions(last_revision)
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
self.from_branch.unlock()
118
def _fetch_revisions(self, last_revision):
119
self.last_revision = self._find_last_revision(last_revision)
120
mutter('fetch up to rev {%s}', self.last_revision)
121
if (self.last_revision is not None and
122
self.to_repository.has_revision(self.last_revision)):
125
revs_to_fetch = self._compare_ancestries()
127
raise InstallFailed([self.last_revision])
128
self._copy_revisions(revs_to_fetch)
129
self.new_ancestry = revs_to_fetch
131
def _find_last_revision(self, last_revision):
132
"""Find the limiting source revision.
134
Every ancestor of that revision will be merged across.
136
Returns the revision_id, or returns None if there's no history
137
in the source branch."""
140
self.pb.update('get source history')
141
from_history = self.from_branch.revision_history()
142
self.pb.update('get destination history')
144
return from_history[-1]
146
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)):
149
def _compare_ancestries(self):
150
"""Get a list of revisions that must be copied.
152
That is, every revision that's in the ancestry of the source
153
branch and not in the destination branch."""
154
self.pb.update('get source ancestry')
155
from_repository = self.from_branch.repository
156
self.from_ancestry = from_repository.get_ancestry(self.last_revision)
158
dest_last_rev = self.to_branch.last_revision()
159
self.pb.update('get destination ancestry')
161
to_repository = self.to_branch.repository
162
dest_ancestry = to_repository.get_ancestry(dest_last_rev)
165
ss = set(dest_ancestry)
167
for rev_id in self.from_ancestry:
169
to_fetch.append(rev_id)
170
mutter('need to get revision {%s}', rev_id)
171
mutter('need to get %d revisions in total', len(to_fetch))
172
self.count_total = len(to_fetch)
175
def _copy_revisions(self, revs_to_fetch):
177
for rev_id in revs_to_fetch:
181
if self.to_repository.has_revision(rev_id):
183
self.pb.update('copy revision', i, self.count_total)
184
self._copy_one_revision(rev_id)
185
self.count_copied += 1
188
def _copy_one_revision(self, rev_id):
189
"""Copy revision and everything referenced by it."""
190
mutter('copying revision {%s}', rev_id)
191
rev_xml = self.from_repository.get_revision_xml(rev_id)
192
inv_xml = self.from_repository.get_inventory_xml(rev_id)
193
rev = serializer_v5.read_revision_from_string(rev_xml)
194
inv = serializer_v5.read_inventory_from_string(inv_xml)
195
assert rev.revision_id == rev_id
196
assert rev.inventory_sha1 == sha_string(inv_xml)
197
mutter(' commiter %s, %d parents',
200
self._copy_new_texts(rev_id, inv)
201
parents = rev.parent_ids
202
new_parents = copy(parents)
203
for parent in parents:
204
if not self.to_repository.has_revision(parent):
205
new_parents.pop(new_parents.index(parent))
206
self._copy_inventory(rev_id, inv_xml, new_parents)
207
self.to_repository.revision_store.add(StringIO(rev_xml), rev_id)
208
mutter('copied revision %s', rev_id)
210
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
211
self.to_control.add_text('inventory', rev_id,
212
split_lines(inv_xml), parent_ids,
213
self.to_repository.get_transaction())
215
def _copy_new_texts(self, rev_id, inv):
216
"""Copy any new texts occuring in this revision."""
217
# TODO: Rather than writing out weaves every time, hold them
218
# in memory until everything's done? But this way is nicer
219
# if it's interrupted.
220
for path, ie in inv.iter_entries():
221
self._copy_one_weave(rev_id, ie.file_id, ie.revision)
223
def _copy_one_weave(self, rev_id, file_id, text_revision):
224
"""Copy one file weave, esuring the result contains text_revision."""
225
# check if the revision is already there
226
if file_id in self.file_ids_names.keys( ) and \
227
text_revision in self.file_ids_names[file_id]:
229
to_weave = self.to_weaves.get_weave_or_empty(file_id,
230
self.to_repository.get_transaction())
231
if not file_id in self.file_ids_names.keys( ):
232
self.file_ids_names[file_id] = to_weave.names( )
233
if text_revision in to_weave:
235
from_weave = self.from_weaves.get_weave(file_id,
236
self.from_branch.repository.get_transaction())
237
if text_revision not in from_weave:
238
raise MissingText(self.from_branch, text_revision, file_id)
239
mutter('copy file {%s} modified in {%s}', file_id, rev_id)
241
if to_weave.numversions() > 0:
242
# destination has contents, must merge
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()
244
to_weave.join(from_weave)
245
except errors.WeaveParentMismatch:
246
to_weave.reweave(from_weave)
248
# destination is empty, just replace it
249
to_weave = from_weave.copy( )
250
self.to_weaves.put_weave(file_id, to_weave,
251
self.to_repository.get_transaction())
252
self.count_weaves += 1
253
self.copied_file_ids.add(file_id)
254
self.file_ids_names[file_id] = to_weave.names()
255
mutter('copied file {%s}', file_id)
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)