1
# Copyright (C) 2006-2009 by Jelmer Vernooij
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 3 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
16
"""Upgrading revisions made with older versions of the mapping."""
19
from bzrlib.errors import (
25
from bzrlib.trace import info
29
class RebaseNotPresent(DependencyNotPresent):
30
_fmt = "Unable to import bzr-rebase (required for upgrade support): %(error)s"
32
def __init__(self, error):
33
DependencyNotPresent.__init__(self, 'bzr-rebase', error)
36
def check_rebase_version(min_version):
37
"""Check what version of bzr-rebase is installed.
39
Raises an exception when the version installed is older than
42
:raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
45
from bzrlib.plugins.rebase import version_info as rebase_version_info
46
if rebase_version_info[:len(min_version)] < min_version:
47
raise RebaseNotPresent("Version %r present, at least %r required"
48
% (rebase_version_info, min_version))
49
except ImportError, e:
50
raise RebaseNotPresent(e)
54
class UpgradeChangesContent(BzrError):
55
"""Inconsistency was found upgrading the mapping of a revision."""
56
_fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
58
def __init__(self, revid):
63
def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
64
"""Create a new revision id for an upgraded version of a revision.
66
Prevents suffix to be appended needlessly.
68
:param revid: Original revision id.
69
:return: New revision id
71
if revid.endswith(upgrade_suffix):
72
return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
74
return revid + mapping_suffix + upgrade_suffix
77
def determine_fileid_renames(old_inv, new_inv):
78
"""Determine the file ids based on a old and a new inventory that
81
:param old_inv: Old inventory
82
:param new_inv: New inventory
83
:return: Dictionary a (old_id, new_id) tuple for each path in the
87
if len(old_inv) != len(new_inv):
88
raise AssertionError("Inventories are not of the same size")
89
for old_file_id in old_inv:
90
new_file_id = new_inv.path2id(old_inv.id2path(old_file_id))
91
if new_file_id is None:
93
"Unable to find %s in new inventory" % old_file_id)
94
if new_file_id != old_file_id:
95
ret[new_inv.id2path(new_file_id)] = (old_file_id, new_file_id)
99
def update_workinginv_fileids(wt, old_inv, new_inv):
100
"""Update all file ids in wt according to old_tree/new_tree.
102
old_tree and new_tree should be two RevisionTree's that differ only
105
fileid_renames = determine_fileid_renames(old_inv, new_inv)
109
# Adjust file ids in working tree
110
# Sorted, so we process parents before children
111
for path in sorted(fileid_renames.keys(), reverse=True):
113
old_fileids.append(fileid_renames[path][0])
114
new_fileids.append((path, fileid_renames[path][1]))
116
new_root_id = fileid_renames[path][1]
117
new_fileids.reverse()
118
wt.unversion(old_fileids)
119
if new_root_id is not None:
120
wt.set_root_id(new_root_id)
121
wt.add([x[0] for x in new_fileids], [x[1] for x in new_fileids])
122
wt.set_last_revision(new_inv.revision_id)
125
def upgrade_workingtree(wt, foreign_repository, new_mapping, mapping_registry,
126
allow_changes=False, verbose=False):
127
"""Upgrade a working tree.
129
:param foreign_repository: Foreign repository object
133
old_revid = wt.last_revision()
134
revid_renames = upgrade_branch(wt.branch, foreign_repository, new_mapping=new_mapping,
135
mapping_registry=mapping_registry,
136
allow_changes=allow_changes, verbose=verbose)
137
last_revid = wt.branch.last_revision()
138
if old_revid == last_revid:
140
old_inv = wt.branch.repository.get_inventory(old_revid)
141
new_inv = wt.branch.repository.get_inventory(last_revid)
142
update_workinginv_fileids(wt, old_inv, new_inv)
149
def upgrade_tags(tags, repository, foreign_repository, new_mapping, mapping_registry,
150
allow_changes=False, verbose=False, branch_renames=None):
151
"""Upgrade a tags dictionary."""
153
if branch_renames is not None:
154
renames.update(branch_renames)
155
pb = ui.ui_factory.nested_progress_bar()
157
tags_dict = tags.get_tag_dict()
158
for i, (name, revid) in enumerate(tags_dict.items()):
159
pb.update("upgrading tags", i, len(tags_dict))
160
if not revid in renames:
161
renames.update(upgrade_repository(repository, foreign_repository,
162
revision_id=revid, new_mapping=new_mapping,
163
mapping_registry=mapping_registry,
164
allow_changes=allow_changes, verbose=verbose))
166
tags.set_tag(name, renames[revid])
171
def upgrade_branch(branch, foreign_repository, new_mapping,
172
mapping_registry, allow_changes=False, verbose=False):
173
"""Upgrade a branch to the current mapping version.
175
:param branch: Branch to upgrade.
176
:param foreign_repository: Repository to fetch new revisions from
177
:param allow_changes: Allow changes in mappings.
178
:param verbose: Whether to print verbose list of rewrites
180
revid = branch.last_revision()
181
renames = upgrade_repository(branch.repository, foreign_repository,
182
revision_id=revid, new_mapping=new_mapping,
183
mapping_registry=mapping_registry,
184
allow_changes=allow_changes, verbose=verbose)
185
upgrade_tags(branch.tags, branch.repository, foreign_repository,
186
new_mapping=new_mapping, mapping_registry=mapping_registry,
187
allow_changes=allow_changes, verbose=verbose, branch_renames=renames)
189
branch.generate_revision_history(renames[revid])
193
def check_revision_changed(oldrev, newrev):
194
"""Check if two revisions are different. This is exactly the same
195
as Revision.equals() except that it does not check the revision_id."""
196
if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
197
newrev.timestamp != oldrev.timestamp or
198
newrev.message != oldrev.message or
199
newrev.timezone != oldrev.timezone or
200
newrev.committer != oldrev.committer or
201
newrev.properties != oldrev.properties):
202
raise UpgradeChangesContent(oldrev.revision_id)
205
def generate_upgrade_map(revs, mapping_registry, determine_upgraded_revid):
206
"""Generate an upgrade map for use by bzr-rebase.
208
:param new_mapping: Mapping to upgrade revisions to.
209
:param revs: Iterator over revisions to upgrade.
210
:return: Map from old revids as keys, new revids as values stored in a
214
# Create a list of revisions that can be renamed during the upgrade
216
assert isinstance(revid, str)
218
(foreign_revid, old_mapping) = mapping_registry.parse_revision_id(revid)
219
except InvalidRevisionId:
220
# Not a foreign revision, nothing to do
222
newrevid = determine_upgraded_revid(foreign_revid)
223
if newrevid in (revid, None):
225
rename_map[revid] = newrevid
228
MIN_REBASE_VERSION = (0, 4, 3)
230
def create_upgrade_plan(repository, foreign_repository, new_mapping,
231
mapping_registry, revision_id=None, allow_changes=False):
232
"""Generate a rebase plan for upgrading revisions.
234
:param repository: Repository to do upgrade in
235
:param foreign_repository: Subversion repository to fetch new revisions from.
236
:param new_mapping: New mapping to use.
237
:param revision_id: Revision to upgrade (None for all revisions in
239
:param allow_changes: Whether an upgrade is allowed to change the contents
241
:return: Tuple with a rebase plan and map of renamed revisions.
243
from bzrlib.plugins.rebase.rebase import generate_transpose_plan
244
check_rebase_version(MIN_REBASE_VERSION)
246
graph = repository.get_graph()
247
if revision_id is None:
248
potential = repository.all_revision_ids()
250
potential = itertools.imap(lambda (rev, parents): rev,
251
graph.iter_ancestry([revision_id]))
253
def determine_upgraded_revid(foreign_revid):
254
# FIXME: Try all mappings until new_mapping rather than just new_mapping
255
new_revid = new_mapping.revision_id_foreign_to_bzr(foreign_revid)
256
# Make sure the revision is there
257
if not repository.has_revision(new_revid):
259
repository.fetch(foreign_repository, new_revid)
260
except NoSuchRevision:
264
upgrade_map = generate_upgrade_map(potential, mapping_registry, determine_upgraded_revid)
266
if not allow_changes:
267
for oldrevid, newrevid in upgrade_map.iteritems():
268
oldrev = repository.get_revision(oldrevid)
269
newrev = repository.get_revision(newrevid)
270
check_revision_changed(oldrev, newrev)
272
if revision_id is None:
273
heads = repository.all_revision_ids()
275
heads = [revision_id]
277
plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map,
278
graph, lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
279
def remove_parents((oldrevid, (newrevid, parents))):
280
return (oldrevid, newrevid)
281
upgrade_map.update(dict(map(remove_parents, plan.iteritems())))
283
return (plan, upgrade_map)
286
def upgrade_repository(repository, foreign_repository, new_mapping,
287
mapping_registry, revision_id=None, allow_changes=False,
289
"""Upgrade the revisions in repository until the specified stop revision.
291
:param repository: Repository in which to upgrade.
292
:param foreign_repository: Repository to fetch new revisions from.
293
:param new_mapping: New mapping.
294
:param revision_id: Revision id up until which to upgrade, or None for
296
:param allow_changes: Allow changes to mappings.
297
:param verbose: Whether to print list of rewrites
298
:return: Dictionary of mapped revisions
300
check_rebase_version(MIN_REBASE_VERSION)
301
from bzrlib.plugins.rebase.rebase import (
302
replay_snapshot, rebase, rebase_todo)
304
# Find revisions that need to be upgraded, create
305
# dictionary with revision ids in key, new parents in value
307
repository.lock_write()
308
foreign_repository.lock_read()
309
(plan, revid_renames) = create_upgrade_plan(repository, foreign_repository,
310
new_mapping, mapping_registry,
311
revision_id=revision_id,
312
allow_changes=allow_changes)
314
for revid in rebase_todo(repository, plan):
315
info("%s -> %s" % (revid, plan[revid][0]))
316
rebase(repository, plan, replay_snapshot)
320
foreign_repository.unlock()