59
58
# and add in all file versions
61
@deprecated_function(zero_eight)
63
62
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
63
"""Legacy API, please see branch.fetch(from_branch, last_revision, pb)."""
64
64
f = Fetcher(to_branch, from_branch, revision, pb)
65
65
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
70
class RepoFetcher(object):
71
"""Pull revisions and texts from one repository to another.
74
if set, try to limit to the data this revision references.
84
77
count_copied -- number of revisions copied
86
count_weaves -- number of file weaves copied
79
This should not be used directory, its essential a object to encapsulate
80
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
82
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
97
84
self.failed_revisions = []
98
85
self.count_copied = 0
100
self.count_weaves = 0
101
self.copied_file_ids = set()
86
if to_repository.control_files._transport.base == from_repository.control_files._transport.base:
87
# check that last_revision is in 'from' and then return a no-operation.
88
if last_revision not in (None, NULL_REVISION):
89
from_repository.get_revision(last_revision)
91
self.to_repository = to_repository
92
self.from_repository = from_repository
93
# must not mutate self._last_revision as its potentially a shared instance
94
self._last_revision = last_revision
103
96
self.pb = bzrlib.ui.ui_factory.progress_bar()
106
self.from_branch.lock_read()
108
self._fetch_revisions(last_revision)
110
self.from_branch.unlock()
99
self.from_repository.lock_read()
101
self.to_repository.lock_write()
105
self.to_repository.unlock()
107
self.from_repository.unlock()
110
"""Primary worker function.
112
This initialises all the needed variables, and then fetches the
113
requested revisions, finally clearing the progress bar.
115
self.to_weaves = self.to_repository.weave_store
116
self.to_control = self.to_repository.control_weaves
117
self.from_weaves = self.from_repository.weave_store
118
self.from_control = self.from_repository.control_weaves
120
self.file_ids_names = {}
122
revs = self._revids_to_fetch()
125
self._fetch_weave_texts(revs)
126
self._fetch_inventory_weave(revs)
127
self._fetch_revision_texts(revs)
128
self.count_copied += len(revs)
113
def _fetch_revisions(self, last_revision):
115
self.last_revision = self._find_last_revision(last_revision)
116
except NoSuchRevision, e:
117
mutter('failed getting last revision: %s', e)
118
raise InstallFailed([last_revision])
119
mutter('fetch up to rev {%s}', self.last_revision)
120
if (self.last_revision is not None and
121
self.to_branch.has_revision(self.last_revision)):
124
revs_to_fetch = self._compare_ancestries()
126
raise InstallFailed([self.last_revision])
127
self._copy_revisions(revs_to_fetch)
128
self.new_ancestry = revs_to_fetch
130
def _find_last_revision(self, last_revision):
131
"""Find the limiting source revision.
133
Every ancestor of that revision will be merged across.
135
Returns the revision_id, or returns None if there's no history
136
in the source branch."""
137
self.pb.update('get source history')
138
from_history = self.from_branch.revision_history()
132
def _revids_to_fetch(self):
139
133
self.pb.update('get destination history')
141
self.from_branch.get_revision(last_revision)
144
return from_history[-1]
146
return None # no history in the source branch
134
mutter('fetch up to rev {%s}', self._last_revision)
135
if self._last_revision is NULL_REVISION:
136
# explicit limit of no revisions needed
138
if (self._last_revision != None and
139
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
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
157
dest_last_rev = self.to_branch.last_revision()
158
self.pb.update('get destination ancestry')
160
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
143
return self.to_repository.missing_revision_ids(self.from_repository,
145
except errors.NoSuchRevision:
146
raise InstallFailed([self._last_revision])
148
def _fetch_revision_texts(self, revs):
149
self.to_repository.revision_store.copy_multi(
150
self.from_repository.revision_store,
154
def _fetch_weave_texts(self, revs):
155
file_ids = self.from_repository.fileid_involved_by_set(revs)
157
num_file_ids = len(file_ids)
158
for file_id in file_ids:
159
self.pb.update("merge weaves", count, num_file_ids)
161
to_weave = self.to_weaves.get_weave_or_empty(file_id,
162
self.to_repository.get_transaction())
163
from_weave = self.from_weaves.get_weave(file_id,
164
self.from_repository.get_transaction())
166
if to_weave.numversions() > 0:
167
# destination has contents, must merge
169
to_weave.join(from_weave)
170
except errors.WeaveParentMismatch:
171
to_weave.reweave(from_weave)
173
# destination is empty, just replace it
174
to_weave = from_weave.copy()
176
self.to_weaves.put_weave(file_id, to_weave,
177
self.to_repository.get_transaction())
180
def _fetch_inventory_weave(self, revs):
181
self.pb.update("inventory fetch", 0, 2)
182
from_weave = self.from_repository.get_inventory_weave()
183
to_weave = self.to_repository.get_inventory_weave()
184
self.pb.update("inventory fetch", 1, 2)
185
to_weave = self.to_control.get_weave('inventory',
186
self.to_repository.get_transaction())
187
self.pb.update("inventory fetch", 2, 2)
189
if to_weave.numversions() > 0:
190
# destination has contents, must merge
192
to_weave.join(from_weave, pb=self.pb, msg='merge inventory')
193
except errors.WeaveParentMismatch:
194
to_weave.reweave(from_weave, pb=self.pb, msg='reweave inventory')
163
ss = set(dest_ancestry)
165
for rev_id in self.from_ancestry:
167
to_fetch.append(rev_id)
168
mutter('need to get revision {%s}', rev_id)
169
mutter('need to get %d revisions in total', len(to_fetch))
170
self.count_total = len(to_fetch)
173
def _copy_revisions(self, revs_to_fetch):
175
for rev_id in revs_to_fetch:
179
if self.to_branch.has_revision(rev_id):
181
self.pb.update('fetch revision', i, self.count_total)
182
self._copy_one_revision(rev_id)
183
self.count_copied += 1
186
def _copy_one_revision(self, rev_id):
187
"""Copy revision and everything referenced by it."""
188
mutter('copying revision {%s}', rev_id)
189
rev_xml = self.from_branch.get_revision_xml(rev_id)
190
inv_xml = self.from_branch.get_inventory_xml(rev_id)
191
rev = serializer_v5.read_revision_from_string(rev_xml)
192
inv = serializer_v5.read_inventory_from_string(inv_xml)
193
assert rev.revision_id == rev_id
194
assert rev.inventory_sha1 == sha_string(inv_xml)
195
mutter(' commiter %s, %d parents',
198
self._copy_new_texts(rev_id, inv)
199
parents = rev.parent_ids
200
for parent in parents:
201
if not self.to_branch.has_revision(parent):
202
parents.pop(parents.index(parent))
203
self._copy_inventory(rev_id, inv_xml, parents)
204
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
205
mutter('copied revision %s', rev_id)
208
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
209
self.to_control.add_text('inventory', rev_id,
210
split_lines(inv_xml), parent_ids,
211
self.to_branch.get_transaction())
213
def _copy_new_texts(self, rev_id, inv):
214
"""Copy any new texts occuring in this revision."""
215
# TODO: Rather than writing out weaves every time, hold them
216
# in memory until everything's done? But this way is nicer
217
# if it's interrupted.
218
for path, ie in inv.iter_entries():
219
if ie.revision != rev_id:
221
mutter('%s {%s} is changed in this revision',
223
self._copy_one_weave(rev_id, ie.file_id)
226
def _copy_one_weave(self, rev_id, file_id):
227
"""Copy one file weave."""
228
mutter('copy file {%s} modified in {%s}', file_id, rev_id)
229
if file_id in self.copied_file_ids:
230
mutter('file {%s} already copied', file_id)
232
from_weave = self.from_weaves.get_weave(file_id,
233
self.from_branch.get_transaction())
234
to_weave = self.to_weaves.get_weave_or_empty(file_id,
235
self.to_branch.get_transaction())
237
to_weave.join(from_weave)
238
except errors.WeaveParentMismatch:
239
to_weave.reweave(from_weave)
240
self.to_weaves.put_weave(file_id, to_weave,
241
self.to_branch.get_transaction())
242
self.count_weaves += 1
243
self.copied_file_ids.add(file_id)
244
mutter('copied file {%s}', file_id)
196
# destination is empty, just replace it
197
to_weave = from_weave.copy()
199
self.to_control.put_weave('inventory', to_weave,
200
self.to_repository.get_transaction())
205
class Fetcher(object):
206
"""Backwards compatability glue for branch.fetch()."""
208
@deprecated_method(zero_eight)
209
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
210
"""Please see branch.fetch()."""
211
to_branch.fetch(from_branch, last_revision, pb)