comparison hgext/convert/__init__.py @ 5621:badbefa55972

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