68
64
return f.count_copied, f.failed_revisions
71
class RepoFetcher(object):
72
"""Pull revisions and texts from one repository to another.
75
if set, try to limit to the data this revision references.
78
count_copied -- number of revisions copied
79
count_weaves -- number of file weaves copied
81
def __init__(self, to_repository, from_repository, last_revision=None, pb=None):
82
if to_repository.bzrdir.transport.base == from_repository.bzrdir.transport.base:
83
raise Exception("can't fetch from a repository to itself %s, %s" %
84
(from_repository.bzrdir.transport.base,
85
to_repository.bzrdir.transport.base))
86
self.to_repository = to_repository
87
self.from_repository = from_repository
88
# must not mutate self._last_revision as its potentially a shared instance
89
self._last_revision = last_revision
91
self.pb = bzrlib.ui.ui_factory.progress_bar()
94
self.from_repository.lock_read()
96
self.to_repository.lock_write()
100
self.to_repository.unlock()
102
self.from_repository.unlock()
105
"""Primary worker function.
107
This initialises all the needed variables, and then fetches the
108
requested revisions, finally clearing the progress bar.
110
self.to_weaves = self.to_repository.weave_store
111
self.to_control = self.to_repository.control_weaves
112
self.from_weaves = self.from_repository.weave_store
113
self.from_control = self.from_repository.control_weaves
114
self.failed_revisions = []
115
self.count_copied = 0
117
self.count_weaves = 0
118
self.copied_file_ids = set()
119
self.file_ids_names = {}
121
revs = self._revids_to_fetch()
124
self._fetch_weave_texts(revs)
125
self._fetch_inventory_weave(revs)
126
self._fetch_revision_texts(revs)
127
self.count_copied += len(revs)
131
def _revids_to_fetch(self):
132
mutter('fetch up to rev {%s}', self._last_revision)
133
if self._last_revision is NULL_REVISION:
134
# explicit limit of no revisions needed
136
if (self._last_revision != None and
137
self.to_repository.has_revision(self._last_revision)):
141
return self.to_repository.missing_revision_ids(self.from_repository,
143
except errors.NoSuchRevision:
144
raise InstallFailed([self._last_revision])
146
def _fetch_revision_texts(self, revs):
147
self.to_repository.revision_store.copy_multi(
148
self.from_repository.revision_store,
152
def _fetch_weave_texts(self, revs):
153
file_ids = self.from_repository.fileid_involved_by_set(revs)
155
num_file_ids = len(file_ids)
156
for file_id in file_ids:
157
self.pb.update("merge weave merge", count, num_file_ids)
159
to_weave = self.to_weaves.get_weave_or_empty(file_id,
160
self.to_repository.get_transaction())
161
from_weave = self.from_weaves.get_weave(file_id,
162
self.from_repository.get_transaction())
164
if to_weave.numversions() > 0:
165
# destination has contents, must merge
167
to_weave.join(from_weave)
168
except errors.WeaveParentMismatch:
169
to_weave.reweave(from_weave)
171
# destination is empty, just replace it
172
to_weave = from_weave.copy()
174
self.to_weaves.put_weave(file_id, to_weave,
175
self.to_repository.get_transaction())
178
def _fetch_inventory_weave(self, revs):
179
self.pb.update("inventory fetch", 0, 2)
180
from_weave = self.from_repository.get_inventory_weave()
181
to_weave = self.to_repository.get_inventory_weave()
182
self.pb.update("inventory fetch", 1, 2)
183
to_weave = self.to_control.get_weave('inventory',
184
self.to_repository.get_transaction())
185
self.pb.update("inventory fetch", 2, 2)
187
if to_weave.numversions() > 0:
188
# destination has contents, must merge
190
to_weave.join(from_weave, pb=self.pb, msg='merge inventory')
191
except errors.WeaveParentMismatch:
192
to_weave.reweave(from_weave, pb=self.pb, msg='reweave inventory')
194
# destination is empty, just replace it
195
to_weave = from_weave.copy()
197
self.to_control.put_weave('inventory', to_weave,
198
self.to_repository.get_transaction())
203
68
class Fetcher(object):
204
69
"""Pull revisions and texts from one branch to another.
218
83
count_copied -- number of revisions copied
220
count_weaves -- number of file weaves copied
85
count_texts -- number of file texts copied
222
87
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
223
if to_branch.base == from_branch.base:
224
raise Exception("can't fetch from a branch to itself %s, %s" %
225
(from_branch.base, to_branch.base))
88
if to_branch == from_branch:
89
raise Exception("can't fetch from a branch to itself")
227
90
self.to_branch = to_branch
91
self.to_weaves = to_branch.weave_store
92
self.to_control = to_branch.control_weaves
228
93
self.from_branch = from_branch
229
self._last_revision = last_revision
94
self.from_weaves = from_branch.weave_store
95
self.from_control = from_branch.control_weaves
96
self.failed_revisions = []
231
101
self.pb = bzrlib.ui.ui_factory.progress_bar()
234
self.from_branch.lock_read()
236
self.to_branch.lock_write()
240
self.to_branch.unlock()
242
self.from_branch.unlock()
245
self._find_last_revision()
246
repo_fetcher = RepoFetcher(to_repository=self.to_branch.repository,
247
from_repository=self.from_branch.repository,
249
last_revision=self._last_revision)
250
self.failed_revisions = repo_fetcher.failed_revisions
251
self.count_copied = repo_fetcher.count_copied
252
self.count_total = repo_fetcher.count_total
253
self.count_weaves = repo_fetcher.count_weaves
254
self.copied_file_ids = repo_fetcher.copied_file_ids
256
def _find_last_revision(self):
105
self.last_revision = self._find_last_revision(last_revision)
106
except NoSuchRevision, e:
107
mutter('failed getting last revision: %s', e)
108
raise InstallFailed([last_revision])
109
mutter('fetch up to rev {%s}', self.last_revision)
111
revs_to_fetch = self._compare_ancestries()
113
raise InstallFailed([self.last_revision])
114
self._copy_revisions(revs_to_fetch)
115
self.new_ancestry = revs_to_fetch
118
def _find_last_revision(self, last_revision):
257
119
"""Find the limiting source revision.
259
121
Every ancestor of that revision will be merged across.
261
123
Returns the revision_id, or returns None if there's no history
262
124
in the source branch."""
263
if self._last_revision:
265
125
self.pb.update('get source history')
266
126
from_history = self.from_branch.revision_history()
267
127
self.pb.update('get destination history')
269
self._last_revision = from_history[-1]
271
# no history in the source branch
272
self._last_revision = NULL_REVISION
129
self.from_branch.get_revision(last_revision)
132
return from_history[-1]
134
return None # no history in the source branch
137
def _compare_ancestries(self):
138
"""Get a list of revisions that must be copied.
140
That is, every revision that's in the ancestry of the source
141
branch and not in the destination branch."""
142
self.pb.update('get source ancestry')
143
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
145
dest_last_rev = self.to_branch.last_revision()
146
self.pb.update('get destination ancestry')
148
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
151
ss = set(dest_ancestry)
153
for rev_id in self.from_ancestry:
155
to_fetch.append(rev_id)
156
mutter('need to get revision {%s}', rev_id)
157
mutter('need to get %d revisions in total', len(to_fetch))
158
self.count_total = len(to_fetch)
161
def _copy_revisions(self, revs_to_fetch):
163
for rev_id in revs_to_fetch:
167
if self.to_branch.has_revision(rev_id):
169
self.pb.update('fetch revision', i, self.count_total)
170
self._copy_one_revision(rev_id)
171
self.count_copied += 1
174
def _copy_one_revision(self, rev_id):
175
"""Copy revision and everything referenced by it."""
176
mutter('copying revision {%s}', rev_id)
177
rev_xml = self.from_branch.get_revision_xml(rev_id)
178
inv_xml = self.from_branch.get_inventory_xml(rev_id)
179
rev = serializer_v5.read_revision_from_string(rev_xml)
180
inv = serializer_v5.read_inventory_from_string(inv_xml)
181
assert rev.revision_id == rev_id
182
assert rev.inventory_sha1 == sha_string(inv_xml)
183
mutter(' commiter %s, %d parents',
186
self._copy_new_texts(rev_id, inv)
187
parents = rev.parent_ids
188
for parent in parents:
189
if not self.to_branch.has_revision(parent):
190
parents.pop(parents.index(parent))
191
self._copy_inventory(rev_id, inv_xml, parents)
192
self._copy_ancestry(rev_id, parents)
193
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
196
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
197
self.to_control.add_text('inventory', rev_id,
198
split_lines(inv_xml), parent_ids)
201
def _copy_ancestry(self, rev_id, parent_ids):
202
ancestry_lines = self.from_control.get_lines('ancestry', rev_id)
203
self.to_control.add_text('ancestry', rev_id, ancestry_lines,
207
def _copy_new_texts(self, rev_id, inv):
208
"""Copy any new texts occuring in this revision."""
209
# TODO: Rather than writing out weaves every time, hold them
210
# in memory until everything's done? But this way is nicer
211
# if it's interrupted.
212
for path, ie in inv.iter_entries():
213
if not ie.has_text():
215
if ie.revision != rev_id:
217
mutter('%s {%s} is changed in this revision',
219
self._copy_one_text(rev_id, ie.file_id)
222
def _copy_one_text(self, rev_id, file_id):
223
"""Copy one file text."""
224
mutter('copy text version {%s} of file {%s}',
226
from_weave = self.from_weaves.get_weave(file_id)
227
from_idx = from_weave.lookup(rev_id)
228
from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
229
text_lines = from_weave.get(from_idx)
230
to_weave = self.to_weaves.get_weave_or_empty(file_id)
231
to_parents = map(to_weave.lookup, from_parents)
232
# it's ok to add even if the text is already there
233
to_weave.add(rev_id, to_parents, text_lines)
234
self.to_weaves.put_weave(file_id, to_weave)
235
self.count_texts += 1