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.revision import Revision
21
from bzrlib.trace import info
25
class RebaseNotPresent(DependencyNotPresent):
26
_fmt = "Unable to import bzr-rebase (required for upgrade support): %(error)s"
28
def __init__(self, error):
29
DependencyNotPresent.__init__(self, 'bzr-rebase', error)
32
def check_rebase_version(min_version):
33
"""Check what version of bzr-rebase is installed.
35
Raises an exception when the version installed is older than
38
:raises RebaseNotPresent: Raised if bzr-rebase is not installed or too old.
41
from bzrlib.plugins.rebase import version_info as rebase_version_info
42
if rebase_version_info[:2] < min_version:
43
raise RebaseNotPresent("Version %r present, at least %r required"
44
% (rebase_version_info, min_version))
45
except ImportError, e:
46
raise RebaseNotPresent(e)
50
class UpgradeChangesContent(BzrError):
51
"""Inconsistency was found upgrading the mapping of a revision."""
52
_fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
54
def __init__(self, revid):
59
def create_upgraded_revid(revid, mapping_suffix, upgrade_suffix="-upgrade"):
60
"""Create a new revision id for an upgraded version of a revision.
62
Prevents suffix to be appended needlessly.
64
:param revid: Original revision id.
65
:return: New revision id
67
if revid.endswith(upgrade_suffix):
68
return revid[0:revid.rfind("-svn")] + mapping_suffix + upgrade_suffix
70
return revid + mapping_suffix + upgrade_suffix
73
def determine_fileid_renames(old_tree, new_tree):
74
for old_file_id in old_tree:
75
new_file_id = new_tree.path2id(old_tree.id2path(old_file_id))
76
if old_file_id == new_file_id:
78
if new_file_id is not None:
79
yield new_tree.id2path(new_file_id), old_file_id, new_file_id
82
def upgrade_workingtree(wt, foreign_repository, new_mapping, mapping_registry,
83
allow_changes=False, verbose=False):
84
"""Upgrade a working tree.
86
:param foreign_repository: Foreign repository object
90
old_revid = wt.last_revision()
91
revid_renames = upgrade_branch(wt.branch, foreign_repository, new_mapping=new_mapping,
92
mapping_registry=mapping_registry,
93
allow_changes=allow_changes, verbose=verbose)
94
last_revid = wt.branch.last_revision()
95
if old_revid == last_revid:
98
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))])
102
# Adjust file ids in working tree
103
for path in sorted(fileid_renames.keys(), reverse=True):
105
old_fileids.append(fileid_renames[path][0])
106
new_fileids.append((path, fileid_renames[path][1]))
108
new_root_id = fileid_renames[path][1]
109
new_fileids.reverse()
110
wt.unversion(old_fileids)
111
if new_root_id is not None:
112
wt.set_root_id(new_root_id)
113
wt.add([x[0] for x in new_fileids], [x[1] for x in new_fileids])
114
wt.set_last_revision(last_revid)
121
def upgrade_tags(tags, repository, foreign_repository, new_mapping, mapping_registry,
122
allow_changes=False, verbose=False, branch_renames=None):
123
"""Upgrade a tags dictionary."""
124
pb = ui.ui_factory.nested_progress_bar()
126
tags_dict = tags.get_tag_dict()
127
for i, (name, revid) in enumerate(tags_dict.items()):
128
pb.update("upgrading tags", i, len(tags_dict))
129
if branch_renames is not None and revid in branch_renames:
130
renames = branch_renames
132
renames = upgrade_repository(repository, foreign_repository,
133
revision_id=revid, new_mapping=new_mapping,
134
mapping_registry=mapping_registry,
135
allow_changes=allow_changes, verbose=verbose)
137
tags.set_tag(name, renames[revid])
142
def upgrade_branch(branch, foreign_repository, new_mapping,
143
mapping_registry, allow_changes=False, verbose=False):
144
"""Upgrade a branch to the current mapping version.
146
:param branch: Branch to upgrade.
147
:param foreign_repository: Repository to fetch new revisions from
148
:param allow_changes: Allow changes in mappings.
149
:param verbose: Whether to print verbose list of rewrites
151
revid = branch.last_revision()
152
renames = upgrade_repository(branch.repository, foreign_repository,
153
revision_id=revid, new_mapping=new_mapping,
154
mapping_registry=mapping_registry,
155
allow_changes=allow_changes, verbose=verbose)
156
upgrade_tags(branch.tags, branch.repository, foreign_repository,
157
new_mapping=new_mapping, mapping_registry=mapping_registry,
158
allow_changes=allow_changes, verbose=verbose)
160
branch.generate_revision_history(renames[revid])
164
def check_revision_changed(oldrev, newrev):
165
"""Check if two revisions are different. This is exactly the same
166
as Revision.equals() except that it does not check the revision_id."""
167
if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
168
newrev.timestamp != oldrev.timestamp or
169
newrev.message != oldrev.message or
170
newrev.timezone != oldrev.timezone or
171
newrev.committer != oldrev.committer or
172
newrev.properties != oldrev.properties):
173
raise UpgradeChangesContent(oldrev.revision_id)
176
def generate_upgrade_map(new_mapping, revs, mapping_registry):
177
"""Generate an upgrade map for use by bzr-rebase.
179
:param new_mapping: Mapping to upgrade revisions to.
180
:param revs: Iterator over revisions to upgrade.
181
:return: Map from old revids as keys, new revids as values stored in a
185
# Create a list of revisions that can be renamed during the upgrade
187
assert isinstance(revid, str)
189
(foreign_revid, _) = mapping_registry.parse_revision_id(revid)
190
except InvalidRevisionId:
191
# Not a foreign revision, nothing to do
193
newrevid = new_mapping.revision_id_foreign_to_bzr(foreign_revid)
194
if revid == newrevid:
196
rename_map[revid] = newrevid
200
MIN_REBASE_VERSION = (0, 4)
202
def create_upgrade_plan(repository, foreign_repository, new_mapping,
203
mapping_registry, revision_id=None, allow_changes=False):
204
"""Generate a rebase plan for upgrading revisions.
206
:param repository: Repository to do upgrade in
207
:param foreign_repository: Subversion repository to fetch new revisions from.
208
:param new_mapping: New mapping to use.
209
:param revision_id: Revision to upgrade (None for all revisions in
211
:param allow_changes: Whether an upgrade is allowed to change the contents
213
:return: Tuple with a rebase plan and map of renamed revisions.
215
from bzrlib.plugins.rebase.rebase import generate_transpose_plan
216
check_rebase_version(MIN_REBASE_VERSION)
218
graph = repository.get_graph()
219
if revision_id is None:
220
potential = repository.all_revision_ids()
222
potential = itertools.imap(lambda (rev, parents): rev,
223
graph.iter_ancestry([revision_id]))
224
upgrade_map = generate_upgrade_map(new_mapping, potential, mapping_registry)
226
# Make sure all the required current version revisions are present
227
for revid in upgrade_map.values():
228
if not repository.has_revision(revid):
229
repository.fetch(foreign_repository, revid)
231
if not allow_changes:
232
for oldrevid, newrevid in upgrade_map.items():
233
oldrev = repository.get_revision(oldrevid)
234
newrev = repository.get_revision(newrevid)
235
check_revision_changed(oldrev, newrev)
237
if revision_id is None:
238
heads = repository.all_revision_ids()
240
heads = [revision_id]
242
plan = generate_transpose_plan(graph.iter_ancestry(heads), upgrade_map,
243
graph, lambda revid: create_upgraded_revid(revid, new_mapping.upgrade_suffix))
244
def remove_parents((oldrevid, (newrevid, parents))):
245
return (oldrevid, newrevid)
246
upgrade_map.update(dict(map(remove_parents, plan.items())))
248
return (plan, upgrade_map)
251
def upgrade_repository(repository, foreign_repository, new_mapping,
252
mapping_registry, revision_id=None, allow_changes=False,
254
"""Upgrade the revisions in repository until the specified stop revision.
256
:param repository: Repository in which to upgrade.
257
:param foreign_repository: Repository to fetch new revisions from.
258
:param new_mapping: New mapping.
259
:param revision_id: Revision id up until which to upgrade, or None for
261
:param allow_changes: Allow changes to mappings.
262
:param verbose: Whether to print list of rewrites
263
:return: Dictionary of mapped revisions
265
check_rebase_version(MIN_REBASE_VERSION)
266
from bzrlib.plugins.rebase.rebase import (
267
replay_snapshot, rebase, rebase_todo)
269
# Find revisions that need to be upgraded, create
270
# dictionary with revision ids in key, new parents in value
272
repository.lock_write()
273
foreign_repository.lock_read()
274
(plan, revid_renames) = create_upgrade_plan(repository, foreign_repository,
275
new_mapping, mapping_registry,
276
revision_id=revision_id,
277
allow_changes=allow_changes)
279
for revid in rebase_todo(repository, plan):
280
info("%s -> %s" % (revid, plan[revid][0]))
281
def fix_revid(revid):
283
(foreign_revid, mapping) = mapping_registry.parse_revision_id(revid)
284
except InvalidRevisionId:
286
return new_mapping.revision_id_foreign_to_bzr(foreign_revid)
287
def replay(repository, oldrevid, newrevid, new_parents):
288
return replay_snapshot(repository, oldrevid, newrevid, new_parents,
289
revid_renames, fix_revid)
290
rebase(repository, plan, replay)
294
foreign_repository.unlock()