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.
45
# XXX: This doesn't handle ghost (not present in branch) revisions at
48
# - get a list of revisions that need to be pulled in
49
# - for each one, pull in that revision file
50
# and get the inventory, and store the inventory with right
52
# - and get the ancestry, and store that with right parents too
53
# - and keep a note of all file ids and version seen
54
# - then go through all files; for each one get the weave,
55
# and add in all file versions
59
def greedy_fetch(to_branch, from_branch, revision, pb):
60
f = Fetcher(to_branch, from_branch, revision, pb)
61
return f.count_copied, f.failed_revisions
64
class Fetcher(object):
65
"""Pull history from one branch to another.
68
If set, pull only up to this revision_id.
70
def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
71
self.to_branch = to_branch
72
self.from_branch = from_branch
73
self.revision_limit = revision_limit
74
self.failed_revisions = []
77
self.pb = bzrlib.ui.ui_factory.progress_bar()
80
self._load_histories()
81
revs_to_fetch = self._compare_ancestries()
82
self._copy_revisions(revs_to_fetch)
84
def _load_histories(self):
85
"""Load histories of both branches, up to the limit."""
86
self.from_history = self.from_branch.revision_history()
87
self.to_history = self.to_branch.revision_history()
88
if self.revision_limit:
89
assert isinstance(revision_limit, basestring)
91
rev_index = self.from_history.index(revision_limit)
94
if rev_index is not None:
95
self.from_history = self.from_history[:rev_index + 1]
97
self.from_history = [revision]
100
def _compare_ancestries(self):
101
"""Get a list of revisions that must be copied.
103
That is, every revision that's in the ancestry of the source
104
branch and not in the destination branch."""
105
if self.from_history:
106
self.from_ancestry = self.from_branch.get_ancestry(self.from_history[-1])
108
self.from_ancestry = []
110
self.to_history = self.to_branch.get_ancestry(self.to_history[-1])
113
ss = set(self.to_history)
115
for rev_id in self.from_ancestry:
117
to_fetch.append(rev_id)
118
mutter('need to get revision {%s}', rev_id)
119
mutter('need to get %d revisions in total', len(to_fetch))
124
def _copy_revisions(self, revs_to_fetch):
125
for rev_id in revs_to_fetch:
126
self._copy_one_revision(rev_id)
129
def _copy_one_revision(self, rev_id):
130
"""Copy revision and everything referenced by it."""
131
mutter('copying revision {%s}', rev_id)
132
rev_xml = self.from_branch.get_revision_xml(rev_id)
133
inv_xml = self.from_branch.get_inventory_xml(rev_id)
134
rev = serializer_v5.read_revision_from_string(rev_xml)
135
inv = serializer_v5.read_inventory_from_string(inv_xml)
136
assert rev.revision_id == rev_id
137
assert rev.inventory_sha1 == sha_string(inv_xml)
138
mutter(' commiter %s, %d parents',
141
self._copy_new_texts(rev_id, inv)
142
self.to_branch.weave_store.add_text(INVENTORY_FILEID, rev_id,
143
split_lines(inv_xml), rev.parents)
144
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
147
def _copy_new_texts(self, rev_id, inv):
148
"""Copy any new texts occuring in this revision."""
149
# TODO: Rather than writing out weaves every time, hold them
150
# in memory until everything's done? But this way is nicer
151
# if it's interrupted.
152
for path, ie in inv.iter_entries():
153
if ie.kind != 'file':
155
if ie.text_version != rev_id:
157
mutter('%s {%s} is changed in this revision',
159
self._copy_one_text(rev_id, ie.file_id)
162
def _copy_one_text(self, rev_id, file_id):
163
"""Copy one file text."""
164
from_weave = self.from_branch.weave_store.get_weave(file_id)
165
from_idx = from_weave.lookup(rev_id)
166
from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
167
text_lines = from_weave.get(from_idx)
168
to_weave = self.to_branch.weave_store.get_weave_or_empty(file_id)
169
to_parents = map(to_weave.lookup, from_parents)
170
# it's ok to add even if the text is already there
171
to_weave.add(rev_id, to_parents, text_lines)
172
self.to_branch.weave_store.put_weave(file_id, to_weave)