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