##// END OF EJS Templates
convert: move commands definition to ease demandload job (issue 860)
Patrick Mezard -
r5631:96e16af9 default
parent child Browse files
Show More
@@ -5,277 +5,10 b''
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, mapfile
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 debugsvnlog, svn_source, svn_sink
14 import filemap
15
16 import os, shutil
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 ('svn', svn_sink),
33 ]
34
35 def convertsource(ui, path, type, rev):
36 exceptions = []
37 for name, source in source_converters:
38 try:
39 if not type or name == type:
40 return source(ui, path, rev)
41 except NoRepo, inst:
42 exceptions.append(inst)
43 if not ui.quiet:
44 for inst in exceptions:
45 ui.write(_("%s\n") % inst)
46 raise util.Abort('%s: unknown repository type' % path)
47
48 def convertsink(ui, path, type):
49 for name, sink in sink_converters:
50 try:
51 if not type or name == type:
52 return sink(ui, path)
53 except NoRepo, inst:
54 ui.note(_("convert: %s\n") % inst)
55 raise util.Abort('%s: unknown repository type' % path)
56
57 class converter(object):
58 def __init__(self, ui, source, dest, revmapfile, opts):
59
60 self.source = source
61 self.dest = dest
62 self.ui = ui
63 self.opts = opts
64 self.commitcache = {}
65 self.authors = {}
66 self.authorfile = None
67
68 self.map = mapfile(ui, revmapfile)
69
70 # Read first the dst author map if any
71 authorfile = self.dest.authorfile()
72 if authorfile and os.path.exists(authorfile):
73 self.readauthormap(authorfile)
74 # Extend/Override with new author map if necessary
75 if opts.get('authors'):
76 self.readauthormap(opts.get('authors'))
77 self.authorfile = self.dest.authorfile()
78
79 def walktree(self, heads):
80 '''Return a mapping that identifies the uncommitted parents of every
81 uncommitted changeset.'''
82 visit = heads
83 known = {}
84 parents = {}
85 while visit:
86 n = visit.pop(0)
87 if n in known or n in self.map: continue
88 known[n] = 1
89 commit = self.cachecommit(n)
90 parents[n] = []
91 for p in commit.parents:
92 parents[n].append(p)
93 visit.append(p)
94
95 return parents
96
97 def toposort(self, parents):
98 '''Return an ordering such that every uncommitted changeset is
99 preceeded by all its uncommitted ancestors.'''
100 visit = parents.keys()
101 seen = {}
102 children = {}
103
104 while visit:
105 n = visit.pop(0)
106 if n in seen: continue
107 seen[n] = 1
108 # Ensure that nodes without parents are present in the 'children'
109 # mapping.
110 children.setdefault(n, [])
111 for p in parents[n]:
112 if not p in self.map:
113 visit.append(p)
114 children.setdefault(p, []).append(n)
115
116 s = []
117 removed = {}
118 visit = children.keys()
119 while visit:
120 n = visit.pop(0)
121 if n in removed: continue
122 dep = 0
123 if n in parents:
124 for p in parents[n]:
125 if p in self.map: continue
126 if p not in removed:
127 # we're still dependent
128 visit.append(n)
129 dep = 1
130 break
131
132 if not dep:
133 # all n's parents are in the list
134 removed[n] = 1
135 if n not in self.map:
136 s.append(n)
137 if n in children:
138 for c in children[n]:
139 visit.insert(0, c)
140
10
141 if self.opts.get('datesort'):
11 # Commands definition was moved elsewhere to ease demandload job.
142 depth = {}
143 for n in s:
144 depth[n] = 0
145 pl = [p for p in self.commitcache[n].parents
146 if p not in self.map]
147 if pl:
148 depth[n] = max([depth[p] for p in pl]) + 1
149
150 s = [(depth[n], self.commitcache[n].date, n) for n in s]
151 s.sort()
152 s = [e[2] for e in s]
153
154 return s
155
156 def writeauthormap(self):
157 authorfile = self.authorfile
158 if authorfile:
159 self.ui.status('Writing author map file %s\n' % authorfile)
160 ofile = open(authorfile, 'w+')
161 for author in self.authors:
162 ofile.write("%s=%s\n" % (author, self.authors[author]))
163 ofile.close()
164
165 def readauthormap(self, authorfile):
166 afile = open(authorfile, 'r')
167 for line in afile:
168 try:
169 srcauthor = line.split('=')[0].strip()
170 dstauthor = line.split('=')[1].strip()
171 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
172 self.ui.status(
173 'Overriding mapping for author %s, was %s, will be %s\n'
174 % (srcauthor, self.authors[srcauthor], dstauthor))
175 else:
176 self.ui.debug('Mapping author %s to %s\n'
177 % (srcauthor, dstauthor))
178 self.authors[srcauthor] = dstauthor
179 except IndexError:
180 self.ui.warn(
181 'Ignoring bad line in author file map %s: %s\n'
182 % (authorfile, line))
183 afile.close()
184
185 def cachecommit(self, rev):
186 commit = self.source.getcommit(rev)
187 commit.author = self.authors.get(commit.author, commit.author)
188 self.commitcache[rev] = commit
189 return commit
190
191 def copy(self, rev):
192 commit = self.commitcache[rev]
193 do_copies = hasattr(self.dest, 'copyfile')
194 filenames = []
195
196 changes = self.source.getchanges(rev)
197 if isinstance(changes, basestring):
198 if changes == SKIPREV:
199 dest = SKIPREV
200 else:
201 dest = self.map[changes]
202 self.map[rev] = dest
203 return
204 files, copies = changes
205 parents = [self.map[r] for r in commit.parents]
206 if commit.parents:
207 prev = commit.parents[0]
208 if prev not in self.commitcache:
209 self.cachecommit(prev)
210 pbranch = self.commitcache[prev].branch
211 else:
212 pbranch = None
213 self.dest.setbranch(commit.branch, pbranch, parents)
214 for f, v in files:
215 filenames.append(f)
216 try:
217 data = self.source.getfile(f, v)
218 except IOError, inst:
219 self.dest.delfile(f)
220 else:
221 e = self.source.getmode(f, v)
222 self.dest.putfile(f, e, data)
223 if do_copies:
224 if f in copies:
225 copyf = copies[f]
226 # Merely marks that a copy happened.
227 self.dest.copyfile(copyf, f)
228
229 newnode = self.dest.putcommit(filenames, parents, commit)
230 self.source.converted(rev, newnode)
231 self.map[rev] = newnode
232
233 def convert(self):
234 try:
235 self.source.before()
236 self.dest.before()
237 self.source.setrevmap(self.map)
238 self.ui.status("scanning source...\n")
239 heads = self.source.getheads()
240 parents = self.walktree(heads)
241 self.ui.status("sorting...\n")
242 t = self.toposort(parents)
243 num = len(t)
244 c = None
245
246 self.ui.status("converting...\n")
247 for c in t:
248 num -= 1
249 desc = self.commitcache[c].desc
250 if "\n" in desc:
251 desc = desc.splitlines()[0]
252 self.ui.status("%d %s\n" % (num, desc))
253 self.copy(c)
254
255 tags = self.source.gettags()
256 ctags = {}
257 for k in tags:
258 v = tags[k]
259 if self.map.get(v, SKIPREV) != SKIPREV:
260 ctags[k] = self.map[v]
261
262 if c and ctags:
263 nrev = self.dest.puttags(ctags)
264 # write another hash correspondence to override the previous
265 # one so we don't end up with extra tag heads
266 if nrev:
267 self.map[c] = nrev
268
269 self.writeauthormap()
270 finally:
271 self.cleanup()
272
273 def cleanup(self):
274 try:
275 self.dest.after()
276 finally:
277 self.source.after()
278 self.map.close()
279
12
280 def convert(ui, src, dest=None, revmapfile=None, **opts):
13 def convert(ui, src, dest=None, revmapfile=None, **opts):
281 """Convert a foreign SCM repository to a Mercurial one.
14 """Convert a foreign SCM repository to a Mercurial one.
@@ -351,37 +84,12 b' def convert(ui, src, dest=None, revmapfi'
351 --config convert.svn.trunk=trunk (directory name)
84 --config convert.svn.trunk=trunk (directory name)
352 svn source: specify the name of the trunk branch
85 svn source: specify the name of the trunk branch
353 """
86 """
354
87 return convcmd.convert(ui, src, dest, revmapfile, **opts)
355 util._encoding = 'UTF-8'
356
357 if not dest:
358 dest = hg.defaultdest(src) + "-hg"
359 ui.status("assuming destination %s\n" % dest)
360
361 destc = convertsink(ui, dest, opts.get('dest_type'))
362
88
363 try:
89 def debugsvnlog(ui, **opts):
364 srcc = convertsource(ui, src, opts.get('source_type'),
90 return convcmd.debugsvnlog(ui, **opts)
365 opts.get('rev'))
366 except Exception:
367 for path in destc.created:
368 shutil.rmtree(path, True)
369 raise
370
91
371 fmap = opts.get('filemap')
92 commands.norepo += " convert debugsvnlog"
372 if fmap:
373 srcc = filemap.filemap_source(ui, srcc, fmap)
374 destc.setfilemapmode(True)
375
376 if not revmapfile:
377 try:
378 revmapfile = destc.revmapfile()
379 except:
380 revmapfile = os.path.join(destc, "map")
381
382 c = converter(ui, srcc, destc, revmapfile, opts)
383 c.convert()
384
385
93
386 cmdtable = {
94 cmdtable = {
387 "convert":
95 "convert":
@@ -398,4 +106,3 b' cmdtable = {'
398 [],
106 [],
399 'hg debugsvnlog'),
107 'hg debugsvnlog'),
400 }
108 }
401
@@ -1,4 +1,4 b''
1 # convert.py Foreign SCM converter
1 # convcmd - convert extension commands definition
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
@@ -14,11 +14,9 b' from subversion import debugsvnlog, svn_'
14 import filemap
14 import filemap
15
15
16 import os, shutil
16 import os, shutil
17 from mercurial import hg, ui, util, commands
17 from mercurial import hg, util
18 from mercurial.i18n import _
18 from mercurial.i18n import _
19
19
20 commands.norepo += " convert debugsvnlog"
21
22 source_converters = [
20 source_converters = [
23 ('cvs', convert_cvs),
21 ('cvs', convert_cvs),
24 ('git', convert_git),
22 ('git', convert_git),
@@ -278,80 +276,6 b' class converter(object):'
278 self.map.close()
276 self.map.close()
279
277
280 def convert(ui, src, dest=None, revmapfile=None, **opts):
278 def convert(ui, src, dest=None, revmapfile=None, **opts):
281 """Convert a foreign SCM repository to a Mercurial one.
282
283 Accepted source formats:
284 - Mercurial
285 - CVS
286 - Darcs
287 - git
288 - Subversion
289
290 Accepted destination formats:
291 - Mercurial
292 - Subversion (history on branches is not preserved)
293
294 If no revision is given, all revisions will be converted. Otherwise,
295 convert will only import up to the named revision (given in a format
296 understood by the source).
297
298 If no destination directory name is specified, it defaults to the
299 basename of the source with '-hg' appended. If the destination
300 repository doesn't exist, it will be created.
301
302 If <MAPFILE> isn't given, it will be put in a default location
303 (<dest>/.hg/shamap by default). The <MAPFILE> is a simple text
304 file that maps each source commit ID to the destination ID for
305 that revision, like so:
306 <source ID> <destination ID>
307
308 If the file doesn't exist, it's automatically created. It's updated
309 on each commit copied, so convert-repo can be interrupted and can
310 be run repeatedly to copy new commits.
311
312 The [username mapping] file is a simple text file that maps each source
313 commit author to a destination commit author. It is handy for source SCMs
314 that use unix logins to identify authors (eg: CVS). One line per author
315 mapping and the line format is:
316 srcauthor=whatever string you want
317
318 The filemap is a file that allows filtering and remapping of files
319 and directories. Comment lines start with '#'. Each line can
320 contain one of the following directives:
321
322 include path/to/file
323
324 exclude path/to/file
325
326 rename from/file to/file
327
328 The 'include' directive causes a file, or all files under a
329 directory, to be included in the destination repository, and the
330 exclusion of all other files and dirs not explicitely included.
331 The 'exclude' directive causes files or directories to be omitted.
332 The 'rename' directive renames a file or directory. To rename from a
333 subdirectory into the root of the repository, use '.' as the path to
334 rename to.
335
336 Back end options:
337
338 --config convert.hg.clonebranches=False (boolean)
339 hg target: XXX not documented
340 --config convert.hg.saverev=True (boolean)
341 hg source: allow target to preserve source revision ID
342 --config convert.hg.tagsbranch=default (branch name)
343 hg target: XXX not documented
344 --config convert.hg.usebranchnames=True (boolean)
345 hg target: preserve branch names
346
347 --config convert.svn.branches=branches (directory name)
348 svn source: specify the directory containing branches
349 --config convert.svn.tags=tags (directory name)
350 svn source: specify the directory containing tags
351 --config convert.svn.trunk=trunk (directory name)
352 svn source: specify the name of the trunk branch
353 """
354
355 util._encoding = 'UTF-8'
279 util._encoding = 'UTF-8'
356
280
357 if not dest:
281 if not dest:
@@ -382,20 +306,3 b' def convert(ui, src, dest=None, revmapfi'
382 c = converter(ui, srcc, destc, revmapfile, opts)
306 c = converter(ui, srcc, destc, revmapfile, opts)
383 c.convert()
307 c.convert()
384
308
385
386 cmdtable = {
387 "convert":
388 (convert,
389 [('A', 'authors', '', 'username mapping filename'),
390 ('d', 'dest-type', '', 'destination repository type'),
391 ('', 'filemap', '', 'remap file names using contents of file'),
392 ('r', 'rev', '', 'import up to target revision REV'),
393 ('s', 'source-type', '', 'source repository type'),
394 ('', 'datesort', None, 'try to sort changesets by date')],
395 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
396 "debugsvnlog":
397 (debugsvnlog,
398 [],
399 'hg debugsvnlog'),
400 }
401
General Comments 0
You need to be logged in to leave comments. Login now