1
# Copyright (C) 2005 by Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from cStringIO import StringIO
22
from bzrlib.trace import mutter, note, warning
23
from bzrlib.branch import Branch, INVENTORY_FILEID, ANCESTRY_FILEID
24
from bzrlib.progress import ProgressBar
25
from bzrlib.xml5 import serializer_v5
26
from bzrlib.osutils import sha_string, split_lines
28
"""Copying of history from one branch to another.
30
The basic plan is that every branch knows the history of everything
31
that has merged into it. As the first step of a merge, pull, or
32
branch operation we copy history from the source into the destination
35
The copying is done in a slightly complicated order. We don't want to
36
add a revision to the store until everything it refers to is also
37
stored, so that if a revision is present we can totally recreate it.
38
However, we can't know what files are included in a revision until we
39
read its inventory. Therefore, we first pull the XML and hold it in
40
memory until we've updated all of the files referenced.
43
# TODO: Avoid repeatedly opening weaves so many times.
46
def greedy_fetch(to_branch, from_branch, revision, pb):
47
f = Fetcher(to_branch, from_branch, revision, pb)
48
return f.count_copied, f.failed_revisions
51
class Fetcher(object):
52
"""Pull history from one branch to another.
55
If set, pull only up to this revision_id.
57
def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
58
self.to_branch = to_branch
59
self.from_branch = from_branch
60
self.revision_limit = revision_limit
61
self.failed_revisions = []
64
self.pb = bzrlib.ui.ui_factory.progress_bar()
67
self._load_histories()
68
revs_to_fetch = self._compare_ancestries()
69
self._copy_revisions(revs_to_fetch)
70
# - get a list of revisions that need to be pulled in
71
# - for each one, pull in that revision file
72
# and get the inventory, and store the inventory with right
74
# - and get the ancestry, and store that with right parents too
75
# - and keep a note of all file ids and version seen
76
# - then go through all files; for each one get the weave,
77
# and add in all file versions
80
def _load_histories(self):
81
"""Load histories of both branches, up to the limit."""
82
self.from_history = self.from_branch.revision_history()
83
self.to_history = self.to_branch.revision_history()
84
if self.revision_limit:
85
assert isinstance(revision_limit, basestring)
87
rev_index = self.from_history.index(revision_limit)
90
if rev_index is not None:
91
self.from_history = self.from_history[:rev_index + 1]
93
self.from_history = [revision]
96
def _compare_ancestries(self):
97
"""Get a list of revisions that must be copied.
99
That is, every revision that's in the ancestry of the source
100
branch and not in the destination branch."""
101
if self.from_history:
102
self.from_ancestry = self.from_branch.get_ancestry(self.from_history[-1])
104
self.from_ancestry = []
106
self.to_history = self.to_branch.get_ancestry(self.to_history[-1])
109
ss = set(self.to_history)
111
for rev_id in self.from_ancestry:
113
to_fetch.append(rev_id)
114
mutter('need to get revision {%s}', rev_id)
115
mutter('need to get %d revisions in total', len(to_fetch))
120
def _copy_revisions(self, revs_to_fetch):
121
for rev_id in revs_to_fetch:
122
self._copy_one_revision(rev_id)
125
def _copy_one_revision(self, rev_id):
126
"""Copy revision and everything referenced by it."""
127
mutter('copying revision {%s}', rev_id)
128
rev_xml = self.from_branch.get_revision_xml(rev_id)
129
inv_xml = self.from_branch.get_inventory_xml(rev_id)
130
rev = serializer_v5.read_revision_from_string(rev_xml)
131
inv = serializer_v5.read_inventory_from_string(inv_xml)
132
assert rev.revision_id == rev_id
133
assert rev.inventory_sha1 == sha_string(inv_xml)
134
mutter(' commiter %s, %d parents',
137
self._copy_new_texts(rev_id, inv)
138
self.to_branch.weave_store.add_text(INVENTORY_FILEID, rev_id,
139
split_lines(inv_xml), rev.parents)
140
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
143
def _copy_new_texts(self, rev_id, inv):
144
"""Copy any new texts occuring in this revision."""
145
# TODO: Rather than writing out weaves every time, hold them
146
# in memory until everything's done? But this way is nicer
147
# if it's interrupted.
148
for path, ie in inv.iter_entries():
149
if ie.kind != 'file':
151
if ie.text_version != rev_id:
153
mutter('%s {%s} is changed in this revision',
155
self._copy_one_text(rev_id, ie.file_id)
158
def _copy_one_text(self, rev_id, file_id):
159
"""Copy one file text."""
160
from_weave = self.from_branch.weave_store.get_weave(file_id)
161
from_idx = from_weave.lookup(rev_id)
162
from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
163
text_lines = from_weave.get(from_idx)
164
to_weave = self.to_branch.weave_store.get_weave_or_empty(file_id)
165
if rev_id in to_weave._name_map:
166
warning('version {%s} already present in weave of file {%s}',
169
to_parents = map(to_weave.lookup, from_parents)
170
to_weave.add(rev_id, to_parents, text_lines)
171
self.to_branch.weave_store.put_weave(file_id, to_weave)
174
def has_revision(branch, revision_id):
176
branch.get_revision_xml_file(revision_id)
178
except bzrlib.errors.NoSuchRevision:
182
def old_greedy_fetch(to_branch, from_branch, revision=None, pb=None):
183
"""Copy all history from one branch to another.
186
If set, copy only up to this point in the source branch.
188
@returns: number copied, missing ids
190
from_history = from_branch.revision_history()
191
required_revisions = set(from_history)
193
if revision is not None:
194
required_revisions.add(revision)
196
rev_index = from_history.index(revision)
199
if rev_index is not None:
200
from_history = from_history[:rev_index + 1]
202
from_history = [revision]
203
to_history = to_branch.revision_history()
205
for rev_id in from_history:
206
if not has_revision(to_branch, rev_id):
207
missing.append(rev_id)
209
# recurse down through the revision graph, looking for things that
212
while len(missing) > 0:
213
installed, failed = to_branch.install_revisions(from_branch,
214
revision_ids=missing,
217
required_failed = failed.intersection(required_revisions)
218
if len(required_failed) > 0:
219
raise bzrlib.errors.InstallFailed(required_failed)
220
for rev_id in failed:
221
note("Failed to install %s" % rev_id)
222
all_failed.update(failed)
224
for rev_id in missing:
226
revision = from_branch.get_revision(rev_id)
227
except bzrlib.errors.NoSuchRevision:
228
if revision in from_history:
232
for parent in [p.revision_id for p in revision.parents]:
233
if not has_revision(to_branch, parent):
234
new_missing.add(parent)
235
missing = new_missing
236
return count, all_failed
239
def old_install_revisions(branch, other, revision_ids, pb):
240
"""Copy revisions from other branch into branch.
242
This is a lower-level function used by a pull or a merge. It
243
incorporates some history from one branch into another, but
244
does not update the revision history or operate on the working
248
Sequence of revisions to copy.
251
Progress bar for copying.
254
if hasattr(other.revision_store, "prefetch"):
255
other.revision_store.prefetch(revision_ids)
256
if hasattr(other.inventory_store, "prefetch"):
257
other.inventory_store.prefetch(revision_ids)
260
pb = bzrlib.ui.ui_factory.progress_bar()
267
for i, rev_id in enumerate(revision_ids):
268
pb.update('fetching revision', i+1, len(revision_ids))
270
rev = other.get_revision(rev_id)
271
except bzrlib.errors.NoSuchRevision:
275
revisions.append(rev)
276
inv = other.get_inventory(rev_id)
277
for key, entry in inv.iter_entries():
278
if entry.text_id is None:
280
if entry.text_id not in branch.text_store:
281
needed_texts.add(entry.text_id)
285
count, cp_fail = branch.text_store.copy_multi(other.text_store,
287
count, cp_fail = branch.inventory_store.copy_multi(other.inventory_store,
289
count, cp_fail = branch.revision_store.copy_multi(other.revision_store,
292
assert len(cp_fail) == 0
293
return count, failures