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