20
20
import bzrlib.errors
21
21
from bzrlib.trace import mutter, note, warning
22
from bzrlib.branch import Branch
22
from bzrlib.branch import Branch, INVENTORY_FILEID, ANCESTRY_FILEID
23
23
from bzrlib.progress import ProgressBar
24
24
from bzrlib.xml5 import serializer_v5
25
25
from bzrlib.osutils import sha_string, split_lines
45
45
# XXX: This doesn't handle ghost (not present in branch) revisions at
46
46
# all yet. I'm not sure they really should be supported.
48
# NOTE: This doesn't copy revisions which may be present but not
49
# merged into the last revision. I'm not sure we want to do that.
48
# TODO: This doesn't handle revisions which may be present but not
49
# merged into the last revision.
51
51
# - get a list of revisions that need to be pulled in
52
52
# - for each one, pull in that revision file
62
def greedy_fetch(to_branch, from_branch, revision=None, pb=None):
62
def greedy_fetch(to_branch, from_branch, revision, pb):
63
63
f = Fetcher(to_branch, from_branch, revision, pb)
64
64
return f.count_copied, f.failed_revisions
68
67
class Fetcher(object):
69
"""Pull revisions and texts from one branch to another.
71
This doesn't update the destination's history; that can be done
72
separately if desired.
68
"""Pull history from one branch to another.
75
71
If set, pull only up to this revision_id.
79
last_revision -- if last_revision
80
is given it will be that, otherwise the last revision of
83
count_copied -- number of revisions copied
85
count_texts -- number of file texts copied
87
def __init__(self, to_branch, from_branch, last_revision=None, pb=None):
73
def __init__(self, to_branch, from_branch, revision_limit=None, pb=None):
88
74
self.to_branch = to_branch
89
self.to_weaves = to_branch.weave_store
90
self.to_control = to_branch.control_weaves
91
75
self.from_branch = from_branch
92
self.from_weaves = from_branch.weave_store
93
self.from_control = from_branch.control_weaves
94
76
self.failed_revisions = []
95
77
self.count_copied = 0
96
78
self.count_total = 0
99
80
self.pb = bzrlib.ui.ui_factory.progress_bar()
102
self.last_revision = self._find_last_revision(last_revision)
103
mutter('fetch up to rev {%s}', self.last_revision)
83
self.revision_limit = self._find_revision_limit(revision_limit)
104
84
revs_to_fetch = self._compare_ancestries()
105
85
self._copy_revisions(revs_to_fetch)
106
self.new_ancestry = revs_to_fetch
110
def _find_last_revision(self, last_revision):
89
def _find_revision_limit(self, revision_limit):
111
90
"""Find the limiting source revision.
113
92
Every ancestor of that revision will be merged across.
117
96
self.pb.update('get source history')
118
97
from_history = self.from_branch.revision_history()
119
98
self.pb.update('get destination history')
121
if last_revision not in from_history:
122
raise NoSuchRevision(self.from_branch, last_revision)
100
if revision_limit not in from_history:
101
raise NoSuchRevision(self.from_branch, revision_limit)
103
return revision_limit
125
104
elif from_history:
126
105
return from_history[-1]
134
113
That is, every revision that's in the ancestry of the source
135
114
branch and not in the destination branch."""
136
115
self.pb.update('get source ancestry')
137
self.from_ancestry = self.from_branch.get_ancestry(self.last_revision)
116
self.from_ancestry = self.from_branch.get_ancestry(self.revision_limit)
139
118
dest_last_rev = self.to_branch.last_revision()
140
119
self.pb.update('get destination ancestry')
157
136
def _copy_revisions(self, revs_to_fetch):
159
138
for rev_id in revs_to_fetch:
161
if self.to_branch.has_revision(rev_id):
163
139
self.pb.update('fetch revision', i, self.count_total)
164
140
self._copy_one_revision(rev_id)
165
self.count_copied += 1
168
144
def _copy_one_revision(self, rev_id):
176
152
assert rev.inventory_sha1 == sha_string(inv_xml)
177
153
mutter(' commiter %s, %d parents',
180
156
self._copy_new_texts(rev_id, inv)
181
self._copy_inventory(rev_id, inv_xml, rev.parent_ids)
182
self._copy_ancestry(rev_id, rev.parent_ids)
157
self.to_branch.weave_store.add_text(INVENTORY_FILEID, rev_id,
158
split_lines(inv_xml), rev.parents)
183
159
self.to_branch.revision_store.add(StringIO(rev_xml), rev_id)
186
def _copy_inventory(self, rev_id, inv_xml, parent_ids):
187
self.to_control.add_text('inventory', rev_id,
188
split_lines(inv_xml), parent_ids)
191
def _copy_ancestry(self, rev_id, parent_ids):
192
ancestry_lines = self.from_control.get_lines('ancestry', rev_id)
193
self.to_control.add_text('ancestry', rev_id, ancestry_lines,
197
162
def _copy_new_texts(self, rev_id, inv):
198
163
"""Copy any new texts occuring in this revision."""
212
177
def _copy_one_text(self, rev_id, file_id):
213
178
"""Copy one file text."""
214
mutter('copy text version {%s} of file {%s}',
216
from_weave = self.from_weaves.get_weave(file_id)
179
from_weave = self.from_branch.weave_store.get_weave(file_id)
217
180
from_idx = from_weave.lookup(rev_id)
218
181
from_parents = map(from_weave.idx_to_name, from_weave.parents(from_idx))
219
182
text_lines = from_weave.get(from_idx)
220
to_weave = self.to_weaves.get_weave_or_empty(file_id)
183
to_weave = self.to_branch.weave_store.get_weave_or_empty(file_id)
221
184
to_parents = map(to_weave.lookup, from_parents)
222
185
# it's ok to add even if the text is already there
223
186
to_weave.add(rev_id, to_parents, text_lines)
224
self.to_weaves.put_weave(file_id, to_weave)
225
self.count_texts += 1
187
self.to_branch.weave_store.put_weave(file_id, to_weave)