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
import bzrlib.errors as errors
23
from bzrlib.errors import InstallFailed, NoSuchRevision, WeaveError
24
from bzrlib.trace import mutter, note, warning
25
from bzrlib.branch import Branch
26
from bzrlib.progress import ProgressBar
27
from bzrlib.xml5 import serializer_v5
28
from bzrlib.osutils import sha_string, split_lines
30
"""Copying of history from one branch to another.
32
The basic plan is that every branch knows the history of everything
33
that has merged into it. As the first step of a merge, pull, or
34
branch operation we copy history from the source into the destination
37
The copying is done in a slightly complicated order. We don't want to
38
add a revision to the store until everything it refers to is also
39
stored, so that if a revision is present we can totally recreate it.
40
However, we can't know what files are included in a revision until we
41
read its inventory. Therefore, we first pull the XML and hold it in
42
memory until we've updated all of the files referenced.
45
# TODO: Avoid repeatedly opening weaves so many times.
47
# XXX: This doesn't handle ghost (not present in branch) revisions at
48
# all yet. I'm not sure they really should be supported.
50
# NOTE: This doesn't copy revisions which may be present but not
51
# merged into the last revision. I'm not sure we want to do that.
53
# - get a list of revisions that need to be pulled in
54
# - for each one, pull in that revision file
55
# and get the inventory, and store the inventory with right
57
# - and get the ancestry, and store that with right parents too
58
# - and keep a note of all file ids and version seen
59
# - then go through all files; for each one get the weave,
60
# and add in all file versions
64
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
65
f = Fetcher(to_branch, from_branch, revision, pb)
66
return f.count_copied, f.failed_revisions
70
class Fetcher(object):
71
"""Pull revisions and texts from one branch to another.
73
This doesn't update the destination's history; that can be done
74
separately if desired.
77
If set, pull only up to this revision_id.
81
last_revision -- if last_revision
82
is given it will be that, otherwise the last revision of
85
count_copied -- number of revisions copied
87
count_weaves -- number of file weaves copied
89
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
90
if to_branch == from_branch:
91
raise Exception("can't fetch from a branch to itself")
92
self.to_branch = to_branch
93
self.to_weaves = to_branch.weave_store
94
self.to_control = to_branch.control_weaves
95
self.from_branch = from_branch
96
self.from_weaves = from_branch.weave_store
97
self.from_control = from_branch.control_weaves
98
self.failed_revisions = []
101
self.count_weaves = 0
102
self.copied_file_ids = set()
104
self.pb = bzrlib.ui.ui_factory.progress_bar()
107
self.from_branch.lock_read()
109
self._fetch_revisions(last_revision)
111
self.from_branch.unlock()
114
def _fetch_revisions(self, last_revision):
115
self.last_revision = self._find_last_revision(last_revision)
116
mutter('fetch up to rev {%s}', self.last_revision)
117
if (self.last_revision is not None and
118
self.to_branch.has_revision(self.last_revision)):
121
revs_to_fetch = self._compare_ancestries()
123
raise InstallFailed([self.last_revision])
124
self._copy_revisions(revs_to_fetch)
125
self.new_ancestry = revs_to_fetch
127
def _find_last_revision(self, last_revision):
128
"""Find the limiting source revision.
130
Every ancestor of that revision will be merged across.
132
Returns the revision_id, or returns None if there's no history
133
in the source branch."""
136
self.pb.update('get source history')
137
from_history = self.from_branch.revision_history()
138
self.pb.update('get destination history')
140
return from_history[-1]
142
return None # no history in the source branch
145
def _compare_ancestries(self):
146
"""Get a list of revisions that must be copied.
148
That is, every revision that's in the ancestry of the source
149
branch and not in the destination branch."""
150
self.pb.update('get source ancestry')
151
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
153
dest_last_rev = self.to_branch.last_revision()
154
self.pb.update('get destination ancestry')
156
dest_ancestry = self.to_branch.get_ancestry(dest_last_rev)
159
ss = set(dest_ancestry)
161
for rev_id in self.from_ancestry:
163
to_fetch.append(rev_id)
164
mutter('need to get revision {%s}', rev_id)
165
mutter('need to get %d revisions in total', len(to_fetch))
166
self.count_total = len(to_fetch)
169
def _copy_revisions(self, revs_to_fetch):
171
for rev_id in revs_to_fetch:
175
if self.to_branch.has_revision(rev_id):
177
self.pb.update('fetch revision', i, self.count_total)
178
self._copy_one_revision(rev_id)
179
self.count_copied += 1
182
def _copy_one_revision(self, rev_id):
183
"""Copy revision and everything referenced by it."""
184
mutter('copying revision {%s}', rev_id)
185
rev_xml = self.from_branch.get_revision_xml(rev_id)
186
inv_xml = self.from_branch.get_inventory_xml(rev_id)
187
rev = serializer_v5.read_revision_from_string(rev_xml)
188
inv = serializer_v5.read_inventory_from_string(inv_xml)
189
assert rev.revision_id == rev_id
190
assert rev.inventory_sha1 == sha_string(inv_xml)
191
mutter(' commiter %s, %d parents',
194
self._copy_new_texts(rev_id, inv)
195
parents = rev.parent_ids
196
new_parents = copy(parents)
197
for parent in parents:
198
if not self.to_branch.has_revision(parent):
199
new_parents.pop(new_parents.index(parent))
200
self._copy_inventory(rev_id, inv_xml, new_parents)
201
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
202
mutter('copied revision %s', rev_id)
204
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
205
self.to_control.add_text('inventory', rev_id,
206
split_lines(inv_xml), parent_ids,
207
self.to_branch.get_transaction())
209
def _copy_new_texts(self, rev_id, inv):
210
"""Copy any new texts occuring in this revision."""
211
# TODO: Rather than writing out weaves every time, hold them
212
# in memory until everything's done? But this way is nicer
213
# if it's interrupted.
214
for path, ie in inv.iter_entries():
215
if ie.revision != rev_id:
217
mutter('%s {%s} is changed in this revision',
219
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)