1 # Copyright (C) 2006 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 2 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."""
18 from bzrlib.errors import BzrError, InvalidRevisionId
19 from bzrlib.trace import info, mutter
20 import bzrlib.ui as ui
22 from errors import RebaseNotPresent
23 from revids import (generate_svn_revision_id, parse_svn_revision_id,
24 MAPPING_VERSION, unescape_svn_path)
25 from scheme import BranchingScheme, guess_scheme_from_branch_path
27 class UpgradeChangesContent(BzrError):
28 """Inconsistency was found upgrading the mapping of a revision."""
29 _fmt = """Upgrade will change contents in revision %(revid)s. Use --allow-changes to override."""
31 def __init__(self, revid):
35 def parse_legacy_revision_id(revid):
36 """Try to parse a legacy Subversion revision id.
38 :param revid: Revision id to parse
39 :return: tuple with (uuid, branch_path, revision number, scheme, mapping version)
41 if revid.startswith("svn-v1:"):
42 revid = revid[len("svn-v1:"):]
44 fash = revid.rindex("-")
45 uuid = revid[at+1:fash]
46 branch_path = unescape_svn_path(revid[fash+1:])
47 revnum = int(revid[0:at])
49 return (uuid, branch_path, revnum, None, 1)
50 elif revid.startswith("svn-v2:"):
51 revid = revid[len("svn-v2:"):]
53 fash = revid.rindex("-")
54 uuid = revid[at+1:fash]
55 branch_path = unescape_svn_path(revid[fash+1:])
56 revnum = int(revid[0:at])
58 return (uuid, branch_path, revnum, None, 2)
59 elif revid.startswith("svn-v3-"):
60 (uuid, bp, rev, scheme) = parse_svn_revision_id(revid)
61 if scheme == "undefined":
63 return (uuid, bp, rev, scheme, 3)
65 raise InvalidRevisionId(revid, None)
68 def create_upgraded_revid(revid):
69 """Create a new revision id for an upgraded version of a revision.
71 Prevents suffix to be appended needlessly.
73 :param revid: Original revision id.
74 :return: New revision id
76 suffix = "-svn%d-upgrade" % MAPPING_VERSION
77 if revid.endswith("-upgrade"):
78 return revid[0:revid.rfind("-svn")] + suffix
83 def upgrade_workingtree(wt, svn_repository, allow_changes=False, verbose=False):
84 upgrade_branch(wt.branch, svn_repository, allow_changes=allow_changes, verbose=verbose)
85 last_revid = wt.branch.last_revision()
86 wt.set_parent_trees([(last_revid, wt.branch.repository.revision_tree(last_revid))])
87 # TODO: Should also adjust file ids in working tree if necessary
90 def upgrade_branch(branch, svn_repository, allow_changes=False, verbose=False):
91 """Upgrade a branch to the current mapping version.
93 :param branch: Branch to upgrade.
94 :param svn_repository: Repository to fetch new revisions from
95 :param allow_changes: Allow changes in mappings.
96 :param verbose: Whether to print verbose list of rewrites
98 revid = branch.last_revision()
99 renames = upgrade_repository(branch.repository, svn_repository,
100 revid, allow_changes=allow_changes, verbose=verbose)
102 branch.generate_revision_history(renames[revid])
105 def check_revision_changed(oldrev, newrev):
106 """Check if two revisions are different. This is exactly the same
107 as Revision.equals() except that it does not check the revision_id."""
108 if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
109 newrev.timestamp != oldrev.timestamp or
110 newrev.message != oldrev.message or
111 newrev.timezone != oldrev.timezone or
112 newrev.committer != oldrev.committer or
113 newrev.properties != oldrev.properties):
114 raise UpgradeChangesContent(oldrev.revision_id)
117 def generate_upgrade_map(revs):
119 pb = ui.ui_factory.nested_progress_bar()
120 # Create a list of revisions that can be renamed during the upgade
123 pb.update('gather revision information', revs.index(revid), len(revs))
125 (uuid, bp, rev, scheme, _) = parse_legacy_revision_id(revid)
126 except InvalidRevisionId:
127 # Not a bzr-svn revision, nothing to do
130 scheme = guess_scheme_from_branch_path(bp)
131 newrevid = generate_svn_revision_id(uuid, rev, bp, scheme)
132 rename_map[revid] = newrevid
139 def create_upgrade_plan(repository, svn_repository, revision_id=None,
140 allow_changes=False):
141 """Generate a rebase plan for upgrading revisions.
143 :param repository: Repository to do upgrade in
144 :param svn_repository: Subversion repository to fetch new revisions from.
145 :param revision_id: Revision to upgrade (None for all revisions in
147 :param allow_changes: Whether an upgrade is allowed to change the contents
149 :return: Tuple with a rebase plan and map of renamed revisions.
152 from bzrlib.plugins.rebase.rebase import generate_transpose_plan
153 except ImportError, e:
154 raise RebaseNotPresent(e)
156 graph = repository.get_revision_graph(revision_id)
157 upgrade_map = generate_upgrade_map(graph.keys())
159 # Make sure all the required current version revisions are present
160 for revid in upgrade_map.values():
161 if not repository.has_revision(revid):
162 repository.fetch(svn_repository, revid)
164 if not allow_changes:
165 for oldrevid, newrevid in upgrade_map.items():
166 oldrev = repository.get_revision(oldrevid)
167 newrev = repository.get_revision(newrevid)
168 check_revision_changed(oldrev, newrev)
170 plan = generate_transpose_plan(graph, upgrade_map,
171 repository.revision_parents,
172 create_upgraded_revid)
173 def remove_parents((oldrevid, (newrevid, parents))):
174 return (oldrevid, newrevid)
175 upgrade_map.update(dict(map(remove_parents, plan.items())))
177 return (plan, upgrade_map)
180 def upgrade_repository(repository, svn_repository, revision_id=None,
181 allow_changes=False, verbose=False):
182 """Upgrade the revisions in repository until the specified stop revision.
184 :param repository: Repository in which to upgrade.
185 :param svn_repository: Repository to fetch new revisions from.
186 :param revision_id: Revision id up until which to upgrade, or None for
188 :param allow_changes: Allow changes to mappings.
189 :param verbose: Whether to print list of rewrites
190 :return: Dictionary of mapped revisions
193 from bzrlib.plugins.rebase.rebase import (
194 replay_snapshot, rebase, rebase_todo)
195 except ImportError, e:
196 raise RebaseNotPresent(e)
198 # Find revisions that need to be upgraded, create
199 # dictionary with revision ids in key, new parents in value
201 repository.lock_write()
202 svn_repository.lock_read()
203 (plan, revid_renames) = create_upgrade_plan(repository, svn_repository,
204 revision_id=revision_id,
205 allow_changes=allow_changes)
207 for revid in rebase_todo(repository, plan):
208 info("%s -> %s" % (revid, plan[revid][0]))
209 def fix_revid(revid):
211 (uuid, bp, rev, scheme, _) = parse_legacy_revision_id(revid)
212 except InvalidRevisionId:
215 scheme = guess_scheme_from_branch_path(bp)
216 return generate_svn_revision_id(uuid, rev, bp, scheme)
217 def replay(repository, oldrevid, newrevid, new_parents):
218 return replay_snapshot(repository, oldrevid, newrevid, new_parents,
219 revid_renames, fix_revid)
220 rebase(repository, plan, replay)
224 svn_repository.unlock()