Fix bug in revid caching.
[jelmer/subvertpy.git] / upgrade.py
1 # Copyright (C) 2006 by Jelmer Vernooij
2
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.
7 #
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.
12 #
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."""
17
18 from bzrlib.config import Config
19 from bzrlib.errors import BzrError, InvalidRevisionId
20 from bzrlib.trace import mutter
21 import bzrlib.ui as ui
22
23 from revids import (generate_svn_revision_id, parse_svn_revision_id, 
24                     MAPPING_VERSION,  unescape_svn_path)
25
26 # Takes an existing Bazaar branch and replaces all old-version mapped revisions 
27 # with new-style revisions mappings. 
28
29 # It checks the sha1s of the contents to make sure that the revision hasn't 
30 # changed. This behaviour can be turned off by specifying --allow-change.
31 #
32 # Usage: svn-upgrade [--allow-change] PATH REPOSITORY
33
34 class UpgradeChangesContent(BzrError):
35     """Inconsistency was found upgrading the mapping of a revision."""
36     _fmt = """Upgrade will change contents in revision %(revid)s."""
37
38     def __init__(self, revid):
39         self.revid = revid
40
41
42 # Change the parent of a revision
43 def change_revision_parent(repository, oldrevid, newrevid, new_parents):
44     assert isinstance(new_parents, list)
45     mutter('creating copy %r of %r with new parents %r' % (newrevid, oldrevid, new_parents))
46     oldrev = repository.get_revision(oldrevid)
47
48     builder = repository.get_commit_builder(branch=None, parents=new_parents, 
49                                   config=Config(),
50                                   committer=oldrev.committer,
51                                   timestamp=oldrev.timestamp,
52                                   timezone=oldrev.timezone,
53                                   revprops=oldrev.properties,
54                                   revision_id=newrevid)
55
56     # Check what new_ie.file_id should be
57     # use old and new parent inventories to generate new_id map
58     old_parents = oldrev.parent_ids
59     new_id = {}
60     for (oldp, newp) in zip(old_parents, new_parents):
61         oldinv = repository.get_revision_inventory(oldp)
62         newinv = repository.get_revision_inventory(newp)
63         for path, ie in oldinv.iter_entries():
64             if newinv.has_filename(path):
65                 new_id[ie.file_id] = newinv.path2id(path)
66
67     mutter('new id %r' % new_id)
68     i = 0
69     class MapTree:
70         def __init__(self, oldtree, map):
71             self.oldtree = oldtree
72             self.map = map
73
74         def old_id(self, file_id):
75             for x in self.map:
76                 if self.map[x] == file_id:
77                     return x
78             return file_id
79
80         def get_file_sha1(self, file_id, path=None):
81             return self.oldtree.get_file_sha1(file_id=self.old_id(file_id), 
82                                               path=path)
83
84         def get_file(self, file_id):
85             return self.oldtree.get_file(self.old_id(file_id=file_id))
86
87         def is_executable(self, file_id, path=None):
88             return self.oldtree.is_executable(self.old_id(file_id=file_id), 
89                                               path=path)
90
91     oldtree = MapTree(repository.revision_tree(oldrevid), new_id)
92     oldinv = repository.get_revision_inventory(oldrevid)
93     total = len(oldinv)
94     pb = ui.ui_factory.nested_progress_bar()
95     try:
96         for path, ie in oldinv.iter_entries():
97             pb.update('upgrading revision', i, total)
98             i += 1
99             new_ie = ie.copy()
100             if new_ie.revision == oldrevid:
101                 new_ie.revision = None
102             def lookup(file_id):
103                 if new_id.has_key(file_id):
104                     return new_id[file_id]
105                 return file_id
106
107             new_ie.file_id = lookup(new_ie.file_id)
108             new_ie.parent_id = lookup(new_ie.parent_id)
109             versionedfile = repository.weave_store.get_weave_or_empty(new_ie.file_id, 
110                     repository.get_transaction())
111             if not versionedfile.has_version(newrevid):
112                 builder.record_entry_contents(new_ie, 
113                        map(repository.get_revision_inventory, new_parents), 
114                        path, oldtree)
115     finally:
116         pb.finished()
117
118     builder.finish_inventory()
119     return builder.commit(oldrev.message)
120
121
122 def parse_legacy_revision_id(revid):
123     if revid.startswith("svn-v1:"):
124         revid = revid[len("svn-v1:"):]
125         at = revid.index("@")
126         fash = revid.rindex("-")
127         uuid = revid[at+1:fash]
128         branch_path = unescape_svn_path(revid[fash+1:])
129         revnum = int(revid[0:at])
130         assert revnum >= 0
131         return (uuid, branch_path, revnum, 1)
132     elif revid.startswith("svn-v2:"):
133         revid = revid[len("svn-v2:"):]
134         at = revid.index("@")
135         fash = revid.rindex("-")
136         uuid = revid[at+1:fash]
137         branch_path = unescape_svn_path(revid[fash+1:])
138         revnum = int(revid[0:at])
139         assert revnum >= 0
140         return (uuid, branch_path, revnum, 2)
141     elif revid.startswith("svn-v3-"):
142         (uuid, bp, rev) = parse_svn_revision_id(revid)
143         return (uuid, bp, rev, 3)
144
145     raise InvalidRevisionId(revid, None)
146
147
148 def create_upgraded_revid(revid):
149     suffix = "-svn%d-upgrade" % MAPPING_VERSION
150     if revid.endswith("-upgrade"):
151         return revid[0:revid.rfind("-svn")] + suffix
152     else:
153         return revid + suffix
154
155
156 def upgrade_branch(branch, svn_repository, allow_change=False):
157     renames = upgrade_repository(branch.repository, svn_repository, 
158               branch.last_revision(), allow_change)
159     mutter('renames %r' % renames)
160     if len(renames) > 0:
161         branch.generate_revision_history(renames[branch.last_revision()])
162
163
164 def revision_changed(oldrev, newrev):
165     if (newrev.inventory_sha1 != oldrev.inventory_sha1 or
166         newrev.timestamp != oldrev.timestamp or
167         newrev.message != oldrev.message or
168         newrev.timezone != oldrev.timezone or
169         newrev.committer != oldrev.committer or
170         newrev.properties != oldrev.properties):
171         return True
172     return False
173
174
175 def upgrade_repository(repository, svn_repository, revision_id=None, 
176                        allow_change=False):
177     needed_revs = []
178     needs_upgrading = []
179     new_parents = {}
180     rename_map = {}
181
182     try:
183         repository.lock_write()
184         svn_repository.lock_read()
185         # Find revisions that need to be upgraded, create
186         # dictionary with revision ids in key, new parents in value
187         graph = repository.get_revision_graph(revision_id)
188         def find_children(revid):
189             for x in graph:
190                 if revid in graph[x]:
191                     yield x
192         pb = ui.ui_factory.nested_progress_bar()
193         i = 0
194         try:
195             for revid in graph:
196                 pb.update('gather revision information', i, len(graph))
197                 i += 1
198                 try:
199                     (uuid, bp, rev, _) = parse_legacy_revision_id(revid)
200                     newrevid = generate_svn_revision_id(uuid, rev, bp)
201                     if svn_repository.has_revision(newrevid):
202                         rename_map[revid] = newrevid
203                         if not repository.has_revision(newrevid):
204                             if not allow_change:
205                                 oldrev = repository.get_revision(revid)
206                                 newrev = svn_repository.get_revision(newrevid)
207                                 if revision_changed(oldrev, newrev):
208                                     raise UpgradeChangesContent(revid)
209                             needed_revs.append(newrevid)
210                         continue
211                 except InvalidRevisionId:
212                     pass
213                 new_parents[revid] = []
214                 for parent in graph[revid]:
215                     try:
216                         (uuid, bp, rev, version) = parse_legacy_revision_id(parent)
217                         new_parent = generate_svn_revision_id(uuid, rev, bp)
218                         if new_parent != parent:
219                             if not repository.has_revision(revid):
220                                 needed_revs.append(new_parent)
221                             needs_upgrading.append(revid)
222
223                             if not allow_change:
224                                 oldrev = repository.get_revision(parent)
225                                 newrev = svn_repository.get_revision(new_parent)
226                                 if revision_changed(oldrev, newrev):
227                                     raise UpgradeChangesContent(parent)
228                         new_parents[revid].append(new_parent)
229                     except InvalidRevisionId:
230                         new_parents[revid].append(parent)
231         finally:
232             pb.finished()
233
234         # Make sure all the required current version revisions are present
235         pb = ui.ui_factory.nested_progress_bar()
236         i = 0
237         try:
238             for revid in needed_revs:
239                 pb.update('fetching new revisions', i, len(needed_revs))
240                 repository.fetch(svn_repository, revid)
241                 i += 1
242         finally:
243             pb.finished()
244
245         pb = ui.ui_factory.nested_progress_bar()
246         i = 0
247         total = len(needs_upgrading)
248         try:
249             while len(needs_upgrading) > 0:
250                 revid = needs_upgrading.pop()
251                 pb.update('upgrading revisions', i, total)
252                 i += 1
253                 newrevid = create_upgraded_revid(revid)
254                 rename_map[revid] = newrevid
255                 if repository.has_revision(newrevid):
256                     continue
257                 change_revision_parent(repository, revid, newrevid, new_parents[revid])
258                 for childrev in find_children(revid):
259                     if not new_parents.has_key(childrev):
260                         new_parents = repository.revision_parents(childrev)
261                     def replace_parent(x):
262                         if x == revid:
263                             return newrevid
264                         return x
265                     if (revid in new_parents[childrev] and 
266                         not childrev in needs_upgrading):
267                         new_parents[childrev] = map(replace_parent, new_parents[childrev])
268                         needs_upgrading.append(childrev)
269         finally:
270             pb.finished()
271         return rename_map
272     finally:
273         repository.unlock()
274         svn_repository.unlock()