|
1 # convcmd - convert extension commands definition |
|
2 # |
|
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com> |
|
4 # |
|
5 # This software may be used and distributed according to the terms |
|
6 # of the GNU General Public License, incorporated herein by reference. |
|
7 |
|
8 from common import NoRepo, SKIPREV, converter_source, converter_sink |
|
9 from cvs import convert_cvs |
|
10 from darcs import darcs_source |
|
11 from git import convert_git |
|
12 from hg import mercurial_source, mercurial_sink |
|
13 from subversion import svn_source, debugsvnlog |
|
14 import filemap |
|
15 |
|
16 import os, shutil |
|
17 from mercurial import hg, util |
|
18 from mercurial.i18n import _ |
|
19 |
|
20 source_converters = [ |
|
21 ('cvs', convert_cvs), |
|
22 ('git', convert_git), |
|
23 ('svn', svn_source), |
|
24 ('hg', mercurial_source), |
|
25 ('darcs', darcs_source), |
|
26 ] |
|
27 |
|
28 sink_converters = [ |
|
29 ('hg', mercurial_sink), |
|
30 ] |
|
31 |
|
32 def convertsource(ui, path, type, rev): |
|
33 exceptions = [] |
|
34 for name, source in source_converters: |
|
35 try: |
|
36 if not type or name == type: |
|
37 return source(ui, path, rev) |
|
38 except NoRepo, inst: |
|
39 exceptions.append(inst) |
|
40 if not ui.quiet: |
|
41 for inst in exceptions: |
|
42 ui.write(_("%s\n") % inst) |
|
43 raise util.Abort('%s: unknown repository type' % path) |
|
44 |
|
45 def convertsink(ui, path, type): |
|
46 for name, sink in sink_converters: |
|
47 try: |
|
48 if not type or name == type: |
|
49 return sink(ui, path) |
|
50 except NoRepo, inst: |
|
51 ui.note(_("convert: %s\n") % inst) |
|
52 raise util.Abort('%s: unknown repository type' % path) |
|
53 |
|
54 class converter(object): |
|
55 def __init__(self, ui, source, dest, revmapfile, opts): |
|
56 |
|
57 self.source = source |
|
58 self.dest = dest |
|
59 self.ui = ui |
|
60 self.opts = opts |
|
61 self.commitcache = {} |
|
62 self.revmapfile = revmapfile |
|
63 self.revmapfilefd = None |
|
64 self.authors = {} |
|
65 self.authorfile = None |
|
66 |
|
67 self.maporder = [] |
|
68 self.map = {} |
|
69 try: |
|
70 origrevmapfile = open(self.revmapfile, 'r') |
|
71 for l in origrevmapfile: |
|
72 sv, dv = l[:-1].split() |
|
73 if sv not in self.map: |
|
74 self.maporder.append(sv) |
|
75 self.map[sv] = dv |
|
76 origrevmapfile.close() |
|
77 except IOError: |
|
78 pass |
|
79 |
|
80 # Read first the dst author map if any |
|
81 authorfile = self.dest.authorfile() |
|
82 if authorfile and os.path.exists(authorfile): |
|
83 self.readauthormap(authorfile) |
|
84 # Extend/Override with new author map if necessary |
|
85 if opts.get('authors'): |
|
86 self.readauthormap(opts.get('authors')) |
|
87 self.authorfile = self.dest.authorfile() |
|
88 |
|
89 def walktree(self, heads): |
|
90 '''Return a mapping that identifies the uncommitted parents of every |
|
91 uncommitted changeset.''' |
|
92 visit = heads |
|
93 known = {} |
|
94 parents = {} |
|
95 while visit: |
|
96 n = visit.pop(0) |
|
97 if n in known or n in self.map: continue |
|
98 known[n] = 1 |
|
99 commit = self.cachecommit(n) |
|
100 parents[n] = [] |
|
101 for p in commit.parents: |
|
102 parents[n].append(p) |
|
103 visit.append(p) |
|
104 |
|
105 return parents |
|
106 |
|
107 def toposort(self, parents): |
|
108 '''Return an ordering such that every uncommitted changeset is |
|
109 preceeded by all its uncommitted ancestors.''' |
|
110 visit = parents.keys() |
|
111 seen = {} |
|
112 children = {} |
|
113 |
|
114 while visit: |
|
115 n = visit.pop(0) |
|
116 if n in seen: continue |
|
117 seen[n] = 1 |
|
118 # Ensure that nodes without parents are present in the 'children' |
|
119 # mapping. |
|
120 children.setdefault(n, []) |
|
121 for p in parents[n]: |
|
122 if not p in self.map: |
|
123 visit.append(p) |
|
124 children.setdefault(p, []).append(n) |
|
125 |
|
126 s = [] |
|
127 removed = {} |
|
128 visit = children.keys() |
|
129 while visit: |
|
130 n = visit.pop(0) |
|
131 if n in removed: continue |
|
132 dep = 0 |
|
133 if n in parents: |
|
134 for p in parents[n]: |
|
135 if p in self.map: continue |
|
136 if p not in removed: |
|
137 # we're still dependent |
|
138 visit.append(n) |
|
139 dep = 1 |
|
140 break |
|
141 |
|
142 if not dep: |
|
143 # all n's parents are in the list |
|
144 removed[n] = 1 |
|
145 if n not in self.map: |
|
146 s.append(n) |
|
147 if n in children: |
|
148 for c in children[n]: |
|
149 visit.insert(0, c) |
|
150 |
|
151 if self.opts.get('datesort'): |
|
152 depth = {} |
|
153 for n in s: |
|
154 depth[n] = 0 |
|
155 pl = [p for p in self.commitcache[n].parents |
|
156 if p not in self.map] |
|
157 if pl: |
|
158 depth[n] = max([depth[p] for p in pl]) + 1 |
|
159 |
|
160 s = [(depth[n], self.commitcache[n].date, n) for n in s] |
|
161 s.sort() |
|
162 s = [e[2] for e in s] |
|
163 |
|
164 return s |
|
165 |
|
166 def mapentry(self, src, dst): |
|
167 if self.revmapfilefd is None: |
|
168 try: |
|
169 self.revmapfilefd = open(self.revmapfile, "a") |
|
170 except IOError, (errno, strerror): |
|
171 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.revmapfile, errno, strerror)) |
|
172 self.map[src] = dst |
|
173 self.revmapfilefd.write("%s %s\n" % (src, dst)) |
|
174 self.revmapfilefd.flush() |
|
175 |
|
176 def writeauthormap(self): |
|
177 authorfile = self.authorfile |
|
178 if authorfile: |
|
179 self.ui.status('Writing author map file %s\n' % authorfile) |
|
180 ofile = open(authorfile, 'w+') |
|
181 for author in self.authors: |
|
182 ofile.write("%s=%s\n" % (author, self.authors[author])) |
|
183 ofile.close() |
|
184 |
|
185 def readauthormap(self, authorfile): |
|
186 afile = open(authorfile, 'r') |
|
187 for line in afile: |
|
188 try: |
|
189 srcauthor = line.split('=')[0].strip() |
|
190 dstauthor = line.split('=')[1].strip() |
|
191 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]: |
|
192 self.ui.status( |
|
193 'Overriding mapping for author %s, was %s, will be %s\n' |
|
194 % (srcauthor, self.authors[srcauthor], dstauthor)) |
|
195 else: |
|
196 self.ui.debug('Mapping author %s to %s\n' |
|
197 % (srcauthor, dstauthor)) |
|
198 self.authors[srcauthor] = dstauthor |
|
199 except IndexError: |
|
200 self.ui.warn( |
|
201 'Ignoring bad line in author file map %s: %s\n' |
|
202 % (authorfile, line)) |
|
203 afile.close() |
|
204 |
|
205 def cachecommit(self, rev): |
|
206 commit = self.source.getcommit(rev) |
|
207 commit.author = self.authors.get(commit.author, commit.author) |
|
208 self.commitcache[rev] = commit |
|
209 return commit |
|
210 |
|
211 def copy(self, rev): |
|
212 commit = self.commitcache[rev] |
|
213 do_copies = hasattr(self.dest, 'copyfile') |
|
214 filenames = [] |
|
215 |
|
216 changes = self.source.getchanges(rev) |
|
217 if isinstance(changes, basestring): |
|
218 if changes == SKIPREV: |
|
219 dest = SKIPREV |
|
220 else: |
|
221 dest = self.map[changes] |
|
222 self.mapentry(rev, dest) |
|
223 return |
|
224 files, copies = changes |
|
225 parents = [self.map[r] for r in commit.parents] |
|
226 if commit.parents: |
|
227 prev = commit.parents[0] |
|
228 if prev not in self.commitcache: |
|
229 self.cachecommit(prev) |
|
230 pbranch = self.commitcache[prev].branch |
|
231 else: |
|
232 pbranch = None |
|
233 self.dest.setbranch(commit.branch, pbranch, parents) |
|
234 for f, v in files: |
|
235 filenames.append(f) |
|
236 try: |
|
237 data = self.source.getfile(f, v) |
|
238 except IOError, inst: |
|
239 self.dest.delfile(f) |
|
240 else: |
|
241 e = self.source.getmode(f, v) |
|
242 self.dest.putfile(f, e, data) |
|
243 if do_copies: |
|
244 if f in copies: |
|
245 copyf = copies[f] |
|
246 # Merely marks that a copy happened. |
|
247 self.dest.copyfile(copyf, f) |
|
248 |
|
249 newnode = self.dest.putcommit(filenames, parents, commit) |
|
250 self.mapentry(rev, newnode) |
|
251 |
|
252 def convert(self): |
|
253 try: |
|
254 self.source.before() |
|
255 self.dest.before() |
|
256 self.source.setrevmap(self.map, self.maporder) |
|
257 self.ui.status("scanning source...\n") |
|
258 heads = self.source.getheads() |
|
259 parents = self.walktree(heads) |
|
260 self.ui.status("sorting...\n") |
|
261 t = self.toposort(parents) |
|
262 num = len(t) |
|
263 c = None |
|
264 |
|
265 self.ui.status("converting...\n") |
|
266 for c in t: |
|
267 num -= 1 |
|
268 desc = self.commitcache[c].desc |
|
269 if "\n" in desc: |
|
270 desc = desc.splitlines()[0] |
|
271 self.ui.status("%d %s\n" % (num, desc)) |
|
272 self.copy(c) |
|
273 |
|
274 tags = self.source.gettags() |
|
275 ctags = {} |
|
276 for k in tags: |
|
277 v = tags[k] |
|
278 if self.map.get(v, SKIPREV) != SKIPREV: |
|
279 ctags[k] = self.map[v] |
|
280 |
|
281 if c and ctags: |
|
282 nrev = self.dest.puttags(ctags) |
|
283 # write another hash correspondence to override the previous |
|
284 # one so we don't end up with extra tag heads |
|
285 if nrev: |
|
286 self.mapentry(c, nrev) |
|
287 |
|
288 self.writeauthormap() |
|
289 finally: |
|
290 self.cleanup() |
|
291 |
|
292 def cleanup(self): |
|
293 try: |
|
294 self.dest.after() |
|
295 finally: |
|
296 self.source.after() |
|
297 if self.revmapfilefd: |
|
298 self.revmapfilefd.close() |
|
299 |
|
300 def convert(ui, src, dest=None, revmapfile=None, **opts): |
|
301 util._encoding = 'UTF-8' |
|
302 |
|
303 if not dest: |
|
304 dest = hg.defaultdest(src) + "-hg" |
|
305 ui.status("assuming destination %s\n" % dest) |
|
306 |
|
307 destc = convertsink(ui, dest, opts.get('dest_type')) |
|
308 |
|
309 try: |
|
310 srcc = convertsource(ui, src, opts.get('source_type'), |
|
311 opts.get('rev')) |
|
312 except Exception: |
|
313 for path in destc.created: |
|
314 shutil.rmtree(path, True) |
|
315 raise |
|
316 |
|
317 fmap = opts.get('filemap') |
|
318 if fmap: |
|
319 srcc = filemap.filemap_source(ui, srcc, fmap) |
|
320 destc.setfilemapmode(True) |
|
321 |
|
322 if not revmapfile: |
|
323 try: |
|
324 revmapfile = destc.revmapfile() |
|
325 except: |
|
326 revmapfile = os.path.join(destc, "map") |
|
327 |
|
328 c = converter(ui, srcc, destc, revmapfile, opts) |
|
329 c.convert() |
|
330 |