Show More
@@ -11,6 +11,7 from darcs import darcs_source | |||||
11 | from git import convert_git |
|
11 | from git import convert_git | |
12 | from hg import mercurial_source, mercurial_sink |
|
12 | from hg import mercurial_source, mercurial_sink | |
13 | from subversion import convert_svn, debugsvnlog |
|
13 | from subversion import convert_svn, debugsvnlog | |
|
14 | import filemap | |||
14 |
|
15 | |||
15 | import os, shutil |
|
16 | import os, shutil | |
16 | from mercurial import hg, ui, util, commands |
|
17 | from mercurial import hg, ui, util, commands | |
@@ -378,6 +379,10 def convert(ui, src, dest=None, revmapfi | |||||
378 | shutil.rmtree(dest, True) |
|
379 | shutil.rmtree(dest, True) | |
379 | raise |
|
380 | raise | |
380 |
|
381 | |||
|
382 | fmap = opts.get('filemap') | |||
|
383 | if fmap: | |||
|
384 | srcc = filemap.filemap_source(ui, srcc, fmap) | |||
|
385 | ||||
381 | if not revmapfile: |
|
386 | if not revmapfile: | |
382 | try: |
|
387 | try: | |
383 | revmapfile = destc.revmapfile() |
|
388 | revmapfile = destc.revmapfile() |
@@ -96,6 +96,19 class converter_source(object): | |||||
96 | except: |
|
96 | except: | |
97 | return s.decode(encoding, "replace").encode("utf-8") |
|
97 | return s.decode(encoding, "replace").encode("utf-8") | |
98 |
|
98 | |||
|
99 | def getchangedfiles(self, rev, i): | |||
|
100 | """Return the files changed by rev compared to parent[i]. | |||
|
101 | ||||
|
102 | i is an index selecting one of the parents of rev. The return | |||
|
103 | value should be the list of files that are different in rev and | |||
|
104 | this parent. | |||
|
105 | ||||
|
106 | If rev has no parents, i is None. | |||
|
107 | ||||
|
108 | This function is only needed to support --filemap | |||
|
109 | """ | |||
|
110 | raise NotImplementedError() | |||
|
111 | ||||
99 | class converter_sink(object): |
|
112 | class converter_sink(object): | |
100 | """Conversion sink (target) interface""" |
|
113 | """Conversion sink (target) interface""" | |
101 |
|
114 |
@@ -1,4 +1,5 | |||||
1 | # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com> |
|
1 | # Copyright 2007 Bryan O'Sullivan <bos@serpentine.com> | |
|
2 | # Copyright 2007 Alexis S. L. Carvalho <alexis@cecm.usp.br> | |||
2 | # |
|
3 | # | |
3 | # This software may be used and distributed according to the terms of |
|
4 | # This software may be used and distributed according to the terms of | |
4 | # the GNU General Public License, incorporated herein by reference. |
|
5 | # the GNU General Public License, incorporated herein by reference. | |
@@ -6,6 +7,7 | |||||
6 | import shlex |
|
7 | import shlex | |
7 | from mercurial.i18n import _ |
|
8 | from mercurial.i18n import _ | |
8 | from mercurial import util |
|
9 | from mercurial import util | |
|
10 | from common import SKIPREV | |||
9 |
|
11 | |||
10 | def rpairs(name): |
|
12 | def rpairs(name): | |
11 | e = len(name) |
|
13 | e = len(name) | |
@@ -92,3 +94,237 class filemapper(object): | |||||
92 |
|
94 | |||
93 | def active(self): |
|
95 | def active(self): | |
94 | return bool(self.include or self.exclude or self.rename) |
|
96 | return bool(self.include or self.exclude or self.rename) | |
|
97 | ||||
|
98 | # This class does two additional things compared to a regular source: | |||
|
99 | # | |||
|
100 | # - Filter and rename files. This is mostly wrapped by the filemapper | |||
|
101 | # class above. We hide the original filename in the revision that is | |||
|
102 | # returned by getchanges to be able to find things later in getfile | |||
|
103 | # and getmode. | |||
|
104 | # | |||
|
105 | # - Return only revisions that matter for the files we're interested in. | |||
|
106 | # This involves rewriting the parents of the original revision to | |||
|
107 | # create a graph that is restricted to those revisions. | |||
|
108 | # | |||
|
109 | # This set of revisions includes not only revisions that directly | |||
|
110 | # touch files we're interested in, but also merges that merge two | |||
|
111 | # or more interesting revisions. | |||
|
112 | ||||
|
113 | class filemap_source(object): | |||
|
114 | def __init__(self, ui, baseconverter, filemap): | |||
|
115 | self.ui = ui | |||
|
116 | self.base = baseconverter | |||
|
117 | self.filemapper = filemapper(ui, filemap) | |||
|
118 | self.commits = {} | |||
|
119 | # if a revision rev has parent p in the original revision graph, then | |||
|
120 | # rev will have parent self.parentmap[p] in the restricted graph. | |||
|
121 | self.parentmap = {} | |||
|
122 | # self.wantedancestors[rev] is the set of all ancestors of rev that | |||
|
123 | # are in the restricted graph. | |||
|
124 | self.wantedancestors = {} | |||
|
125 | self.convertedorder = None | |||
|
126 | self._rebuilt = False | |||
|
127 | self.origparents = {} | |||
|
128 | ||||
|
129 | def setrevmap(self, revmap, order): | |||
|
130 | # rebuild our state to make things restartable | |||
|
131 | # | |||
|
132 | # To avoid calling getcommit for every revision that has already | |||
|
133 | # been converted, we rebuild only the parentmap, delaying the | |||
|
134 | # rebuild of wantedancestors until we need it (i.e. until a | |||
|
135 | # merge). | |||
|
136 | # | |||
|
137 | # We assume the order argument lists the revisions in | |||
|
138 | # topological order, so that we can infer which revisions were | |||
|
139 | # wanted by previous runs. | |||
|
140 | self._rebuilt = not revmap | |||
|
141 | seen = {SKIPREV: SKIPREV} | |||
|
142 | dummyset = util.set() | |||
|
143 | converted = [] | |||
|
144 | for rev in order: | |||
|
145 | mapped = revmap[rev] | |||
|
146 | wanted = mapped not in seen | |||
|
147 | if wanted: | |||
|
148 | seen[mapped] = rev | |||
|
149 | self.parentmap[rev] = rev | |||
|
150 | else: | |||
|
151 | self.parentmap[rev] = seen[mapped] | |||
|
152 | self.wantedancestors[rev] = dummyset | |||
|
153 | arg = seen[mapped] | |||
|
154 | if arg == SKIPREV: | |||
|
155 | arg = None | |||
|
156 | converted.append((rev, wanted, arg)) | |||
|
157 | self.convertedorder = converted | |||
|
158 | return self.base.setrevmap(revmap, order) | |||
|
159 | ||||
|
160 | def rebuild(self): | |||
|
161 | if self._rebuilt: | |||
|
162 | return True | |||
|
163 | self._rebuilt = True | |||
|
164 | pmap = self.parentmap.copy() | |||
|
165 | self.parentmap.clear() | |||
|
166 | self.wantedancestors.clear() | |||
|
167 | for rev, wanted, arg in self.convertedorder: | |||
|
168 | parents = self.origparents.get(rev) | |||
|
169 | if parents is None: | |||
|
170 | parents = self.base.getcommit(rev).parents | |||
|
171 | if wanted: | |||
|
172 | self.mark_wanted(rev, parents) | |||
|
173 | else: | |||
|
174 | self.mark_not_wanted(rev, arg) | |||
|
175 | ||||
|
176 | assert pmap == self.parentmap | |||
|
177 | return True | |||
|
178 | ||||
|
179 | def getheads(self): | |||
|
180 | return self.base.getheads() | |||
|
181 | ||||
|
182 | def getcommit(self, rev): | |||
|
183 | # We want to save a reference to the commit objects to be able | |||
|
184 | # to rewrite their parents later on. | |||
|
185 | self.commits[rev] = self.base.getcommit(rev) | |||
|
186 | return self.commits[rev] | |||
|
187 | ||||
|
188 | def wanted(self, rev, i): | |||
|
189 | # Return True if we're directly interested in rev. | |||
|
190 | # | |||
|
191 | # i is an index selecting one of the parents of rev (if rev | |||
|
192 | # has no parents, i is None). getchangedfiles will give us | |||
|
193 | # the list of files that are different in rev and in the parent | |||
|
194 | # indicated by i. If we're interested in any of these files, | |||
|
195 | # we're interested in rev. | |||
|
196 | try: | |||
|
197 | files = self.base.getchangedfiles(rev, i) | |||
|
198 | except NotImplementedError: | |||
|
199 | raise util.Abort(_("source repository doesn't support --filemap")) | |||
|
200 | for f in files: | |||
|
201 | if self.filemapper(f): | |||
|
202 | return True | |||
|
203 | return False | |||
|
204 | ||||
|
205 | def mark_not_wanted(self, rev, p): | |||
|
206 | # Mark rev as not interesting and update data structures. | |||
|
207 | ||||
|
208 | if p is None: | |||
|
209 | # A root revision. Use SKIPREV to indicate that it doesn't | |||
|
210 | # map to any revision in the restricted graph. Put SKIPREV | |||
|
211 | # in the set of wanted ancestors to simplify code elsewhere | |||
|
212 | self.parentmap[rev] = SKIPREV | |||
|
213 | self.wantedancestors[rev] = util.set((SKIPREV,)) | |||
|
214 | return | |||
|
215 | ||||
|
216 | # Reuse the data from our parent. | |||
|
217 | self.parentmap[rev] = self.parentmap[p] | |||
|
218 | self.wantedancestors[rev] = self.wantedancestors[p] | |||
|
219 | ||||
|
220 | def mark_wanted(self, rev, parents): | |||
|
221 | # Mark rev ss wanted and update data structures. | |||
|
222 | ||||
|
223 | # rev will be in the restricted graph, so children of rev in | |||
|
224 | # the original graph should still have rev as a parent in the | |||
|
225 | # restricted graph. | |||
|
226 | self.parentmap[rev] = rev | |||
|
227 | ||||
|
228 | # The set of wanted ancestors of rev is the union of the sets | |||
|
229 | # of wanted ancestors of its parents. Plus rev itself. | |||
|
230 | wrev = util.set() | |||
|
231 | for p in parents: | |||
|
232 | wrev.update(self.wantedancestors[p]) | |||
|
233 | wrev.add(rev) | |||
|
234 | self.wantedancestors[rev] = wrev | |||
|
235 | ||||
|
236 | def getchanges(self, rev): | |||
|
237 | parents = self.commits[rev].parents | |||
|
238 | if len(parents) > 1: | |||
|
239 | self.rebuild() | |||
|
240 | ||||
|
241 | # To decide whether we're interested in rev we: | |||
|
242 | # | |||
|
243 | # - calculate what parents rev will have if it turns out we're | |||
|
244 | # interested in it. If it's going to have more than 1 parent, | |||
|
245 | # we're interested in it. | |||
|
246 | # | |||
|
247 | # - otherwise, we'll compare it with the single parent we found. | |||
|
248 | # If any of the files we're interested in is different in the | |||
|
249 | # the two revisions, we're interested in rev. | |||
|
250 | ||||
|
251 | # A parent p is interesting if its mapped version (self.parentmap[p]): | |||
|
252 | # - is not SKIPREV | |||
|
253 | # - is still not in the list of parents (we don't want duplicates) | |||
|
254 | # - is not an ancestor of the mapped versions of the other parents | |||
|
255 | mparents = [] | |||
|
256 | wp = None | |||
|
257 | for i, p1 in enumerate(parents): | |||
|
258 | mp1 = self.parentmap[p1] | |||
|
259 | if mp1 == SKIPREV or mp1 in mparents: | |||
|
260 | continue | |||
|
261 | for p2 in parents: | |||
|
262 | if p1 == p2 or mp1 == self.parentmap[p2]: | |||
|
263 | continue | |||
|
264 | if mp1 in self.wantedancestors[p2]: | |||
|
265 | break | |||
|
266 | else: | |||
|
267 | mparents.append(mp1) | |||
|
268 | wp = i | |||
|
269 | ||||
|
270 | if wp is None and parents: | |||
|
271 | wp = 0 | |||
|
272 | ||||
|
273 | self.origparents[rev] = parents | |||
|
274 | ||||
|
275 | if len(mparents) < 2 and not self.wanted(rev, wp): | |||
|
276 | # We don't want this revision. | |||
|
277 | # Update our state and tell the convert process to map this | |||
|
278 | # revision to the same revision its parent as mapped to. | |||
|
279 | p = None | |||
|
280 | if parents: | |||
|
281 | p = parents[wp] | |||
|
282 | self.mark_not_wanted(rev, p) | |||
|
283 | self.convertedorder.append((rev, False, p)) | |||
|
284 | return self.parentmap[rev] | |||
|
285 | ||||
|
286 | # We want this revision. | |||
|
287 | # Rewrite the parents of the commit object | |||
|
288 | self.commits[rev].parents = mparents | |||
|
289 | self.mark_wanted(rev, parents) | |||
|
290 | self.convertedorder.append((rev, True, None)) | |||
|
291 | ||||
|
292 | # Get the real changes and do the filtering/mapping. | |||
|
293 | # To be able to get the files later on in getfile and getmode, | |||
|
294 | # we hide the original filename in the rev part of the return | |||
|
295 | # value. | |||
|
296 | changes, copies = self.base.getchanges(rev) | |||
|
297 | newnames = {} | |||
|
298 | files = [] | |||
|
299 | for f, r in changes: | |||
|
300 | newf = self.filemapper(f) | |||
|
301 | if newf: | |||
|
302 | files.append((newf, (f, r))) | |||
|
303 | newnames[f] = newf | |||
|
304 | ||||
|
305 | ncopies = {} | |||
|
306 | for c in copies: | |||
|
307 | newc = self.filemapper(c) | |||
|
308 | if newc: | |||
|
309 | newsource = self.filemapper(copies[c]) | |||
|
310 | if newsource: | |||
|
311 | ncopies[newc] = newsource | |||
|
312 | ||||
|
313 | return files, ncopies | |||
|
314 | ||||
|
315 | def getfile(self, name, rev): | |||
|
316 | realname, realrev = rev | |||
|
317 | return self.base.getfile(realname, realrev) | |||
|
318 | ||||
|
319 | def getmode(self, name, rev): | |||
|
320 | realname, realrev = rev | |||
|
321 | return self.base.getmode(realname, realrev) | |||
|
322 | ||||
|
323 | def gettags(self): | |||
|
324 | return self.base.gettags() | |||
|
325 | ||||
|
326 | def before(self): | |||
|
327 | pass | |||
|
328 | ||||
|
329 | def after(self): | |||
|
330 | pass |
General Comments 0
You need to be logged in to leave comments.
Login now