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