19
from cStringIO import StringIO
20
21
import bzrlib.errors
21
from bzrlib.trace import mutter, note
22
from bzrlib.branch import Branch
22
from bzrlib.trace import mutter, note, warning
23
from bzrlib.branch import Branch, INVENTORY_FILEID, ANCESTRY_FILEID
23
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.
26
46
def greedy_fetch(to_branch, from_branch, revision, pb):
31
51
class Fetcher(object):
32
"""Pull history from one branch to another."""
52
"""Pull history from one branch to another.
55
If set, pull only up to this revision_id.
33
57
def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
34
58
self.to_branch = to_branch
35
59
self.from_branch = from_branch
36
60
self.revision_limit = revision_limit
61
self.failed_revisions = []
38
64
self.pb = bzrlib.ui.ui_factory.progress_bar()
41
self._scan_histories()
42
self.failed_revisions = []
47
def _scan_histories(self):
48
self.from_history = from_branch.revision_history()
49
self.required_revisions = set(from_history)
50
self.to_history = to_branch.revision_history()
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()
51
84
if self.revision_limit:
52
raise NotImplementedError('sorry, revision_limit not handled yet')
53
self.need_revisions = []
54
for rev_id in self.from_history:
55
if not has_revision(self.to_branch):
56
self.need_revisions.append(rev_id)
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)
57
114
mutter('need to get revision {%s}', rev_id)
61
while self.need_revisions:
62
rev_id = self.need_revisions.pop()
63
mutter('try 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)
69
174
def has_revision(branch, revision_id):
71
branch.get_revision_xml(revision_id)
176
branch.get_revision_xml_file(revision_id)
73
178
except bzrlib.errors.NoSuchRevision: