##// END OF EJS Templates
convert: better error on invalid repository type
Patrick Mezard -
r9962:a7178ecc default
parent child Browse files
Show More
@@ -1,399 +1,403 b''
1 # convcmd - convert extension commands definition
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2, incorporated herein by reference.
6 # GNU General Public License version 2, incorporated herein by reference.
7
7
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
8 from common import NoRepo, MissingTool, SKIPREV, 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 svn_source, svn_sink
13 from subversion import svn_source, svn_sink
14 from monotone import monotone_source
14 from monotone import monotone_source
15 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
16 from bzr import bzr_source
16 from bzr import bzr_source
17 from p4 import p4_source
17 from p4 import p4_source
18 import filemap
18 import filemap
19
19
20 import os, shutil
20 import os, shutil
21 from mercurial import hg, util, encoding
21 from mercurial import hg, util, encoding
22 from mercurial.i18n import _
22 from mercurial.i18n import _
23
23
24 orig_encoding = 'ascii'
24 orig_encoding = 'ascii'
25
25
26 def recode(s):
26 def recode(s):
27 if isinstance(s, unicode):
27 if isinstance(s, unicode):
28 return s.encode(orig_encoding, 'replace')
28 return s.encode(orig_encoding, 'replace')
29 else:
29 else:
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
30 return s.decode('utf-8').encode(orig_encoding, 'replace')
31
31
32 source_converters = [
32 source_converters = [
33 ('cvs', convert_cvs, 'branchsort'),
33 ('cvs', convert_cvs, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
34 ('git', convert_git, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
35 ('svn', svn_source, 'branchsort'),
36 ('hg', mercurial_source, 'sourcesort'),
36 ('hg', mercurial_source, 'sourcesort'),
37 ('darcs', darcs_source, 'branchsort'),
37 ('darcs', darcs_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
38 ('mtn', monotone_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
39 ('gnuarch', gnuarch_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
40 ('bzr', bzr_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
41 ('p4', p4_source, 'branchsort'),
42 ]
42 ]
43
43
44 sink_converters = [
44 sink_converters = [
45 ('hg', mercurial_sink),
45 ('hg', mercurial_sink),
46 ('svn', svn_sink),
46 ('svn', svn_sink),
47 ]
47 ]
48
48
49 def convertsource(ui, path, type, rev):
49 def convertsource(ui, path, type, rev):
50 exceptions = []
50 exceptions = []
51 if type and type not in [s[0] for s in source_converters]:
52 raise util.Abort(_('%s: invalid source repository type') % type)
51 for name, source, sortmode in source_converters:
53 for name, source, sortmode in source_converters:
52 try:
54 try:
53 if not type or name == type:
55 if not type or name == type:
54 return source(ui, path, rev), sortmode
56 return source(ui, path, rev), sortmode
55 except (NoRepo, MissingTool), inst:
57 except (NoRepo, MissingTool), inst:
56 exceptions.append(inst)
58 exceptions.append(inst)
57 if not ui.quiet:
59 if not ui.quiet:
58 for inst in exceptions:
60 for inst in exceptions:
59 ui.write("%s\n" % inst)
61 ui.write("%s\n" % inst)
60 raise util.Abort(_('%s: missing or unsupported repository') % path)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
61
63
62 def convertsink(ui, path, type):
64 def convertsink(ui, path, type):
65 if type and type not in [s[0] for s in sink_converters]:
66 raise util.Abort(_('%s: invalid destination repository type') % type)
63 for name, sink in sink_converters:
67 for name, sink in sink_converters:
64 try:
68 try:
65 if not type or name == type:
69 if not type or name == type:
66 return sink(ui, path)
70 return sink(ui, path)
67 except NoRepo, inst:
71 except NoRepo, inst:
68 ui.note(_("convert: %s\n") % inst)
72 ui.note(_("convert: %s\n") % inst)
69 raise util.Abort(_('%s: unknown repository type') % path)
73 raise util.Abort(_('%s: unknown repository type') % path)
70
74
71 class converter(object):
75 class converter(object):
72 def __init__(self, ui, source, dest, revmapfile, opts):
76 def __init__(self, ui, source, dest, revmapfile, opts):
73
77
74 self.source = source
78 self.source = source
75 self.dest = dest
79 self.dest = dest
76 self.ui = ui
80 self.ui = ui
77 self.opts = opts
81 self.opts = opts
78 self.commitcache = {}
82 self.commitcache = {}
79 self.authors = {}
83 self.authors = {}
80 self.authorfile = None
84 self.authorfile = None
81
85
82 # Record converted revisions persistently: maps source revision
86 # Record converted revisions persistently: maps source revision
83 # ID to target revision ID (both strings). (This is how
87 # ID to target revision ID (both strings). (This is how
84 # incremental conversions work.)
88 # incremental conversions work.)
85 self.map = mapfile(ui, revmapfile)
89 self.map = mapfile(ui, revmapfile)
86
90
87 # Read first the dst author map if any
91 # Read first the dst author map if any
88 authorfile = self.dest.authorfile()
92 authorfile = self.dest.authorfile()
89 if authorfile and os.path.exists(authorfile):
93 if authorfile and os.path.exists(authorfile):
90 self.readauthormap(authorfile)
94 self.readauthormap(authorfile)
91 # Extend/Override with new author map if necessary
95 # Extend/Override with new author map if necessary
92 if opts.get('authors'):
96 if opts.get('authors'):
93 self.readauthormap(opts.get('authors'))
97 self.readauthormap(opts.get('authors'))
94 self.authorfile = self.dest.authorfile()
98 self.authorfile = self.dest.authorfile()
95
99
96 self.splicemap = mapfile(ui, opts.get('splicemap'))
100 self.splicemap = mapfile(ui, opts.get('splicemap'))
97 self.branchmap = mapfile(ui, opts.get('branchmap'))
101 self.branchmap = mapfile(ui, opts.get('branchmap'))
98
102
99 def walktree(self, heads):
103 def walktree(self, heads):
100 '''Return a mapping that identifies the uncommitted parents of every
104 '''Return a mapping that identifies the uncommitted parents of every
101 uncommitted changeset.'''
105 uncommitted changeset.'''
102 visit = heads
106 visit = heads
103 known = set()
107 known = set()
104 parents = {}
108 parents = {}
105 while visit:
109 while visit:
106 n = visit.pop(0)
110 n = visit.pop(0)
107 if n in known or n in self.map: continue
111 if n in known or n in self.map: continue
108 known.add(n)
112 known.add(n)
109 commit = self.cachecommit(n)
113 commit = self.cachecommit(n)
110 parents[n] = []
114 parents[n] = []
111 for p in commit.parents:
115 for p in commit.parents:
112 parents[n].append(p)
116 parents[n].append(p)
113 visit.append(p)
117 visit.append(p)
114
118
115 return parents
119 return parents
116
120
117 def toposort(self, parents, sortmode):
121 def toposort(self, parents, sortmode):
118 '''Return an ordering such that every uncommitted changeset is
122 '''Return an ordering such that every uncommitted changeset is
119 preceeded by all its uncommitted ancestors.'''
123 preceeded by all its uncommitted ancestors.'''
120
124
121 def mapchildren(parents):
125 def mapchildren(parents):
122 """Return a (children, roots) tuple where 'children' maps parent
126 """Return a (children, roots) tuple where 'children' maps parent
123 revision identifiers to children ones, and 'roots' is the list of
127 revision identifiers to children ones, and 'roots' is the list of
124 revisions without parents. 'parents' must be a mapping of revision
128 revisions without parents. 'parents' must be a mapping of revision
125 identifier to its parents ones.
129 identifier to its parents ones.
126 """
130 """
127 visit = parents.keys()
131 visit = parents.keys()
128 seen = set()
132 seen = set()
129 children = {}
133 children = {}
130 roots = []
134 roots = []
131
135
132 while visit:
136 while visit:
133 n = visit.pop(0)
137 n = visit.pop(0)
134 if n in seen:
138 if n in seen:
135 continue
139 continue
136 seen.add(n)
140 seen.add(n)
137 # Ensure that nodes without parents are present in the
141 # Ensure that nodes without parents are present in the
138 # 'children' mapping.
142 # 'children' mapping.
139 children.setdefault(n, [])
143 children.setdefault(n, [])
140 hasparent = False
144 hasparent = False
141 for p in parents[n]:
145 for p in parents[n]:
142 if not p in self.map:
146 if not p in self.map:
143 visit.append(p)
147 visit.append(p)
144 hasparent = True
148 hasparent = True
145 children.setdefault(p, []).append(n)
149 children.setdefault(p, []).append(n)
146 if not hasparent:
150 if not hasparent:
147 roots.append(n)
151 roots.append(n)
148
152
149 return children, roots
153 return children, roots
150
154
151 # Sort functions are supposed to take a list of revisions which
155 # Sort functions are supposed to take a list of revisions which
152 # can be converted immediately and pick one
156 # can be converted immediately and pick one
153
157
154 def makebranchsorter():
158 def makebranchsorter():
155 """If the previously converted revision has a child in the
159 """If the previously converted revision has a child in the
156 eligible revisions list, pick it. Return the list head
160 eligible revisions list, pick it. Return the list head
157 otherwise. Branch sort attempts to minimize branch
161 otherwise. Branch sort attempts to minimize branch
158 switching, which is harmful for Mercurial backend
162 switching, which is harmful for Mercurial backend
159 compression.
163 compression.
160 """
164 """
161 prev = [None]
165 prev = [None]
162 def picknext(nodes):
166 def picknext(nodes):
163 next = nodes[0]
167 next = nodes[0]
164 for n in nodes:
168 for n in nodes:
165 if prev[0] in parents[n]:
169 if prev[0] in parents[n]:
166 next = n
170 next = n
167 break
171 break
168 prev[0] = next
172 prev[0] = next
169 return next
173 return next
170 return picknext
174 return picknext
171
175
172 def makesourcesorter():
176 def makesourcesorter():
173 """Source specific sort."""
177 """Source specific sort."""
174 keyfn = lambda n: self.commitcache[n].sortkey
178 keyfn = lambda n: self.commitcache[n].sortkey
175 def picknext(nodes):
179 def picknext(nodes):
176 return sorted(nodes, key=keyfn)[0]
180 return sorted(nodes, key=keyfn)[0]
177 return picknext
181 return picknext
178
182
179 def makedatesorter():
183 def makedatesorter():
180 """Sort revisions by date."""
184 """Sort revisions by date."""
181 dates = {}
185 dates = {}
182 def getdate(n):
186 def getdate(n):
183 if n not in dates:
187 if n not in dates:
184 dates[n] = util.parsedate(self.commitcache[n].date)
188 dates[n] = util.parsedate(self.commitcache[n].date)
185 return dates[n]
189 return dates[n]
186
190
187 def picknext(nodes):
191 def picknext(nodes):
188 return min([(getdate(n), n) for n in nodes])[1]
192 return min([(getdate(n), n) for n in nodes])[1]
189
193
190 return picknext
194 return picknext
191
195
192 if sortmode == 'branchsort':
196 if sortmode == 'branchsort':
193 picknext = makebranchsorter()
197 picknext = makebranchsorter()
194 elif sortmode == 'datesort':
198 elif sortmode == 'datesort':
195 picknext = makedatesorter()
199 picknext = makedatesorter()
196 elif sortmode == 'sourcesort':
200 elif sortmode == 'sourcesort':
197 picknext = makesourcesorter()
201 picknext = makesourcesorter()
198 else:
202 else:
199 raise util.Abort(_('unknown sort mode: %s') % sortmode)
203 raise util.Abort(_('unknown sort mode: %s') % sortmode)
200
204
201 children, actives = mapchildren(parents)
205 children, actives = mapchildren(parents)
202
206
203 s = []
207 s = []
204 pendings = {}
208 pendings = {}
205 while actives:
209 while actives:
206 n = picknext(actives)
210 n = picknext(actives)
207 actives.remove(n)
211 actives.remove(n)
208 s.append(n)
212 s.append(n)
209
213
210 # Update dependents list
214 # Update dependents list
211 for c in children.get(n, []):
215 for c in children.get(n, []):
212 if c not in pendings:
216 if c not in pendings:
213 pendings[c] = [p for p in parents[c] if p not in self.map]
217 pendings[c] = [p for p in parents[c] if p not in self.map]
214 try:
218 try:
215 pendings[c].remove(n)
219 pendings[c].remove(n)
216 except ValueError:
220 except ValueError:
217 raise util.Abort(_('cycle detected between %s and %s')
221 raise util.Abort(_('cycle detected between %s and %s')
218 % (recode(c), recode(n)))
222 % (recode(c), recode(n)))
219 if not pendings[c]:
223 if not pendings[c]:
220 # Parents are converted, node is eligible
224 # Parents are converted, node is eligible
221 actives.insert(0, c)
225 actives.insert(0, c)
222 pendings[c] = None
226 pendings[c] = None
223
227
224 if len(s) != len(parents):
228 if len(s) != len(parents):
225 raise util.Abort(_("not all revisions were sorted"))
229 raise util.Abort(_("not all revisions were sorted"))
226
230
227 return s
231 return s
228
232
229 def writeauthormap(self):
233 def writeauthormap(self):
230 authorfile = self.authorfile
234 authorfile = self.authorfile
231 if authorfile:
235 if authorfile:
232 self.ui.status(_('Writing author map file %s\n') % authorfile)
236 self.ui.status(_('Writing author map file %s\n') % authorfile)
233 ofile = open(authorfile, 'w+')
237 ofile = open(authorfile, 'w+')
234 for author in self.authors:
238 for author in self.authors:
235 ofile.write("%s=%s\n" % (author, self.authors[author]))
239 ofile.write("%s=%s\n" % (author, self.authors[author]))
236 ofile.close()
240 ofile.close()
237
241
238 def readauthormap(self, authorfile):
242 def readauthormap(self, authorfile):
239 afile = open(authorfile, 'r')
243 afile = open(authorfile, 'r')
240 for line in afile:
244 for line in afile:
241
245
242 line = line.strip()
246 line = line.strip()
243 if not line or line.startswith('#'):
247 if not line or line.startswith('#'):
244 continue
248 continue
245
249
246 try:
250 try:
247 srcauthor, dstauthor = line.split('=', 1)
251 srcauthor, dstauthor = line.split('=', 1)
248 except ValueError:
252 except ValueError:
249 msg = _('Ignoring bad line in author map file %s: %s\n')
253 msg = _('Ignoring bad line in author map file %s: %s\n')
250 self.ui.warn(msg % (authorfile, line.rstrip()))
254 self.ui.warn(msg % (authorfile, line.rstrip()))
251 continue
255 continue
252
256
253 srcauthor = srcauthor.strip()
257 srcauthor = srcauthor.strip()
254 dstauthor = dstauthor.strip()
258 dstauthor = dstauthor.strip()
255 if self.authors.get(srcauthor) in (None, dstauthor):
259 if self.authors.get(srcauthor) in (None, dstauthor):
256 msg = _('mapping author %s to %s\n')
260 msg = _('mapping author %s to %s\n')
257 self.ui.debug(msg % (srcauthor, dstauthor))
261 self.ui.debug(msg % (srcauthor, dstauthor))
258 self.authors[srcauthor] = dstauthor
262 self.authors[srcauthor] = dstauthor
259 continue
263 continue
260
264
261 m = _('overriding mapping for author %s, was %s, will be %s\n')
265 m = _('overriding mapping for author %s, was %s, will be %s\n')
262 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
266 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
263
267
264 afile.close()
268 afile.close()
265
269
266 def cachecommit(self, rev):
270 def cachecommit(self, rev):
267 commit = self.source.getcommit(rev)
271 commit = self.source.getcommit(rev)
268 commit.author = self.authors.get(commit.author, commit.author)
272 commit.author = self.authors.get(commit.author, commit.author)
269 commit.branch = self.branchmap.get(commit.branch, commit.branch)
273 commit.branch = self.branchmap.get(commit.branch, commit.branch)
270 self.commitcache[rev] = commit
274 self.commitcache[rev] = commit
271 return commit
275 return commit
272
276
273 def copy(self, rev):
277 def copy(self, rev):
274 commit = self.commitcache[rev]
278 commit = self.commitcache[rev]
275
279
276 changes = self.source.getchanges(rev)
280 changes = self.source.getchanges(rev)
277 if isinstance(changes, basestring):
281 if isinstance(changes, basestring):
278 if changes == SKIPREV:
282 if changes == SKIPREV:
279 dest = SKIPREV
283 dest = SKIPREV
280 else:
284 else:
281 dest = self.map[changes]
285 dest = self.map[changes]
282 self.map[rev] = dest
286 self.map[rev] = dest
283 return
287 return
284 files, copies = changes
288 files, copies = changes
285 pbranches = []
289 pbranches = []
286 if commit.parents:
290 if commit.parents:
287 for prev in commit.parents:
291 for prev in commit.parents:
288 if prev not in self.commitcache:
292 if prev not in self.commitcache:
289 self.cachecommit(prev)
293 self.cachecommit(prev)
290 pbranches.append((self.map[prev],
294 pbranches.append((self.map[prev],
291 self.commitcache[prev].branch))
295 self.commitcache[prev].branch))
292 self.dest.setbranch(commit.branch, pbranches)
296 self.dest.setbranch(commit.branch, pbranches)
293 try:
297 try:
294 parents = self.splicemap[rev].replace(',', ' ').split()
298 parents = self.splicemap[rev].replace(',', ' ').split()
295 self.ui.status(_('spliced in %s as parents of %s\n') %
299 self.ui.status(_('spliced in %s as parents of %s\n') %
296 (parents, rev))
300 (parents, rev))
297 parents = [self.map.get(p, p) for p in parents]
301 parents = [self.map.get(p, p) for p in parents]
298 except KeyError:
302 except KeyError:
299 parents = [b[0] for b in pbranches]
303 parents = [b[0] for b in pbranches]
300 newnode = self.dest.putcommit(files, copies, parents, commit,
304 newnode = self.dest.putcommit(files, copies, parents, commit,
301 self.source, self.map)
305 self.source, self.map)
302 self.source.converted(rev, newnode)
306 self.source.converted(rev, newnode)
303 self.map[rev] = newnode
307 self.map[rev] = newnode
304
308
305 def convert(self, sortmode):
309 def convert(self, sortmode):
306 try:
310 try:
307 self.source.before()
311 self.source.before()
308 self.dest.before()
312 self.dest.before()
309 self.source.setrevmap(self.map)
313 self.source.setrevmap(self.map)
310 self.ui.status(_("scanning source...\n"))
314 self.ui.status(_("scanning source...\n"))
311 heads = self.source.getheads()
315 heads = self.source.getheads()
312 parents = self.walktree(heads)
316 parents = self.walktree(heads)
313 self.ui.status(_("sorting...\n"))
317 self.ui.status(_("sorting...\n"))
314 t = self.toposort(parents, sortmode)
318 t = self.toposort(parents, sortmode)
315 num = len(t)
319 num = len(t)
316 c = None
320 c = None
317
321
318 self.ui.status(_("converting...\n"))
322 self.ui.status(_("converting...\n"))
319 for c in t:
323 for c in t:
320 num -= 1
324 num -= 1
321 desc = self.commitcache[c].desc
325 desc = self.commitcache[c].desc
322 if "\n" in desc:
326 if "\n" in desc:
323 desc = desc.splitlines()[0]
327 desc = desc.splitlines()[0]
324 # convert log message to local encoding without using
328 # convert log message to local encoding without using
325 # tolocal() because encoding.encoding conver() use it as
329 # tolocal() because encoding.encoding conver() use it as
326 # 'utf-8'
330 # 'utf-8'
327 self.ui.status("%d %s\n" % (num, recode(desc)))
331 self.ui.status("%d %s\n" % (num, recode(desc)))
328 self.ui.note(_("source: %s\n") % recode(c))
332 self.ui.note(_("source: %s\n") % recode(c))
329 self.copy(c)
333 self.copy(c)
330
334
331 tags = self.source.gettags()
335 tags = self.source.gettags()
332 ctags = {}
336 ctags = {}
333 for k in tags:
337 for k in tags:
334 v = tags[k]
338 v = tags[k]
335 if self.map.get(v, SKIPREV) != SKIPREV:
339 if self.map.get(v, SKIPREV) != SKIPREV:
336 ctags[k] = self.map[v]
340 ctags[k] = self.map[v]
337
341
338 if c and ctags:
342 if c and ctags:
339 nrev, tagsparent = self.dest.puttags(ctags)
343 nrev, tagsparent = self.dest.puttags(ctags)
340 if nrev and tagsparent:
344 if nrev and tagsparent:
341 # write another hash correspondence to override the previous
345 # write another hash correspondence to override the previous
342 # one so we don't end up with extra tag heads
346 # one so we don't end up with extra tag heads
343 tagsparents = [e for e in self.map.iteritems()
347 tagsparents = [e for e in self.map.iteritems()
344 if e[1] == tagsparent]
348 if e[1] == tagsparent]
345 if tagsparents:
349 if tagsparents:
346 self.map[tagsparents[0][0]] = nrev
350 self.map[tagsparents[0][0]] = nrev
347
351
348 self.writeauthormap()
352 self.writeauthormap()
349 finally:
353 finally:
350 self.cleanup()
354 self.cleanup()
351
355
352 def cleanup(self):
356 def cleanup(self):
353 try:
357 try:
354 self.dest.after()
358 self.dest.after()
355 finally:
359 finally:
356 self.source.after()
360 self.source.after()
357 self.map.close()
361 self.map.close()
358
362
359 def convert(ui, src, dest=None, revmapfile=None, **opts):
363 def convert(ui, src, dest=None, revmapfile=None, **opts):
360 global orig_encoding
364 global orig_encoding
361 orig_encoding = encoding.encoding
365 orig_encoding = encoding.encoding
362 encoding.encoding = 'UTF-8'
366 encoding.encoding = 'UTF-8'
363
367
364 if not dest:
368 if not dest:
365 dest = hg.defaultdest(src) + "-hg"
369 dest = hg.defaultdest(src) + "-hg"
366 ui.status(_("assuming destination %s\n") % dest)
370 ui.status(_("assuming destination %s\n") % dest)
367
371
368 destc = convertsink(ui, dest, opts.get('dest_type'))
372 destc = convertsink(ui, dest, opts.get('dest_type'))
369
373
370 try:
374 try:
371 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
375 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
372 opts.get('rev'))
376 opts.get('rev'))
373 except Exception:
377 except Exception:
374 for path in destc.created:
378 for path in destc.created:
375 shutil.rmtree(path, True)
379 shutil.rmtree(path, True)
376 raise
380 raise
377
381
378 sortmodes = ('branchsort', 'datesort', 'sourcesort')
382 sortmodes = ('branchsort', 'datesort', 'sourcesort')
379 sortmode = [m for m in sortmodes if opts.get(m)]
383 sortmode = [m for m in sortmodes if opts.get(m)]
380 if len(sortmode) > 1:
384 if len(sortmode) > 1:
381 raise util.Abort(_('more than one sort mode specified'))
385 raise util.Abort(_('more than one sort mode specified'))
382 sortmode = sortmode and sortmode[0] or defaultsort
386 sortmode = sortmode and sortmode[0] or defaultsort
383 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
387 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
384 raise util.Abort(_('--sourcesort is not supported by this data source'))
388 raise util.Abort(_('--sourcesort is not supported by this data source'))
385
389
386 fmap = opts.get('filemap')
390 fmap = opts.get('filemap')
387 if fmap:
391 if fmap:
388 srcc = filemap.filemap_source(ui, srcc, fmap)
392 srcc = filemap.filemap_source(ui, srcc, fmap)
389 destc.setfilemapmode(True)
393 destc.setfilemapmode(True)
390
394
391 if not revmapfile:
395 if not revmapfile:
392 try:
396 try:
393 revmapfile = destc.revmapfile()
397 revmapfile = destc.revmapfile()
394 except:
398 except:
395 revmapfile = os.path.join(destc, "map")
399 revmapfile = os.path.join(destc, "map")
396
400
397 c = converter(ui, srcc, destc, revmapfile, opts)
401 c = converter(ui, srcc, destc, revmapfile, opts)
398 c.convert(sortmode)
402 c.convert(sortmode)
399
403
@@ -1,52 +1,59 b''
1 #!/bin/sh
1 #!/bin/sh
2
2
3 cat >> $HGRCPATH <<EOF
3 cat >> $HGRCPATH <<EOF
4 [extensions]
4 [extensions]
5 convert=
5 convert=
6 [convert]
6 [convert]
7 hg.saverev=False
7 hg.saverev=False
8 EOF
8 EOF
9
9
10 hg help convert
10 hg help convert
11
11
12 hg init a
12 hg init a
13 cd a
13 cd a
14 echo a > a
14 echo a > a
15 hg ci -d'0 0' -Ama
15 hg ci -d'0 0' -Ama
16 hg cp a b
16 hg cp a b
17 hg ci -d'1 0' -mb
17 hg ci -d'1 0' -mb
18 hg rm a
18 hg rm a
19 hg ci -d'2 0' -mc
19 hg ci -d'2 0' -mc
20 hg mv b a
20 hg mv b a
21 hg ci -d'3 0' -md
21 hg ci -d'3 0' -md
22 echo a >> a
22 echo a >> a
23 hg ci -d'4 0' -me
23 hg ci -d'4 0' -me
24
24
25 cd ..
25 cd ..
26 hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
26 hg convert a 2>&1 | grep -v 'subversion python bindings could not be loaded'
27 hg --cwd a-hg pull ../a
27 hg --cwd a-hg pull ../a
28
28
29 touch bogusfile
29 touch bogusfile
30 echo % should fail
30 echo % should fail
31 hg convert a bogusfile
31 hg convert a bogusfile
32
32
33 mkdir bogusdir
33 mkdir bogusdir
34 chmod 000 bogusdir
34 chmod 000 bogusdir
35
35
36 echo % should fail
36 echo % should fail
37 hg convert a bogusdir
37 hg convert a bogusdir
38
38
39 echo % should succeed
39 echo % should succeed
40 chmod 700 bogusdir
40 chmod 700 bogusdir
41 hg convert a bogusdir
41 hg convert a bogusdir
42
42
43 echo % test pre and post conversion actions
43 echo % test pre and post conversion actions
44 echo 'include b' > filemap
44 echo 'include b' > filemap
45 hg convert --debug --filemap filemap a partialb | \
45 hg convert --debug --filemap filemap a partialb | \
46 grep 'run hg'
46 grep 'run hg'
47
47
48 echo % converting empty dir should fail "nicely"
48 echo % converting empty dir should fail "nicely"
49 mkdir emptydir
49 mkdir emptydir
50 # override $PATH to ensure p4 not visible; use $PYTHON in case we're
50 # override $PATH to ensure p4 not visible; use $PYTHON in case we're
51 # running from a devel copy, not a temp installation
51 # running from a devel copy, not a temp installation
52 PATH=$BINDIR $PYTHON $BINDIR/hg convert emptydir 2>&1 | sed 's,file://.*/emptydir,.../emptydir,g'
52 PATH=$BINDIR $PYTHON $BINDIR/hg convert emptydir 2>&1 | sed 's,file://.*/emptydir,.../emptydir,g'
53
54 echo % convert with imaginary source type
55 hg convert --source-type foo a a-foo
56 echo % convert with imaginary sink type
57 hg convert --dest-type foo a a-foo
58
59 true
@@ -1,261 +1,266 b''
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
1 hg convert [OPTION]... SOURCE [DEST [REVMAP]]
2
2
3 convert a foreign SCM repository to a Mercurial one.
3 convert a foreign SCM repository to a Mercurial one.
4
4
5 Accepted source formats [identifiers]:
5 Accepted source formats [identifiers]:
6
6
7 - Mercurial [hg]
7 - Mercurial [hg]
8 - CVS [cvs]
8 - CVS [cvs]
9 - Darcs [darcs]
9 - Darcs [darcs]
10 - git [git]
10 - git [git]
11 - Subversion [svn]
11 - Subversion [svn]
12 - Monotone [mtn]
12 - Monotone [mtn]
13 - GNU Arch [gnuarch]
13 - GNU Arch [gnuarch]
14 - Bazaar [bzr]
14 - Bazaar [bzr]
15 - Perforce [p4]
15 - Perforce [p4]
16
16
17 Accepted destination formats [identifiers]:
17 Accepted destination formats [identifiers]:
18
18
19 - Mercurial [hg]
19 - Mercurial [hg]
20 - Subversion [svn] (history on branches is not preserved)
20 - Subversion [svn] (history on branches is not preserved)
21
21
22 If no revision is given, all revisions will be converted. Otherwise,
22 If no revision is given, all revisions will be converted. Otherwise,
23 convert will only import up to the named revision (given in a format
23 convert will only import up to the named revision (given in a format
24 understood by the source).
24 understood by the source).
25
25
26 If no destination directory name is specified, it defaults to the basename
26 If no destination directory name is specified, it defaults to the basename
27 of the source with '-hg' appended. If the destination repository doesn't
27 of the source with '-hg' appended. If the destination repository doesn't
28 exist, it will be created.
28 exist, it will be created.
29
29
30 By default, all sources except Mercurial will use --branchsort. Mercurial
30 By default, all sources except Mercurial will use --branchsort. Mercurial
31 uses --sourcesort to preserve original revision numbers order. Sort modes
31 uses --sourcesort to preserve original revision numbers order. Sort modes
32 have the following effects:
32 have the following effects:
33
33
34 --branchsort convert from parent to child revision when possible, which
34 --branchsort convert from parent to child revision when possible, which
35 means branches are usually converted one after the other. It
35 means branches are usually converted one after the other. It
36 generates more compact repositories.
36 generates more compact repositories.
37 --datesort sort revisions by date. Converted repositories have good-
37 --datesort sort revisions by date. Converted repositories have good-
38 looking changelogs but are often an order of magnitude
38 looking changelogs but are often an order of magnitude
39 larger than the same ones generated by --branchsort.
39 larger than the same ones generated by --branchsort.
40 --sourcesort try to preserve source revisions order, only supported by
40 --sourcesort try to preserve source revisions order, only supported by
41 Mercurial sources.
41 Mercurial sources.
42
42
43 If <REVMAP> isn't given, it will be put in a default location
43 If <REVMAP> isn't given, it will be put in a default location
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
44 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text file that
45 maps each source commit ID to the destination ID for that revision, like
45 maps each source commit ID to the destination ID for that revision, like
46 so:
46 so:
47
47
48 <source ID> <destination ID>
48 <source ID> <destination ID>
49
49
50 If the file doesn't exist, it's automatically created. It's updated on
50 If the file doesn't exist, it's automatically created. It's updated on
51 each commit copied, so convert-repo can be interrupted and can be run
51 each commit copied, so convert-repo can be interrupted and can be run
52 repeatedly to copy new commits.
52 repeatedly to copy new commits.
53
53
54 The [username mapping] file is a simple text file that maps each source
54 The [username mapping] file is a simple text file that maps each source
55 commit author to a destination commit author. It is handy for source SCMs
55 commit author to a destination commit author. It is handy for source SCMs
56 that use unix logins to identify authors (eg: CVS). One line per author
56 that use unix logins to identify authors (eg: CVS). One line per author
57 mapping and the line format is: srcauthor=whatever string you want
57 mapping and the line format is: srcauthor=whatever string you want
58
58
59 The filemap is a file that allows filtering and remapping of files and
59 The filemap is a file that allows filtering and remapping of files and
60 directories. Comment lines start with '#'. Each line can contain one of
60 directories. Comment lines start with '#'. Each line can contain one of
61 the following directives:
61 the following directives:
62
62
63 include path/to/file
63 include path/to/file
64
64
65 exclude path/to/file
65 exclude path/to/file
66
66
67 rename from/file to/file
67 rename from/file to/file
68
68
69 The 'include' directive causes a file, or all files under a directory, to
69 The 'include' directive causes a file, or all files under a directory, to
70 be included in the destination repository, and the exclusion of all other
70 be included in the destination repository, and the exclusion of all other
71 files and directories not explicitly included. The 'exclude' directive
71 files and directories not explicitly included. The 'exclude' directive
72 causes files or directories to be omitted. The 'rename' directive renames
72 causes files or directories to be omitted. The 'rename' directive renames
73 a file or directory. To rename from a subdirectory into the root of the
73 a file or directory. To rename from a subdirectory into the root of the
74 repository, use '.' as the path to rename to.
74 repository, use '.' as the path to rename to.
75
75
76 The splicemap is a file that allows insertion of synthetic history,
76 The splicemap is a file that allows insertion of synthetic history,
77 letting you specify the parents of a revision. This is useful if you want
77 letting you specify the parents of a revision. This is useful if you want
78 to e.g. give a Subversion merge two parents, or graft two disconnected
78 to e.g. give a Subversion merge two parents, or graft two disconnected
79 series of history together. Each entry contains a key, followed by a
79 series of history together. Each entry contains a key, followed by a
80 space, followed by one or two comma-separated values. The key is the
80 space, followed by one or two comma-separated values. The key is the
81 revision ID in the source revision control system whose parents should be
81 revision ID in the source revision control system whose parents should be
82 modified (same format as a key in .hg/shamap). The values are the revision
82 modified (same format as a key in .hg/shamap). The values are the revision
83 IDs (in either the source or destination revision control system) that
83 IDs (in either the source or destination revision control system) that
84 should be used as the new parents for that node. For example, if you have
84 should be used as the new parents for that node. For example, if you have
85 merged "release-1.0" into "trunk", then you should specify the revision on
85 merged "release-1.0" into "trunk", then you should specify the revision on
86 "trunk" as the first parent and the one on the "release-1.0" branch as the
86 "trunk" as the first parent and the one on the "release-1.0" branch as the
87 second.
87 second.
88
88
89 The branchmap is a file that allows you to rename a branch when it is
89 The branchmap is a file that allows you to rename a branch when it is
90 being brought in from whatever external repository. When used in
90 being brought in from whatever external repository. When used in
91 conjunction with a splicemap, it allows for a powerful combination to help
91 conjunction with a splicemap, it allows for a powerful combination to help
92 fix even the most badly mismanaged repositories and turn them into nicely
92 fix even the most badly mismanaged repositories and turn them into nicely
93 structured Mercurial repositories. The branchmap contains lines of the
93 structured Mercurial repositories. The branchmap contains lines of the
94 form "original_branch_name new_branch_name". "original_branch_name" is the
94 form "original_branch_name new_branch_name". "original_branch_name" is the
95 name of the branch in the source repository, and "new_branch_name" is the
95 name of the branch in the source repository, and "new_branch_name" is the
96 name of the branch is the destination repository. This can be used to (for
96 name of the branch is the destination repository. This can be used to (for
97 instance) move code in one repository from "default" to a named branch.
97 instance) move code in one repository from "default" to a named branch.
98
98
99 Mercurial Source
99 Mercurial Source
100 ----------------
100 ----------------
101
101
102 --config convert.hg.ignoreerrors=False (boolean)
102 --config convert.hg.ignoreerrors=False (boolean)
103 ignore integrity errors when reading. Use it to fix Mercurial
103 ignore integrity errors when reading. Use it to fix Mercurial
104 repositories with missing revlogs, by converting from and to
104 repositories with missing revlogs, by converting from and to
105 Mercurial.
105 Mercurial.
106 --config convert.hg.saverev=False (boolean)
106 --config convert.hg.saverev=False (boolean)
107 store original revision ID in changeset (forces target IDs to change)
107 store original revision ID in changeset (forces target IDs to change)
108 --config convert.hg.startrev=0 (hg revision identifier)
108 --config convert.hg.startrev=0 (hg revision identifier)
109 convert start revision and its descendants
109 convert start revision and its descendants
110
110
111 CVS Source
111 CVS Source
112 ----------
112 ----------
113
113
114 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
114 CVS source will use a sandbox (i.e. a checked-out copy) from CVS to
115 indicate the starting point of what will be converted. Direct access to
115 indicate the starting point of what will be converted. Direct access to
116 the repository files is not needed, unless of course the repository is
116 the repository files is not needed, unless of course the repository is
117 :local:. The conversion uses the top level directory in the sandbox to
117 :local:. The conversion uses the top level directory in the sandbox to
118 find the CVS repository, and then uses CVS rlog commands to find files to
118 find the CVS repository, and then uses CVS rlog commands to find files to
119 convert. This means that unless a filemap is given, all files under the
119 convert. This means that unless a filemap is given, all files under the
120 starting directory will be converted, and that any directory
120 starting directory will be converted, and that any directory
121 reorganization in the CVS sandbox is ignored.
121 reorganization in the CVS sandbox is ignored.
122
122
123 The options shown are the defaults.
123 The options shown are the defaults.
124
124
125 --config convert.cvsps.cache=True (boolean)
125 --config convert.cvsps.cache=True (boolean)
126 Set to False to disable remote log caching, for testing and debugging
126 Set to False to disable remote log caching, for testing and debugging
127 purposes.
127 purposes.
128 --config convert.cvsps.fuzz=60 (integer)
128 --config convert.cvsps.fuzz=60 (integer)
129 Specify the maximum time (in seconds) that is allowed between commits
129 Specify the maximum time (in seconds) that is allowed between commits
130 with identical user and log message in a single changeset. When very
130 with identical user and log message in a single changeset. When very
131 large files were checked in as part of a changeset then the default
131 large files were checked in as part of a changeset then the default
132 may not be long enough.
132 may not be long enough.
133 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
133 --config convert.cvsps.mergeto='{{mergetobranch ([-\w]+)}}'
134 Specify a regular expression to which commit log messages are matched.
134 Specify a regular expression to which commit log messages are matched.
135 If a match occurs, then the conversion process will insert a dummy
135 If a match occurs, then the conversion process will insert a dummy
136 revision merging the branch on which this log message occurs to the
136 revision merging the branch on which this log message occurs to the
137 branch indicated in the regex.
137 branch indicated in the regex.
138 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
138 --config convert.cvsps.mergefrom='{{mergefrombranch ([-\w]+)}}'
139 Specify a regular expression to which commit log messages are matched.
139 Specify a regular expression to which commit log messages are matched.
140 If a match occurs, then the conversion process will add the most
140 If a match occurs, then the conversion process will add the most
141 recent revision on the branch indicated in the regex as the second
141 recent revision on the branch indicated in the regex as the second
142 parent of the changeset.
142 parent of the changeset.
143
143
144 An additional "debugcvsps" Mercurial command allows the builtin changeset
144 An additional "debugcvsps" Mercurial command allows the builtin changeset
145 merging code to be run without doing a conversion. Its parameters and
145 merging code to be run without doing a conversion. Its parameters and
146 output are similar to that of cvsps 2.1. Please see the command help for
146 output are similar to that of cvsps 2.1. Please see the command help for
147 more details.
147 more details.
148
148
149 Subversion Source
149 Subversion Source
150 -----------------
150 -----------------
151
151
152 Subversion source detects classical trunk/branches/tags layouts. By
152 Subversion source detects classical trunk/branches/tags layouts. By
153 default, the supplied "svn://repo/path/" source URL is converted as a
153 default, the supplied "svn://repo/path/" source URL is converted as a
154 single branch. If "svn://repo/path/trunk" exists it replaces the default
154 single branch. If "svn://repo/path/trunk" exists it replaces the default
155 branch. If "svn://repo/path/branches" exists, its subdirectories are
155 branch. If "svn://repo/path/branches" exists, its subdirectories are
156 listed as possible branches. If "svn://repo/path/tags" exists, it is
156 listed as possible branches. If "svn://repo/path/tags" exists, it is
157 looked for tags referencing converted branches. Default "trunk",
157 looked for tags referencing converted branches. Default "trunk",
158 "branches" and "tags" values can be overridden with following options. Set
158 "branches" and "tags" values can be overridden with following options. Set
159 them to paths relative to the source URL, or leave them blank to disable
159 them to paths relative to the source URL, or leave them blank to disable
160 auto detection.
160 auto detection.
161
161
162 --config convert.svn.branches=branches (directory name)
162 --config convert.svn.branches=branches (directory name)
163 specify the directory containing branches
163 specify the directory containing branches
164 --config convert.svn.tags=tags (directory name)
164 --config convert.svn.tags=tags (directory name)
165 specify the directory containing tags
165 specify the directory containing tags
166 --config convert.svn.trunk=trunk (directory name)
166 --config convert.svn.trunk=trunk (directory name)
167 specify the name of the trunk branch
167 specify the name of the trunk branch
168
168
169 Source history can be retrieved starting at a specific revision, instead
169 Source history can be retrieved starting at a specific revision, instead
170 of being integrally converted. Only single branch conversions are
170 of being integrally converted. Only single branch conversions are
171 supported.
171 supported.
172
172
173 --config convert.svn.startrev=0 (svn revision number)
173 --config convert.svn.startrev=0 (svn revision number)
174 specify start Subversion revision.
174 specify start Subversion revision.
175
175
176 Perforce Source
176 Perforce Source
177 ---------------
177 ---------------
178
178
179 The Perforce (P4) importer can be given a p4 depot path or a client
179 The Perforce (P4) importer can be given a p4 depot path or a client
180 specification as source. It will convert all files in the source to a flat
180 specification as source. It will convert all files in the source to a flat
181 Mercurial repository, ignoring labels, branches and integrations. Note
181 Mercurial repository, ignoring labels, branches and integrations. Note
182 that when a depot path is given you then usually should specify a target
182 that when a depot path is given you then usually should specify a target
183 directory, because otherwise the target may be named ...-hg.
183 directory, because otherwise the target may be named ...-hg.
184
184
185 It is possible to limit the amount of source history to be converted by
185 It is possible to limit the amount of source history to be converted by
186 specifying an initial Perforce revision.
186 specifying an initial Perforce revision.
187
187
188 --config convert.p4.startrev=0 (perforce changelist number)
188 --config convert.p4.startrev=0 (perforce changelist number)
189 specify initial Perforce revision.
189 specify initial Perforce revision.
190
190
191 Mercurial Destination
191 Mercurial Destination
192 ---------------------
192 ---------------------
193
193
194 --config convert.hg.clonebranches=False (boolean)
194 --config convert.hg.clonebranches=False (boolean)
195 dispatch source branches in separate clones.
195 dispatch source branches in separate clones.
196 --config convert.hg.tagsbranch=default (branch name)
196 --config convert.hg.tagsbranch=default (branch name)
197 tag revisions branch name
197 tag revisions branch name
198 --config convert.hg.usebranchnames=True (boolean)
198 --config convert.hg.usebranchnames=True (boolean)
199 preserve branch names
199 preserve branch names
200
200
201 options:
201 options:
202
202
203 -A --authors username mapping filename
203 -A --authors username mapping filename
204 -d --dest-type destination repository type
204 -d --dest-type destination repository type
205 --filemap remap file names using contents of file
205 --filemap remap file names using contents of file
206 -r --rev import up to target revision REV
206 -r --rev import up to target revision REV
207 -s --source-type source repository type
207 -s --source-type source repository type
208 --splicemap splice synthesized history into place
208 --splicemap splice synthesized history into place
209 --branchmap change branch names while converting
209 --branchmap change branch names while converting
210 --branchsort try to sort changesets by branches
210 --branchsort try to sort changesets by branches
211 --datesort try to sort changesets by date
211 --datesort try to sort changesets by date
212 --sourcesort preserve source changesets order
212 --sourcesort preserve source changesets order
213
213
214 use "hg -v help convert" to show global options
214 use "hg -v help convert" to show global options
215 adding a
215 adding a
216 assuming destination a-hg
216 assuming destination a-hg
217 initializing destination a-hg repository
217 initializing destination a-hg repository
218 scanning source...
218 scanning source...
219 sorting...
219 sorting...
220 converting...
220 converting...
221 4 a
221 4 a
222 3 b
222 3 b
223 2 c
223 2 c
224 1 d
224 1 d
225 0 e
225 0 e
226 pulling from ../a
226 pulling from ../a
227 searching for changes
227 searching for changes
228 no changes found
228 no changes found
229 % should fail
229 % should fail
230 initializing destination bogusfile repository
230 initializing destination bogusfile repository
231 abort: cannot create new bundle repository
231 abort: cannot create new bundle repository
232 % should fail
232 % should fail
233 abort: Permission denied: bogusdir
233 abort: Permission denied: bogusdir
234 % should succeed
234 % should succeed
235 initializing destination bogusdir repository
235 initializing destination bogusdir repository
236 scanning source...
236 scanning source...
237 sorting...
237 sorting...
238 converting...
238 converting...
239 4 a
239 4 a
240 3 b
240 3 b
241 2 c
241 2 c
242 1 d
242 1 d
243 0 e
243 0 e
244 % test pre and post conversion actions
244 % test pre and post conversion actions
245 run hg source pre-conversion action
245 run hg source pre-conversion action
246 run hg sink pre-conversion action
246 run hg sink pre-conversion action
247 run hg sink post-conversion action
247 run hg sink post-conversion action
248 run hg source post-conversion action
248 run hg source post-conversion action
249 % converting empty dir should fail nicely
249 % converting empty dir should fail nicely
250 assuming destination emptydir-hg
250 assuming destination emptydir-hg
251 initializing destination emptydir-hg repository
251 initializing destination emptydir-hg repository
252 emptydir does not look like a CVS checkout
252 emptydir does not look like a CVS checkout
253 emptydir does not look like a Git repo
253 emptydir does not look like a Git repo
254 emptydir does not look like a Subversion repo
254 emptydir does not look like a Subversion repo
255 emptydir is not a local Mercurial repo
255 emptydir is not a local Mercurial repo
256 emptydir does not look like a darcs repo
256 emptydir does not look like a darcs repo
257 emptydir does not look like a monotone repo
257 emptydir does not look like a monotone repo
258 emptydir does not look like a GNU Arch repo
258 emptydir does not look like a GNU Arch repo
259 emptydir does not look like a Bazaar repo
259 emptydir does not look like a Bazaar repo
260 cannot find required "p4" tool
260 cannot find required "p4" tool
261 abort: emptydir: missing or unsupported repository
261 abort: emptydir: missing or unsupported repository
262 % convert with imaginary source type
263 initializing destination a-foo repository
264 abort: foo: invalid source repository type
265 % convert with imaginary sink type
266 abort: foo: invalid destination repository type
General Comments 0
You need to be logged in to leave comments. Login now