59
62
# and add in all file versions
65
@deprecated_function(zero_eight)
63
66
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
67
"""Legacy API, please see branch.fetch(from_branch, last_revision, pb)."""
64
68
f = Fetcher(to_branch, from_branch, revision, pb)
65
69
return f.count_copied, f.failed_revisions
69
class Fetcher(object):
70
"""Pull revisions and texts from one branch to another.
72
This doesn't update the destination's history; that can be done
73
separately if desired.
76
If set, pull only up to this revision_id.
80
last_revision -- if last_revision
81
is given it will be that, otherwise the last revision of
74
class RepoFetcher(object):
75
"""Pull revisions and texts from one repository to another.
78
if set, try to limit to the data this revision references.
84
81
count_copied -- number of revisions copied
86
count_weaves -- number of file weaves copied
83
This should not be used directory, its essential a object to encapsulate
84
the logic in InterRepository.fetch().
88
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
89
if to_branch == from_branch:
90
raise Exception("can't fetch from a branch to itself")
91
self.to_branch = to_branch
92
self.to_weaves = to_branch.weave_store
93
self.to_control = to_branch.control_weaves
94
self.from_branch = from_branch
95
self.from_weaves = from_branch.weave_store
96
self.from_control = from_branch.control_weaves
86
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
97
88
self.failed_revisions = []
98
89
self.count_copied = 0
100
self.count_weaves = 0
101
self.copied_file_ids = set()
90
if to_repository.control_files._transport.base == from_repository.control_files._transport.base:
91
# check that last_revision is in 'from' and then return a no-operation.
92
if last_revision not in (None, NULL_REVISION):
93
from_repository.get_revision(last_revision)
95
self.to_repository = to_repository
96
self.from_repository = from_repository
97
# must not mutate self._last_revision as its potentially a shared instance
98
self._last_revision = last_revision
103
self.pb = bzrlib.ui.ui_factory.progress_bar()
100
self.pb = bzrlib.ui.ui_factory.nested_progress_bar()
101
self.nested_pb = self.pb
106
self.from_branch.lock_read()
108
self._fetch_revisions(last_revision)
110
self.from_branch.unlock()
104
self.nested_pb = None
105
self.from_repository.lock_read()
107
self.to_repository.lock_write()
111
if self.nested_pb is not None:
112
self.nested_pb.finished()
113
self.to_repository.unlock()
115
self.from_repository.unlock()
118
"""Primary worker function.
120
This initialises all the needed variables, and then fetches the
121
requested revisions, finally clearing the progress bar.
123
self.to_weaves = self.to_repository.weave_store
124
self.to_control = self.to_repository.control_weaves
125
self.from_weaves = self.from_repository.weave_store
126
self.from_control = self.from_repository.control_weaves
128
self.file_ids_names = {}
129
pp = ProgressPhase('Fetch phase', 4, self.pb)
132
revs = self._revids_to_fetch()
136
self._fetch_weave_texts(revs)
138
self._fetch_inventory_weave(revs)
140
self._fetch_revision_texts(revs)
141
self.count_copied += len(revs)
113
def _fetch_revisions(self, last_revision):
114
self.last_revision = self._find_last_revision(last_revision)
115
mutter('fetch up to rev {%s}', self.last_revision)
116
if (self.last_revision is not None and
117
self.to_branch.has_revision(self.last_revision)):
120
revs_to_fetch = self._compare_ancestries()
122
raise InstallFailed([self.last_revision])
123
self._copy_revisions(revs_to_fetch)
124
self.new_ancestry = revs_to_fetch
126
def _find_last_revision(self, last_revision):
127
"""Find the limiting source revision.
129
Every ancestor of that revision will be merged across.
131
Returns the revision_id, or returns None if there's no history
132
in the source branch."""
135
self.pb.update('get source history')
136
from_history = self.from_branch.revision_history()
137
self.pb.update('get destination history')
139
return from_history[-1]
141
return None # no history in the source branch
145
def _revids_to_fetch(self):
146
mutter('fetch up to rev {%s}', self._last_revision)
147
if self._last_revision is NULL_REVISION:
148
# explicit limit of no revisions needed
150
if (self._last_revision is not None and
151
self.to_repository.has_revision(self._last_revision)):
144
def _compare_ancestries(self):
145
"""Get a list of revisions that must be copied.
147
That is, every revision that's in the ancestry of the source
148
branch and not in the destination branch."""
149
self.pb.update('get source ancestry')
150
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
152
dest_last_rev = self.to_branch.last_revision()
153
self.pb.update('get destination ancestry')
155
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
158
ss = set(dest_ancestry)
160
for rev_id in self.from_ancestry:
162
to_fetch.append(rev_id)
163
mutter('need to get revision {%s}', rev_id)
164
mutter('need to get %d revisions in total', len(to_fetch))
165
self.count_total = len(to_fetch)
168
def _copy_revisions(self, revs_to_fetch):
170
for rev_id in revs_to_fetch:
174
if self.to_branch.has_revision(rev_id):
176
self.pb.update('fetch revision', i, self.count_total)
177
self._copy_one_revision(rev_id)
178
self.count_copied += 1
181
def _copy_one_revision(self, rev_id):
182
"""Copy revision and everything referenced by it."""
183
mutter('copying revision {%s}', rev_id)
184
rev_xml = self.from_branch.get_revision_xml(rev_id)
185
inv_xml = self.from_branch.get_inventory_xml(rev_id)
186
rev = serializer_v5.read_revision_from_string(rev_xml)
187
inv = serializer_v5.read_inventory_from_string(inv_xml)
188
assert rev.revision_id == rev_id
189
assert rev.inventory_sha1 == sha_string(inv_xml)
190
mutter(' commiter %s, %d parents',
193
self._copy_new_texts(rev_id, inv)
194
parents = rev.parent_ids
195
for parent in parents:
196
if not self.to_branch.has_revision(parent):
197
parents.pop(parents.index(parent))
198
self._copy_inventory(rev_id, inv_xml, parents)
199
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
200
mutter('copied revision %s', rev_id)
203
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
204
self.to_control.add_text('inventory', rev_id,
205
split_lines(inv_xml), parent_ids,
206
self.to_branch.get_transaction())
208
def _copy_new_texts(self, rev_id, inv):
209
"""Copy any new texts occuring in this revision."""
210
# TODO: Rather than writing out weaves every time, hold them
211
# in memory until everything's done? But this way is nicer
212
# if it's interrupted.
213
for path, ie in inv.iter_entries():
214
if ie.revision != rev_id:
216
mutter('%s {%s} is changed in this revision',
218
self._copy_one_weave(rev_id, ie.file_id)
221
def _copy_one_weave(self, rev_id, file_id):
222
"""Copy one file weave."""
223
mutter('copy file {%s} modified in {%s}', file_id, rev_id)
224
if file_id in self.copied_file_ids:
225
mutter('file {%s} already copied', file_id)
227
from_weave = self.from_weaves.get_weave(file_id,
228
self.from_branch.get_transaction())
229
to_weave = self.to_weaves.get_weave_or_empty(file_id,
230
self.to_branch.get_transaction())
232
to_weave.join(from_weave)
233
except errors.WeaveParentMismatch:
234
to_weave.reweave(from_weave)
235
self.to_weaves.put_weave(file_id, to_weave,
236
self.to_branch.get_transaction())
237
self.count_weaves += 1
238
self.copied_file_ids.add(file_id)
239
mutter('copied file {%s}', file_id)
155
return self.to_repository.missing_revision_ids(self.from_repository,
157
except errors.NoSuchRevision:
158
raise InstallFailed([self._last_revision])
160
def _fetch_weave_texts(self, revs):
161
texts_pb = bzrlib.ui.ui_factory.nested_progress_bar()
163
# fileids_altered_by_revision_ids requires reading the inventory
164
# weave, we will need to read the inventory weave again when
165
# all this is done, so enable caching for that specific weave
166
inv_w = self.from_repository.get_inventory_weave()
168
file_ids = self.from_repository.fileids_altered_by_revision_ids(revs)
170
num_file_ids = len(file_ids)
171
for file_id, required_versions in file_ids.items():
172
texts_pb.update("fetch texts", count, num_file_ids)
174
to_weave = self.to_weaves.get_weave_or_empty(file_id,
175
self.to_repository.get_transaction())
176
from_weave = self.from_weaves.get_weave(file_id,
177
self.from_repository.get_transaction())
178
# we fetch all the texts, because texts do
179
# not reference anything, and its cheap enough
180
to_weave.join(from_weave, version_ids=required_versions)
181
# we don't need *all* of this data anymore, but we dont know
182
# what we do. This cache clearing will result in a new read
183
# of the knit data when we do the checkout, but probably we
184
# want to emit the needed data on the fly rather than at the
186
# the from weave should know not to cache data being joined,
187
# but its ok to ask it to clear.
188
from_weave.clear_cache()
189
to_weave.clear_cache()
193
def _fetch_inventory_weave(self, revs):
194
pb = bzrlib.ui.ui_factory.nested_progress_bar()
196
pb.update("fetch inventory", 0, 2)
197
to_weave = self.to_control.get_weave('inventory',
198
self.to_repository.get_transaction())
200
child_pb = bzrlib.ui.ui_factory.nested_progress_bar()
202
# just merge, this is optimisable and its means we don't
203
# copy unreferenced data such as not-needed inventories.
204
pb.update("fetch inventory", 1, 3)
205
from_weave = self.from_repository.get_inventory_weave()
206
pb.update("fetch inventory", 2, 3)
207
# we fetch only the referenced inventories because we do not
208
# know for unselected inventories whether all their required
209
# texts are present in the other repository - it could be
211
to_weave.join(from_weave, pb=child_pb, msg='merge inventory',
213
from_weave.clear_cache()
220
class GenericRepoFetcher(RepoFetcher):
221
"""This is a generic repo to repo fetcher.
223
This makes minimal assumptions about repo layout and contents.
224
It triggers a reconciliation after fetching to ensure integrity.
227
def _fetch_revision_texts(self, revs):
228
"""Fetch revision object texts"""
229
rev_pb = bzrlib.ui.ui_factory.nested_progress_bar()
231
to_txn = self.to_transaction = self.to_repository.get_transaction()
234
to_store = self.to_repository._revision_store
236
pb = bzrlib.ui.ui_factory.nested_progress_bar()
238
pb.update('copying revisions', count, total)
240
sig_text = self.from_repository.get_signature_text(rev)
241
to_store.add_revision_signature_text(rev, sig_text, to_txn)
242
except errors.NoSuchRevision:
245
to_store.add_revision(self.from_repository.get_revision(rev),
250
# fixup inventory if needed:
251
# this is expensive because we have no inverse index to current ghosts.
252
# but on local disk its a few seconds and sftp push is already insane.
254
# FIXME: repository should inform if this is needed.
255
self.to_repository.reconcile()
260
class KnitRepoFetcher(RepoFetcher):
261
"""This is a knit format repository specific fetcher.
263
This differs from the GenericRepoFetcher by not doing a
264
reconciliation after copying, and using knit joining to
268
def _fetch_revision_texts(self, revs):
269
# may need to be a InterRevisionStore call here.
270
from_transaction = self.from_repository.get_transaction()
271
to_transaction = self.to_repository.get_transaction()
272
to_sf = self.to_repository._revision_store.get_signature_file(
274
from_sf = self.from_repository._revision_store.get_signature_file(
276
to_sf.join(from_sf, version_ids=revs, ignore_missing=True)
277
to_rf = self.to_repository._revision_store.get_revision_file(
279
from_rf = self.from_repository._revision_store.get_revision_file(
281
to_rf.join(from_rf, version_ids=revs)
284
class Inter1and2Helper(object):
285
"""Helper for operations that convert data from model 1 and 2
287
This is for use by fetchers and converters.
290
def __init__(self, source, target):
293
:param source: The repository data comes from
294
:param target: The repository data goes to
299
def iter_rev_trees(self, revs):
300
"""Iterate through RevisionTrees efficiently.
302
Additionally, the inventory's revision_id is set if unset.
304
Trees are retrieved in batches of 100, and then yielded in the order
307
:param revs: A list of revision ids
310
for tree in self.source.revision_trees(revs[:100]):
311
if tree.inventory.revision_id is None:
312
tree.inventory.revision_id = tree.get_revision_id()
316
def generate_root_texts(self, revs):
317
"""Generate VersionedFiles for all root ids.
319
:param revs: the revisions to include
321
inventory_weave = self.source.get_inventory_weave()
324
to_store = self.target.weave_store
325
for tree in self.iter_rev_trees(revs):
326
revision_id = tree.inventory.root.revision
327
root_id = tree.inventory.root.file_id
328
parents = inventory_weave.get_parents(revision_id)
329
if root_id not in versionedfile:
330
versionedfile[root_id] = to_store.get_weave_or_empty(root_id,
331
self.target.get_transaction())
332
parent_texts[root_id] = versionedfile[root_id].add_lines(
333
revision_id, parents, [], parent_texts)
335
def regenerate_inventory(self, revs):
336
"""Generate a new inventory versionedfile in target, convertin data.
338
The inventory is retrieved from the source, (deserializing it), and
339
stored in the target (reserializing it in a different format).
340
:param revs: The revisions to include
342
inventory_weave = self.source.get_inventory_weave()
343
for tree in self.iter_rev_trees(revs):
344
parents = inventory_weave.get_parents(tree.get_revision_id())
345
self.target.add_inventory(tree.get_revision_id(), tree.inventory,
349
class Model1toKnit2Fetcher(GenericRepoFetcher):
350
"""Fetch from a Model1 repository into a Knit2 repository
352
def __init__(self, to_repository, from_repository, last_revision=None,
354
self.helper = Inter1and2Helper(from_repository, to_repository)
355
GenericRepoFetcher.__init__(self, to_repository, from_repository,
358
def _fetch_weave_texts(self, revs):
359
GenericRepoFetcher._fetch_weave_texts(self, revs)
360
# Now generate a weave for the tree root
361
self.helper.generate_root_texts(revs)
363
def _fetch_inventory_weave(self, revs):
364
self.helper.regenerate_inventory(revs)
367
class Knit1to2Fetcher(KnitRepoFetcher):
368
"""Fetch from a Knit1 repository into a Knit2 repository"""
370
def __init__(self, to_repository, from_repository, last_revision=None,
372
self.helper = Inter1and2Helper(from_repository, to_repository)
373
KnitRepoFetcher.__init__(self, to_repository, from_repository,
376
def _fetch_weave_texts(self, revs):
377
KnitRepoFetcher._fetch_weave_texts(self, revs)
378
# Now generate a weave for the tree root
379
self.helper.generate_root_texts(revs)
381
def _fetch_inventory_weave(self, revs):
382
self.helper.regenerate_inventory(revs)
385
class Fetcher(object):
386
"""Backwards compatibility glue for branch.fetch()."""
388
@deprecated_method(zero_eight)
389
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
390
"""Please see branch.fetch()."""
391
to_branch.fetch(from_branch, last_revision, pb)