##// END OF EJS Templates
convcmd: use our shlex wrapper to avoid Python 3 tracebacks...
Augie Fackler -
r36576:d4c98b67 default
parent child Browse files
Show More
@@ -1,618 +1,615
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 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import collections
9 import collections
10 import os
10 import os
11 import shlex
12 import shutil
11 import shutil
13
12
14 from mercurial.i18n import _
13 from mercurial.i18n import _
15 from mercurial import (
14 from mercurial import (
16 encoding,
15 encoding,
17 error,
16 error,
18 hg,
17 hg,
19 pycompat,
18 pycompat,
20 scmutil,
19 scmutil,
21 util,
20 util,
22 )
21 )
23
22
24 from . import (
23 from . import (
25 bzr,
24 bzr,
26 common,
25 common,
27 cvs,
26 cvs,
28 darcs,
27 darcs,
29 filemap,
28 filemap,
30 git,
29 git,
31 gnuarch,
30 gnuarch,
32 hg as hgconvert,
31 hg as hgconvert,
33 monotone,
32 monotone,
34 p4,
33 p4,
35 subversion,
34 subversion,
36 )
35 )
37
36
38 mapfile = common.mapfile
37 mapfile = common.mapfile
39 MissingTool = common.MissingTool
38 MissingTool = common.MissingTool
40 NoRepo = common.NoRepo
39 NoRepo = common.NoRepo
41 SKIPREV = common.SKIPREV
40 SKIPREV = common.SKIPREV
42
41
43 bzr_source = bzr.bzr_source
42 bzr_source = bzr.bzr_source
44 convert_cvs = cvs.convert_cvs
43 convert_cvs = cvs.convert_cvs
45 convert_git = git.convert_git
44 convert_git = git.convert_git
46 darcs_source = darcs.darcs_source
45 darcs_source = darcs.darcs_source
47 gnuarch_source = gnuarch.gnuarch_source
46 gnuarch_source = gnuarch.gnuarch_source
48 mercurial_sink = hgconvert.mercurial_sink
47 mercurial_sink = hgconvert.mercurial_sink
49 mercurial_source = hgconvert.mercurial_source
48 mercurial_source = hgconvert.mercurial_source
50 monotone_source = monotone.monotone_source
49 monotone_source = monotone.monotone_source
51 p4_source = p4.p4_source
50 p4_source = p4.p4_source
52 svn_sink = subversion.svn_sink
51 svn_sink = subversion.svn_sink
53 svn_source = subversion.svn_source
52 svn_source = subversion.svn_source
54
53
55 orig_encoding = 'ascii'
54 orig_encoding = 'ascii'
56
55
57 def recode(s):
56 def recode(s):
58 if isinstance(s, unicode):
57 if isinstance(s, unicode):
59 return s.encode(pycompat.sysstr(orig_encoding), 'replace')
58 return s.encode(pycompat.sysstr(orig_encoding), 'replace')
60 else:
59 else:
61 return s.decode('utf-8').encode(
60 return s.decode('utf-8').encode(
62 pycompat.sysstr(orig_encoding), 'replace')
61 pycompat.sysstr(orig_encoding), 'replace')
63
62
64 def mapbranch(branch, branchmap):
63 def mapbranch(branch, branchmap):
65 '''
64 '''
66 >>> bmap = {b'default': b'branch1'}
65 >>> bmap = {b'default': b'branch1'}
67 >>> for i in [b'', None]:
66 >>> for i in [b'', None]:
68 ... mapbranch(i, bmap)
67 ... mapbranch(i, bmap)
69 'branch1'
68 'branch1'
70 'branch1'
69 'branch1'
71 >>> bmap = {b'None': b'branch2'}
70 >>> bmap = {b'None': b'branch2'}
72 >>> for i in [b'', None]:
71 >>> for i in [b'', None]:
73 ... mapbranch(i, bmap)
72 ... mapbranch(i, bmap)
74 'branch2'
73 'branch2'
75 'branch2'
74 'branch2'
76 >>> bmap = {b'None': b'branch3', b'default': b'branch4'}
75 >>> bmap = {b'None': b'branch3', b'default': b'branch4'}
77 >>> for i in [b'None', b'', None, b'default', b'branch5']:
76 >>> for i in [b'None', b'', None, b'default', b'branch5']:
78 ... mapbranch(i, bmap)
77 ... mapbranch(i, bmap)
79 'branch3'
78 'branch3'
80 'branch4'
79 'branch4'
81 'branch4'
80 'branch4'
82 'branch4'
81 'branch4'
83 'branch5'
82 'branch5'
84 '''
83 '''
85 # If branch is None or empty, this commit is coming from the source
84 # If branch is None or empty, this commit is coming from the source
86 # repository's default branch and destined for the default branch in the
85 # repository's default branch and destined for the default branch in the
87 # destination repository. For such commits, using a literal "default"
86 # destination repository. For such commits, using a literal "default"
88 # in branchmap below allows the user to map "default" to an alternate
87 # in branchmap below allows the user to map "default" to an alternate
89 # default branch in the destination repository.
88 # default branch in the destination repository.
90 branch = branchmap.get(branch or 'default', branch)
89 branch = branchmap.get(branch or 'default', branch)
91 # At some point we used "None" literal to denote the default branch,
90 # At some point we used "None" literal to denote the default branch,
92 # attempt to use that for backward compatibility.
91 # attempt to use that for backward compatibility.
93 if (not branch):
92 if (not branch):
94 branch = branchmap.get('None', branch)
93 branch = branchmap.get('None', branch)
95 return branch
94 return branch
96
95
97 source_converters = [
96 source_converters = [
98 ('cvs', convert_cvs, 'branchsort'),
97 ('cvs', convert_cvs, 'branchsort'),
99 ('git', convert_git, 'branchsort'),
98 ('git', convert_git, 'branchsort'),
100 ('svn', svn_source, 'branchsort'),
99 ('svn', svn_source, 'branchsort'),
101 ('hg', mercurial_source, 'sourcesort'),
100 ('hg', mercurial_source, 'sourcesort'),
102 ('darcs', darcs_source, 'branchsort'),
101 ('darcs', darcs_source, 'branchsort'),
103 ('mtn', monotone_source, 'branchsort'),
102 ('mtn', monotone_source, 'branchsort'),
104 ('gnuarch', gnuarch_source, 'branchsort'),
103 ('gnuarch', gnuarch_source, 'branchsort'),
105 ('bzr', bzr_source, 'branchsort'),
104 ('bzr', bzr_source, 'branchsort'),
106 ('p4', p4_source, 'branchsort'),
105 ('p4', p4_source, 'branchsort'),
107 ]
106 ]
108
107
109 sink_converters = [
108 sink_converters = [
110 ('hg', mercurial_sink),
109 ('hg', mercurial_sink),
111 ('svn', svn_sink),
110 ('svn', svn_sink),
112 ]
111 ]
113
112
114 def convertsource(ui, path, type, revs):
113 def convertsource(ui, path, type, revs):
115 exceptions = []
114 exceptions = []
116 if type and type not in [s[0] for s in source_converters]:
115 if type and type not in [s[0] for s in source_converters]:
117 raise error.Abort(_('%s: invalid source repository type') % type)
116 raise error.Abort(_('%s: invalid source repository type') % type)
118 for name, source, sortmode in source_converters:
117 for name, source, sortmode in source_converters:
119 try:
118 try:
120 if not type or name == type:
119 if not type or name == type:
121 return source(ui, name, path, revs), sortmode
120 return source(ui, name, path, revs), sortmode
122 except (NoRepo, MissingTool) as inst:
121 except (NoRepo, MissingTool) as inst:
123 exceptions.append(inst)
122 exceptions.append(inst)
124 if not ui.quiet:
123 if not ui.quiet:
125 for inst in exceptions:
124 for inst in exceptions:
126 ui.write("%s\n" % inst)
125 ui.write("%s\n" % inst)
127 raise error.Abort(_('%s: missing or unsupported repository') % path)
126 raise error.Abort(_('%s: missing or unsupported repository') % path)
128
127
129 def convertsink(ui, path, type):
128 def convertsink(ui, path, type):
130 if type and type not in [s[0] for s in sink_converters]:
129 if type and type not in [s[0] for s in sink_converters]:
131 raise error.Abort(_('%s: invalid destination repository type') % type)
130 raise error.Abort(_('%s: invalid destination repository type') % type)
132 for name, sink in sink_converters:
131 for name, sink in sink_converters:
133 try:
132 try:
134 if not type or name == type:
133 if not type or name == type:
135 return sink(ui, name, path)
134 return sink(ui, name, path)
136 except NoRepo as inst:
135 except NoRepo as inst:
137 ui.note(_("convert: %s\n") % inst)
136 ui.note(_("convert: %s\n") % inst)
138 except MissingTool as inst:
137 except MissingTool as inst:
139 raise error.Abort('%s\n' % inst)
138 raise error.Abort('%s\n' % inst)
140 raise error.Abort(_('%s: unknown repository type') % path)
139 raise error.Abort(_('%s: unknown repository type') % path)
141
140
142 class progresssource(object):
141 class progresssource(object):
143 def __init__(self, ui, source, filecount):
142 def __init__(self, ui, source, filecount):
144 self.ui = ui
143 self.ui = ui
145 self.source = source
144 self.source = source
146 self.filecount = filecount
145 self.filecount = filecount
147 self.retrieved = 0
146 self.retrieved = 0
148
147
149 def getfile(self, file, rev):
148 def getfile(self, file, rev):
150 self.retrieved += 1
149 self.retrieved += 1
151 self.ui.progress(_('getting files'), self.retrieved,
150 self.ui.progress(_('getting files'), self.retrieved,
152 item=file, total=self.filecount, unit=_('files'))
151 item=file, total=self.filecount, unit=_('files'))
153 return self.source.getfile(file, rev)
152 return self.source.getfile(file, rev)
154
153
155 def targetfilebelongstosource(self, targetfilename):
154 def targetfilebelongstosource(self, targetfilename):
156 return self.source.targetfilebelongstosource(targetfilename)
155 return self.source.targetfilebelongstosource(targetfilename)
157
156
158 def lookuprev(self, rev):
157 def lookuprev(self, rev):
159 return self.source.lookuprev(rev)
158 return self.source.lookuprev(rev)
160
159
161 def close(self):
160 def close(self):
162 self.ui.progress(_('getting files'), None)
161 self.ui.progress(_('getting files'), None)
163
162
164 class converter(object):
163 class converter(object):
165 def __init__(self, ui, source, dest, revmapfile, opts):
164 def __init__(self, ui, source, dest, revmapfile, opts):
166
165
167 self.source = source
166 self.source = source
168 self.dest = dest
167 self.dest = dest
169 self.ui = ui
168 self.ui = ui
170 self.opts = opts
169 self.opts = opts
171 self.commitcache = {}
170 self.commitcache = {}
172 self.authors = {}
171 self.authors = {}
173 self.authorfile = None
172 self.authorfile = None
174
173
175 # Record converted revisions persistently: maps source revision
174 # Record converted revisions persistently: maps source revision
176 # ID to target revision ID (both strings). (This is how
175 # ID to target revision ID (both strings). (This is how
177 # incremental conversions work.)
176 # incremental conversions work.)
178 self.map = mapfile(ui, revmapfile)
177 self.map = mapfile(ui, revmapfile)
179
178
180 # Read first the dst author map if any
179 # Read first the dst author map if any
181 authorfile = self.dest.authorfile()
180 authorfile = self.dest.authorfile()
182 if authorfile and os.path.exists(authorfile):
181 if authorfile and os.path.exists(authorfile):
183 self.readauthormap(authorfile)
182 self.readauthormap(authorfile)
184 # Extend/Override with new author map if necessary
183 # Extend/Override with new author map if necessary
185 if opts.get('authormap'):
184 if opts.get('authormap'):
186 self.readauthormap(opts.get('authormap'))
185 self.readauthormap(opts.get('authormap'))
187 self.authorfile = self.dest.authorfile()
186 self.authorfile = self.dest.authorfile()
188
187
189 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
188 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
190 self.branchmap = mapfile(ui, opts.get('branchmap'))
189 self.branchmap = mapfile(ui, opts.get('branchmap'))
191
190
192 def parsesplicemap(self, path):
191 def parsesplicemap(self, path):
193 """ check and validate the splicemap format and
192 """ check and validate the splicemap format and
194 return a child/parents dictionary.
193 return a child/parents dictionary.
195 Format checking has two parts.
194 Format checking has two parts.
196 1. generic format which is same across all source types
195 1. generic format which is same across all source types
197 2. specific format checking which may be different for
196 2. specific format checking which may be different for
198 different source type. This logic is implemented in
197 different source type. This logic is implemented in
199 checkrevformat function in source files like
198 checkrevformat function in source files like
200 hg.py, subversion.py etc.
199 hg.py, subversion.py etc.
201 """
200 """
202
201
203 if not path:
202 if not path:
204 return {}
203 return {}
205 m = {}
204 m = {}
206 try:
205 try:
207 fp = open(path, 'rb')
206 fp = open(path, 'rb')
208 for i, line in enumerate(util.iterfile(fp)):
207 for i, line in enumerate(util.iterfile(fp)):
209 line = line.splitlines()[0].rstrip()
208 line = line.splitlines()[0].rstrip()
210 if not line:
209 if not line:
211 # Ignore blank lines
210 # Ignore blank lines
212 continue
211 continue
213 # split line
212 # split line
214 lex = shlex.shlex(line, posix=True)
213 lex = common.shlexer(data=line, whitespace=',')
215 lex.whitespace_split = True
216 lex.whitespace += ','
217 line = list(lex)
214 line = list(lex)
218 # check number of parents
215 # check number of parents
219 if not (2 <= len(line) <= 3):
216 if not (2 <= len(line) <= 3):
220 raise error.Abort(_('syntax error in %s(%d): child parent1'
217 raise error.Abort(_('syntax error in %s(%d): child parent1'
221 '[,parent2] expected') % (path, i + 1))
218 '[,parent2] expected') % (path, i + 1))
222 for part in line:
219 for part in line:
223 self.source.checkrevformat(part)
220 self.source.checkrevformat(part)
224 child, p1, p2 = line[0], line[1:2], line[2:]
221 child, p1, p2 = line[0], line[1:2], line[2:]
225 if p1 == p2:
222 if p1 == p2:
226 m[child] = p1
223 m[child] = p1
227 else:
224 else:
228 m[child] = p1 + p2
225 m[child] = p1 + p2
229 # if file does not exist or error reading, exit
226 # if file does not exist or error reading, exit
230 except IOError:
227 except IOError:
231 raise error.Abort(_('splicemap file not found or error reading %s:')
228 raise error.Abort(_('splicemap file not found or error reading %s:')
232 % path)
229 % path)
233 return m
230 return m
234
231
235
232
236 def walktree(self, heads):
233 def walktree(self, heads):
237 '''Return a mapping that identifies the uncommitted parents of every
234 '''Return a mapping that identifies the uncommitted parents of every
238 uncommitted changeset.'''
235 uncommitted changeset.'''
239 visit = heads
236 visit = heads
240 known = set()
237 known = set()
241 parents = {}
238 parents = {}
242 numcommits = self.source.numcommits()
239 numcommits = self.source.numcommits()
243 while visit:
240 while visit:
244 n = visit.pop(0)
241 n = visit.pop(0)
245 if n in known:
242 if n in known:
246 continue
243 continue
247 if n in self.map:
244 if n in self.map:
248 m = self.map[n]
245 m = self.map[n]
249 if m == SKIPREV or self.dest.hascommitfrommap(m):
246 if m == SKIPREV or self.dest.hascommitfrommap(m):
250 continue
247 continue
251 known.add(n)
248 known.add(n)
252 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
249 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
253 total=numcommits)
250 total=numcommits)
254 commit = self.cachecommit(n)
251 commit = self.cachecommit(n)
255 parents[n] = []
252 parents[n] = []
256 for p in commit.parents:
253 for p in commit.parents:
257 parents[n].append(p)
254 parents[n].append(p)
258 visit.append(p)
255 visit.append(p)
259 self.ui.progress(_('scanning'), None)
256 self.ui.progress(_('scanning'), None)
260
257
261 return parents
258 return parents
262
259
263 def mergesplicemap(self, parents, splicemap):
260 def mergesplicemap(self, parents, splicemap):
264 """A splicemap redefines child/parent relationships. Check the
261 """A splicemap redefines child/parent relationships. Check the
265 map contains valid revision identifiers and merge the new
262 map contains valid revision identifiers and merge the new
266 links in the source graph.
263 links in the source graph.
267 """
264 """
268 for c in sorted(splicemap):
265 for c in sorted(splicemap):
269 if c not in parents:
266 if c not in parents:
270 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
267 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
271 # Could be in source but not converted during this run
268 # Could be in source but not converted during this run
272 self.ui.warn(_('splice map revision %s is not being '
269 self.ui.warn(_('splice map revision %s is not being '
273 'converted, ignoring\n') % c)
270 'converted, ignoring\n') % c)
274 continue
271 continue
275 pc = []
272 pc = []
276 for p in splicemap[c]:
273 for p in splicemap[c]:
277 # We do not have to wait for nodes already in dest.
274 # We do not have to wait for nodes already in dest.
278 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
275 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
279 continue
276 continue
280 # Parent is not in dest and not being converted, not good
277 # Parent is not in dest and not being converted, not good
281 if p not in parents:
278 if p not in parents:
282 raise error.Abort(_('unknown splice map parent: %s') % p)
279 raise error.Abort(_('unknown splice map parent: %s') % p)
283 pc.append(p)
280 pc.append(p)
284 parents[c] = pc
281 parents[c] = pc
285
282
286 def toposort(self, parents, sortmode):
283 def toposort(self, parents, sortmode):
287 '''Return an ordering such that every uncommitted changeset is
284 '''Return an ordering such that every uncommitted changeset is
288 preceded by all its uncommitted ancestors.'''
285 preceded by all its uncommitted ancestors.'''
289
286
290 def mapchildren(parents):
287 def mapchildren(parents):
291 """Return a (children, roots) tuple where 'children' maps parent
288 """Return a (children, roots) tuple where 'children' maps parent
292 revision identifiers to children ones, and 'roots' is the list of
289 revision identifiers to children ones, and 'roots' is the list of
293 revisions without parents. 'parents' must be a mapping of revision
290 revisions without parents. 'parents' must be a mapping of revision
294 identifier to its parents ones.
291 identifier to its parents ones.
295 """
292 """
296 visit = collections.deque(sorted(parents))
293 visit = collections.deque(sorted(parents))
297 seen = set()
294 seen = set()
298 children = {}
295 children = {}
299 roots = []
296 roots = []
300
297
301 while visit:
298 while visit:
302 n = visit.popleft()
299 n = visit.popleft()
303 if n in seen:
300 if n in seen:
304 continue
301 continue
305 seen.add(n)
302 seen.add(n)
306 # Ensure that nodes without parents are present in the
303 # Ensure that nodes without parents are present in the
307 # 'children' mapping.
304 # 'children' mapping.
308 children.setdefault(n, [])
305 children.setdefault(n, [])
309 hasparent = False
306 hasparent = False
310 for p in parents[n]:
307 for p in parents[n]:
311 if p not in self.map:
308 if p not in self.map:
312 visit.append(p)
309 visit.append(p)
313 hasparent = True
310 hasparent = True
314 children.setdefault(p, []).append(n)
311 children.setdefault(p, []).append(n)
315 if not hasparent:
312 if not hasparent:
316 roots.append(n)
313 roots.append(n)
317
314
318 return children, roots
315 return children, roots
319
316
320 # Sort functions are supposed to take a list of revisions which
317 # Sort functions are supposed to take a list of revisions which
321 # can be converted immediately and pick one
318 # can be converted immediately and pick one
322
319
323 def makebranchsorter():
320 def makebranchsorter():
324 """If the previously converted revision has a child in the
321 """If the previously converted revision has a child in the
325 eligible revisions list, pick it. Return the list head
322 eligible revisions list, pick it. Return the list head
326 otherwise. Branch sort attempts to minimize branch
323 otherwise. Branch sort attempts to minimize branch
327 switching, which is harmful for Mercurial backend
324 switching, which is harmful for Mercurial backend
328 compression.
325 compression.
329 """
326 """
330 prev = [None]
327 prev = [None]
331 def picknext(nodes):
328 def picknext(nodes):
332 next = nodes[0]
329 next = nodes[0]
333 for n in nodes:
330 for n in nodes:
334 if prev[0] in parents[n]:
331 if prev[0] in parents[n]:
335 next = n
332 next = n
336 break
333 break
337 prev[0] = next
334 prev[0] = next
338 return next
335 return next
339 return picknext
336 return picknext
340
337
341 def makesourcesorter():
338 def makesourcesorter():
342 """Source specific sort."""
339 """Source specific sort."""
343 keyfn = lambda n: self.commitcache[n].sortkey
340 keyfn = lambda n: self.commitcache[n].sortkey
344 def picknext(nodes):
341 def picknext(nodes):
345 return sorted(nodes, key=keyfn)[0]
342 return sorted(nodes, key=keyfn)[0]
346 return picknext
343 return picknext
347
344
348 def makeclosesorter():
345 def makeclosesorter():
349 """Close order sort."""
346 """Close order sort."""
350 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
347 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
351 self.commitcache[n].sortkey)
348 self.commitcache[n].sortkey)
352 def picknext(nodes):
349 def picknext(nodes):
353 return sorted(nodes, key=keyfn)[0]
350 return sorted(nodes, key=keyfn)[0]
354 return picknext
351 return picknext
355
352
356 def makedatesorter():
353 def makedatesorter():
357 """Sort revisions by date."""
354 """Sort revisions by date."""
358 dates = {}
355 dates = {}
359 def getdate(n):
356 def getdate(n):
360 if n not in dates:
357 if n not in dates:
361 dates[n] = util.parsedate(self.commitcache[n].date)
358 dates[n] = util.parsedate(self.commitcache[n].date)
362 return dates[n]
359 return dates[n]
363
360
364 def picknext(nodes):
361 def picknext(nodes):
365 return min([(getdate(n), n) for n in nodes])[1]
362 return min([(getdate(n), n) for n in nodes])[1]
366
363
367 return picknext
364 return picknext
368
365
369 if sortmode == 'branchsort':
366 if sortmode == 'branchsort':
370 picknext = makebranchsorter()
367 picknext = makebranchsorter()
371 elif sortmode == 'datesort':
368 elif sortmode == 'datesort':
372 picknext = makedatesorter()
369 picknext = makedatesorter()
373 elif sortmode == 'sourcesort':
370 elif sortmode == 'sourcesort':
374 picknext = makesourcesorter()
371 picknext = makesourcesorter()
375 elif sortmode == 'closesort':
372 elif sortmode == 'closesort':
376 picknext = makeclosesorter()
373 picknext = makeclosesorter()
377 else:
374 else:
378 raise error.Abort(_('unknown sort mode: %s') % sortmode)
375 raise error.Abort(_('unknown sort mode: %s') % sortmode)
379
376
380 children, actives = mapchildren(parents)
377 children, actives = mapchildren(parents)
381
378
382 s = []
379 s = []
383 pendings = {}
380 pendings = {}
384 while actives:
381 while actives:
385 n = picknext(actives)
382 n = picknext(actives)
386 actives.remove(n)
383 actives.remove(n)
387 s.append(n)
384 s.append(n)
388
385
389 # Update dependents list
386 # Update dependents list
390 for c in children.get(n, []):
387 for c in children.get(n, []):
391 if c not in pendings:
388 if c not in pendings:
392 pendings[c] = [p for p in parents[c] if p not in self.map]
389 pendings[c] = [p for p in parents[c] if p not in self.map]
393 try:
390 try:
394 pendings[c].remove(n)
391 pendings[c].remove(n)
395 except ValueError:
392 except ValueError:
396 raise error.Abort(_('cycle detected between %s and %s')
393 raise error.Abort(_('cycle detected between %s and %s')
397 % (recode(c), recode(n)))
394 % (recode(c), recode(n)))
398 if not pendings[c]:
395 if not pendings[c]:
399 # Parents are converted, node is eligible
396 # Parents are converted, node is eligible
400 actives.insert(0, c)
397 actives.insert(0, c)
401 pendings[c] = None
398 pendings[c] = None
402
399
403 if len(s) != len(parents):
400 if len(s) != len(parents):
404 raise error.Abort(_("not all revisions were sorted"))
401 raise error.Abort(_("not all revisions were sorted"))
405
402
406 return s
403 return s
407
404
408 def writeauthormap(self):
405 def writeauthormap(self):
409 authorfile = self.authorfile
406 authorfile = self.authorfile
410 if authorfile:
407 if authorfile:
411 self.ui.status(_('writing author map file %s\n') % authorfile)
408 self.ui.status(_('writing author map file %s\n') % authorfile)
412 ofile = open(authorfile, 'wb+')
409 ofile = open(authorfile, 'wb+')
413 for author in self.authors:
410 for author in self.authors:
414 ofile.write(util.tonativeeol("%s=%s\n"
411 ofile.write(util.tonativeeol("%s=%s\n"
415 % (author, self.authors[author])))
412 % (author, self.authors[author])))
416 ofile.close()
413 ofile.close()
417
414
418 def readauthormap(self, authorfile):
415 def readauthormap(self, authorfile):
419 afile = open(authorfile, 'rb')
416 afile = open(authorfile, 'rb')
420 for line in afile:
417 for line in afile:
421
418
422 line = line.strip()
419 line = line.strip()
423 if not line or line.startswith('#'):
420 if not line or line.startswith('#'):
424 continue
421 continue
425
422
426 try:
423 try:
427 srcauthor, dstauthor = line.split('=', 1)
424 srcauthor, dstauthor = line.split('=', 1)
428 except ValueError:
425 except ValueError:
429 msg = _('ignoring bad line in author map file %s: %s\n')
426 msg = _('ignoring bad line in author map file %s: %s\n')
430 self.ui.warn(msg % (authorfile, line.rstrip()))
427 self.ui.warn(msg % (authorfile, line.rstrip()))
431 continue
428 continue
432
429
433 srcauthor = srcauthor.strip()
430 srcauthor = srcauthor.strip()
434 dstauthor = dstauthor.strip()
431 dstauthor = dstauthor.strip()
435 if self.authors.get(srcauthor) in (None, dstauthor):
432 if self.authors.get(srcauthor) in (None, dstauthor):
436 msg = _('mapping author %s to %s\n')
433 msg = _('mapping author %s to %s\n')
437 self.ui.debug(msg % (srcauthor, dstauthor))
434 self.ui.debug(msg % (srcauthor, dstauthor))
438 self.authors[srcauthor] = dstauthor
435 self.authors[srcauthor] = dstauthor
439 continue
436 continue
440
437
441 m = _('overriding mapping for author %s, was %s, will be %s\n')
438 m = _('overriding mapping for author %s, was %s, will be %s\n')
442 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
439 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
443
440
444 afile.close()
441 afile.close()
445
442
446 def cachecommit(self, rev):
443 def cachecommit(self, rev):
447 commit = self.source.getcommit(rev)
444 commit = self.source.getcommit(rev)
448 commit.author = self.authors.get(commit.author, commit.author)
445 commit.author = self.authors.get(commit.author, commit.author)
449 commit.branch = mapbranch(commit.branch, self.branchmap)
446 commit.branch = mapbranch(commit.branch, self.branchmap)
450 self.commitcache[rev] = commit
447 self.commitcache[rev] = commit
451 return commit
448 return commit
452
449
453 def copy(self, rev):
450 def copy(self, rev):
454 commit = self.commitcache[rev]
451 commit = self.commitcache[rev]
455 full = self.opts.get('full')
452 full = self.opts.get('full')
456 changes = self.source.getchanges(rev, full)
453 changes = self.source.getchanges(rev, full)
457 if isinstance(changes, bytes):
454 if isinstance(changes, bytes):
458 if changes == SKIPREV:
455 if changes == SKIPREV:
459 dest = SKIPREV
456 dest = SKIPREV
460 else:
457 else:
461 dest = self.map[changes]
458 dest = self.map[changes]
462 self.map[rev] = dest
459 self.map[rev] = dest
463 return
460 return
464 files, copies, cleanp2 = changes
461 files, copies, cleanp2 = changes
465 pbranches = []
462 pbranches = []
466 if commit.parents:
463 if commit.parents:
467 for prev in commit.parents:
464 for prev in commit.parents:
468 if prev not in self.commitcache:
465 if prev not in self.commitcache:
469 self.cachecommit(prev)
466 self.cachecommit(prev)
470 pbranches.append((self.map[prev],
467 pbranches.append((self.map[prev],
471 self.commitcache[prev].branch))
468 self.commitcache[prev].branch))
472 self.dest.setbranch(commit.branch, pbranches)
469 self.dest.setbranch(commit.branch, pbranches)
473 try:
470 try:
474 parents = self.splicemap[rev]
471 parents = self.splicemap[rev]
475 self.ui.status(_('spliced in %s as parents of %s\n') %
472 self.ui.status(_('spliced in %s as parents of %s\n') %
476 (_(' and ').join(parents), rev))
473 (_(' and ').join(parents), rev))
477 parents = [self.map.get(p, p) for p in parents]
474 parents = [self.map.get(p, p) for p in parents]
478 except KeyError:
475 except KeyError:
479 parents = [b[0] for b in pbranches]
476 parents = [b[0] for b in pbranches]
480 parents.extend(self.map[x]
477 parents.extend(self.map[x]
481 for x in commit.optparents
478 for x in commit.optparents
482 if x in self.map)
479 if x in self.map)
483 if len(pbranches) != 2:
480 if len(pbranches) != 2:
484 cleanp2 = set()
481 cleanp2 = set()
485 if len(parents) < 3:
482 if len(parents) < 3:
486 source = progresssource(self.ui, self.source, len(files))
483 source = progresssource(self.ui, self.source, len(files))
487 else:
484 else:
488 # For an octopus merge, we end up traversing the list of
485 # For an octopus merge, we end up traversing the list of
489 # changed files N-1 times. This tweak to the number of
486 # changed files N-1 times. This tweak to the number of
490 # files makes it so the progress bar doesn't overflow
487 # files makes it so the progress bar doesn't overflow
491 # itself.
488 # itself.
492 source = progresssource(self.ui, self.source,
489 source = progresssource(self.ui, self.source,
493 len(files) * (len(parents) - 1))
490 len(files) * (len(parents) - 1))
494 newnode = self.dest.putcommit(files, copies, parents, commit,
491 newnode = self.dest.putcommit(files, copies, parents, commit,
495 source, self.map, full, cleanp2)
492 source, self.map, full, cleanp2)
496 source.close()
493 source.close()
497 self.source.converted(rev, newnode)
494 self.source.converted(rev, newnode)
498 self.map[rev] = newnode
495 self.map[rev] = newnode
499
496
500 def convert(self, sortmode):
497 def convert(self, sortmode):
501 try:
498 try:
502 self.source.before()
499 self.source.before()
503 self.dest.before()
500 self.dest.before()
504 self.source.setrevmap(self.map)
501 self.source.setrevmap(self.map)
505 self.ui.status(_("scanning source...\n"))
502 self.ui.status(_("scanning source...\n"))
506 heads = self.source.getheads()
503 heads = self.source.getheads()
507 parents = self.walktree(heads)
504 parents = self.walktree(heads)
508 self.mergesplicemap(parents, self.splicemap)
505 self.mergesplicemap(parents, self.splicemap)
509 self.ui.status(_("sorting...\n"))
506 self.ui.status(_("sorting...\n"))
510 t = self.toposort(parents, sortmode)
507 t = self.toposort(parents, sortmode)
511 num = len(t)
508 num = len(t)
512 c = None
509 c = None
513
510
514 self.ui.status(_("converting...\n"))
511 self.ui.status(_("converting...\n"))
515 for i, c in enumerate(t):
512 for i, c in enumerate(t):
516 num -= 1
513 num -= 1
517 desc = self.commitcache[c].desc
514 desc = self.commitcache[c].desc
518 if "\n" in desc:
515 if "\n" in desc:
519 desc = desc.splitlines()[0]
516 desc = desc.splitlines()[0]
520 # convert log message to local encoding without using
517 # convert log message to local encoding without using
521 # tolocal() because the encoding.encoding convert()
518 # tolocal() because the encoding.encoding convert()
522 # uses is 'utf-8'
519 # uses is 'utf-8'
523 self.ui.status("%d %s\n" % (num, recode(desc)))
520 self.ui.status("%d %s\n" % (num, recode(desc)))
524 self.ui.note(_("source: %s\n") % recode(c))
521 self.ui.note(_("source: %s\n") % recode(c))
525 self.ui.progress(_('converting'), i, unit=_('revisions'),
522 self.ui.progress(_('converting'), i, unit=_('revisions'),
526 total=len(t))
523 total=len(t))
527 self.copy(c)
524 self.copy(c)
528 self.ui.progress(_('converting'), None)
525 self.ui.progress(_('converting'), None)
529
526
530 if not self.ui.configbool('convert', 'skiptags'):
527 if not self.ui.configbool('convert', 'skiptags'):
531 tags = self.source.gettags()
528 tags = self.source.gettags()
532 ctags = {}
529 ctags = {}
533 for k in tags:
530 for k in tags:
534 v = tags[k]
531 v = tags[k]
535 if self.map.get(v, SKIPREV) != SKIPREV:
532 if self.map.get(v, SKIPREV) != SKIPREV:
536 ctags[k] = self.map[v]
533 ctags[k] = self.map[v]
537
534
538 if c and ctags:
535 if c and ctags:
539 nrev, tagsparent = self.dest.puttags(ctags)
536 nrev, tagsparent = self.dest.puttags(ctags)
540 if nrev and tagsparent:
537 if nrev and tagsparent:
541 # write another hash correspondence to override the
538 # write another hash correspondence to override the
542 # previous one so we don't end up with extra tag heads
539 # previous one so we don't end up with extra tag heads
543 tagsparents = [e for e in self.map.iteritems()
540 tagsparents = [e for e in self.map.iteritems()
544 if e[1] == tagsparent]
541 if e[1] == tagsparent]
545 if tagsparents:
542 if tagsparents:
546 self.map[tagsparents[0][0]] = nrev
543 self.map[tagsparents[0][0]] = nrev
547
544
548 bookmarks = self.source.getbookmarks()
545 bookmarks = self.source.getbookmarks()
549 cbookmarks = {}
546 cbookmarks = {}
550 for k in bookmarks:
547 for k in bookmarks:
551 v = bookmarks[k]
548 v = bookmarks[k]
552 if self.map.get(v, SKIPREV) != SKIPREV:
549 if self.map.get(v, SKIPREV) != SKIPREV:
553 cbookmarks[k] = self.map[v]
550 cbookmarks[k] = self.map[v]
554
551
555 if c and cbookmarks:
552 if c and cbookmarks:
556 self.dest.putbookmarks(cbookmarks)
553 self.dest.putbookmarks(cbookmarks)
557
554
558 self.writeauthormap()
555 self.writeauthormap()
559 finally:
556 finally:
560 self.cleanup()
557 self.cleanup()
561
558
562 def cleanup(self):
559 def cleanup(self):
563 try:
560 try:
564 self.dest.after()
561 self.dest.after()
565 finally:
562 finally:
566 self.source.after()
563 self.source.after()
567 self.map.close()
564 self.map.close()
568
565
569 def convert(ui, src, dest=None, revmapfile=None, **opts):
566 def convert(ui, src, dest=None, revmapfile=None, **opts):
570 opts = pycompat.byteskwargs(opts)
567 opts = pycompat.byteskwargs(opts)
571 global orig_encoding
568 global orig_encoding
572 orig_encoding = encoding.encoding
569 orig_encoding = encoding.encoding
573 encoding.encoding = 'UTF-8'
570 encoding.encoding = 'UTF-8'
574
571
575 # support --authors as an alias for --authormap
572 # support --authors as an alias for --authormap
576 if not opts.get('authormap'):
573 if not opts.get('authormap'):
577 opts['authormap'] = opts.get('authors')
574 opts['authormap'] = opts.get('authors')
578
575
579 if not dest:
576 if not dest:
580 dest = hg.defaultdest(src) + "-hg"
577 dest = hg.defaultdest(src) + "-hg"
581 ui.status(_("assuming destination %s\n") % dest)
578 ui.status(_("assuming destination %s\n") % dest)
582
579
583 destc = convertsink(ui, dest, opts.get('dest_type'))
580 destc = convertsink(ui, dest, opts.get('dest_type'))
584 destc = scmutil.wrapconvertsink(destc)
581 destc = scmutil.wrapconvertsink(destc)
585
582
586 try:
583 try:
587 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
584 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
588 opts.get('rev'))
585 opts.get('rev'))
589 except Exception:
586 except Exception:
590 for path in destc.created:
587 for path in destc.created:
591 shutil.rmtree(path, True)
588 shutil.rmtree(path, True)
592 raise
589 raise
593
590
594 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
591 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
595 sortmode = [m for m in sortmodes if opts.get(m)]
592 sortmode = [m for m in sortmodes if opts.get(m)]
596 if len(sortmode) > 1:
593 if len(sortmode) > 1:
597 raise error.Abort(_('more than one sort mode specified'))
594 raise error.Abort(_('more than one sort mode specified'))
598 if sortmode:
595 if sortmode:
599 sortmode = sortmode[0]
596 sortmode = sortmode[0]
600 else:
597 else:
601 sortmode = defaultsort
598 sortmode = defaultsort
602
599
603 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
600 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
604 raise error.Abort(_('--sourcesort is not supported by this data source')
601 raise error.Abort(_('--sourcesort is not supported by this data source')
605 )
602 )
606 if sortmode == 'closesort' and not srcc.hasnativeclose():
603 if sortmode == 'closesort' and not srcc.hasnativeclose():
607 raise error.Abort(_('--closesort is not supported by this data source'))
604 raise error.Abort(_('--closesort is not supported by this data source'))
608
605
609 fmap = opts.get('filemap')
606 fmap = opts.get('filemap')
610 if fmap:
607 if fmap:
611 srcc = filemap.filemap_source(ui, srcc, fmap)
608 srcc = filemap.filemap_source(ui, srcc, fmap)
612 destc.setfilemapmode(True)
609 destc.setfilemapmode(True)
613
610
614 if not revmapfile:
611 if not revmapfile:
615 revmapfile = destc.revmapfile()
612 revmapfile = destc.revmapfile()
616
613
617 c = converter(ui, srcc, destc, revmapfile, opts)
614 c = converter(ui, srcc, destc, revmapfile, opts)
618 c.convert(sortmode)
615 c.convert(sortmode)
General Comments 0
You need to be logged in to leave comments. Login now