1
# Copyright (C) 2006,2008 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 BzrError, InvalidRevisionId, DependencyNotPresent
20
from bzrlib.trace import info
24
class RebaseNotPresent(DependencyNotPresent):
25
_fmt = "Unable to import bzr-rebase (required for upgrade support): %(error)s"
27
def __init__(self, error):
28
DependencyNotPresent.__init__(self, 'bzr-rebase', error)
31
def check_rebase_version(min_version):
32
"""Check what version of bzr-rebase is installed.
34
Raises an exception when the version installed is older than
37
:raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
40
from bzrlib.plugins.rebase import version_info as rebase_version_info
41
if rebase_version_info[:2] < min_version:
42
raise RebaseNotPresent("Version %r present, at least %r required"
43
% (rebase_version_info, min_version))
44
except ImportError, e:
45
raise RebaseNotPresent(e)
49
class UpgradeChangesContent(BzrError):
50
"""Inconsistency was found upgrading the mapping of a revision."""
51
_fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
53
def __init__(self, revid):
58
def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
59
"""Create a new revision id for an upgraded version of a revision.
61
Prevents suffix to be appended needlessly.
63
:param revid: Original revision id.
64
:return: New revision id
66
if revid.endswith(upgrade_suffix):
67
return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
69
return revid + mapping_suffix + upgrade_suffix
72
def determine_fileid_renames(old_tree, new_tree):
73
for old_file_id in old_tree:
74
new_file_id = new_tree.path2id(old_tree.id2path(old_file_id))
75
if old_file_id == new_file_id:
77
if new_file_id is not None:
78
yield new_tree.id2path(new_file_id), old_file_id, new_file_id
81
def upgrade_workingtree(wt, foreign_repository, new_mapping, mapping_registry,
82
allow_changes=False, verbose=False):
83
"""Upgrade a working tree.
85
:param foreign_repository: Foreign repository object
89
old_revid = wt.last_revision()
90
revid_renames = upgrade_branch(wt.branch, foreign_repository, new_mapping=new_mapping,
91
mapping_registry=mapping_registry,
92
allow_changes=allow_changes, verbose=verbose)
93
last_revid = wt.branch.last_revision()
94
if old_revid == last_revid:
97
fileid_renames = dict([(path, (old_fileid, new_fileid)) for (path, old_fileid, new_fileid) in determine_fileid_renames(wt.branch.repository.revision_tree(old_revid), wt.branch.repository.revision_tree(last_revid))])
101
# Adjust file ids in working tree
102
for path in sorted(fileid_renames.keys(), reverse=True):
104
old_fileids.append(fileid_renames[path][0])
105
new_fileids.append((path, fileid_renames[path][1]))
107
new_root_id = fileid_renames[path][1]
108
new_fileids.reverse()
109
wt.unversion(old_fileids)
110
if new_root_id is not None:
111
wt.set_root_id(new_root_id)
112
wt.add([x[0] for x in new_fileids], [x[1] for x in new_fileids])
113
wt.set_last_revision(last_revid)
120
def upgrade_tags(tags, repository, foreign_repository, new_mapping, mapping_registry,
121
allow_changes=False, verbose=False, branch_renames=None):
122
"""Upgrade a tags dictionary."""
123
pb = ui.ui_factory.nested_progress_bar()
125
tags_dict = tags.get_tag_dict()
126
for i, (name, revid) in enumerate(tags_dict.items()):
127
pb.update("upgrading tags", i, len(tags_dict))
128
if branch_renames is not None and revid in branch_renames:
129
renames = branch_renames
131
renames = upgrade_repository(repository, foreign_repository,
132
revision_id=revid, new_mapping=new_mapping,
133
mapping_registry=mapping_registry,
134
allow_changes=allow_changes, verbose=verbose)
136
tags.set_tag(name, renames[revid])
141
def upgrade_branch(branch, foreign_repository, new_mapping,
142
mapping_registry, allow_changes=False, verbose=False):
143
"""Upgrade a branch to the current mapping version.
145
:param branch: Branch to upgrade.
146
:param foreign_repository: Repository to fetch new revisions from
147
:param allow_changes: Allow changes in mappings.
148
:param verbose: Whether to print verbose list of rewrites
150
revid = branch.last_revision()
151
renames = upgrade_repository(branch.repository, foreign_repository,
152
revision_id=revid, new_mapping=new_mapping,
153
mapping_registry=mapping_registry,
154
allow_changes=allow_changes, verbose=verbose)
155
upgrade_tags(branch.tags, branch.repository, foreign_repository,
156
new_mapping=new_mapping, mapping_registry=mapping_registry,
157
allow_changes=allow_changes, verbose=verbose)
159
branch.generate_revision_history(renames[revid])
163
def check_revision_changed(oldrev, newrev):
164
"""Check if two revisions are different. This is exactly the same
165
as Revision.equals() except that it does not check the revision_id."""
166
if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
167
newrev.timestamp != oldrev.timestamp or
168
newrev.message != oldrev.message or
169
newrev.timezone != oldrev.timezone or
170
newrev.committer != oldrev.committer or
171
newrev.properties != oldrev.properties):
172
raise UpgradeChangesContent(oldrev.revision_id)
175
def generate_upgrade_map(new_mapping, revs, mapping_registry):
176
"""Generate an upgrade map for use by bzr-rebase.
178
:param new_mapping: Mapping to upgrade revisions to.
179
:param revs: Iterator over revisions to upgrade.
180
:return: Map from old revids as keys, new revids as values stored in a
184
# Create a list of revisions that can be renamed during the upgrade
186
assert isinstance(revid, str)
188
(foreign_revid, _) = mapping_registry.parse_revision_id(revid)
189
except InvalidRevisionId:
190
# Not a foreign revision, nothing to do
192
newrevid = new_mapping.revision_id_foreign_to_bzr(foreign_revid)
193
if revid == newrevid:
195
rename_map[revid] = newrevid
199
MIN_REBASE_VERSION = (0, 4)
201
def create_upgrade_plan(repository, foreign_repository, new_mapping,
202
mapping_registry, revision_id=None, allow_changes=False):
203
"""Generate a rebase plan for upgrading revisions.
205
:param repository: Repository to do upgrade in
206
:param foreign_repository: Subversion repository to fetch new revisions from.
207
:param new_mapping: New mapping to use.
208
:param revision_id: Revision to upgrade (None for all revisions in
210
:param allow_changes: Whether an upgrade is allowed to change the contents
212
:return: Tuple with a rebase plan and map of renamed revisions.
214
from bzrlib.plugins.rebase.rebase import generate_transpose_plan
215
check_rebase_version(MIN_REBASE_VERSION)
217
graph = repository.get_graph()
218
if revision_id is None:
219
potential = repository.all_revision_ids()
221
potential = itertools.imap(lambda (rev, parents): rev,
222
graph.iter_ancestry([revision_id]))
223
upgrade_map = generate_upgrade_map(new_mapping, potential, mapping_registry)
225
# Make sure all the required current version revisions are present
226
for revid in upgrade_map.values():
227
if not repository.has_revision(revid):
228
repository.fetch(foreign_repository, revid)
230
if not allow_changes:
231
for oldrevid, newrevid in upgrade_map.items():
232
oldrev = repository.get_revision(oldrevid)
233
newrev = repository.get_revision(newrevid)
234
check_revision_changed(oldrev, newrev)
236
if revision_id is None:
237
heads = repository.all_revision_ids()
239
heads = [revision_id]
241
plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map,
242
graph, lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
243
def remove_parents((oldrevid, (newrevid, parents))):
244
return (oldrevid, newrevid)
245
upgrade_map.update(dict(map(remove_parents, plan.items())))
247
return (plan, upgrade_map)
250
def upgrade_repository(repository, foreign_repository, new_mapping,
251
mapping_registry, revision_id=None, allow_changes=False,
253
"""Upgrade the revisions in repository until the specified stop revision.
255
:param repository: Repository in which to upgrade.
256
:param foreign_repository: Repository to fetch new revisions from.
257
:param new_mapping: New mapping.
258
:param revision_id: Revision id up until which to upgrade, or None for
260
:param allow_changes: Allow changes to mappings.
261
:param verbose: Whether to print list of rewrites
262
:return: Dictionary of mapped revisions
264
check_rebase_version(MIN_REBASE_VERSION)
265
from bzrlib.plugins.rebase.rebase import (
266
replay_snapshot, rebase, rebase_todo)
268
# Find revisions that need to be upgraded, create
269
# dictionary with revision ids in key, new parents in value
271
repository.lock_write()
272
foreign_repository.lock_read()
273
(plan, revid_renames) = create_upgrade_plan(repository, foreign_repository,
274
new_mapping, mapping_registry,
275
revision_id=revision_id,
276
allow_changes=allow_changes)
278
for revid in rebase_todo(repository, plan):
279
info("%s -> %s" % (revid, plan[revid][0]))
280
def fix_revid(revid):
282
(foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
283
except InvalidRevisionId:
285
return new_mapping.revision_id_foreign_to_bzr(foreign_revid)
286
def replay(repository, oldrevid, newrevid, new_parents):
287
return replay_snapshot(repository, oldrevid, newrevid, new_parents,
288
revid_renames, fix_revid)
289
rebase(repository, plan, replay)
293
foreign_repository.unlock()