##// END OF EJS Templates
convert: use branchmap to change default branch in destination (issue3469)...
lstewart -
r20331:1d155582 stable
parent child Browse files
Show More
@@ -1,523 +1,528 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
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, shlex
20 import os, shutil, shlex
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]:
51 if type and type not in [s[0] for s in source_converters]:
52 raise util.Abort(_('%s: invalid source repository type') % type)
52 raise util.Abort(_('%s: invalid source repository type') % type)
53 for name, source, sortmode in source_converters:
53 for name, source, sortmode in source_converters:
54 try:
54 try:
55 if not type or name == type:
55 if not type or name == type:
56 return source(ui, path, rev), sortmode
56 return source(ui, path, rev), sortmode
57 except (NoRepo, MissingTool), inst:
57 except (NoRepo, MissingTool), inst:
58 exceptions.append(inst)
58 exceptions.append(inst)
59 if not ui.quiet:
59 if not ui.quiet:
60 for inst in exceptions:
60 for inst in exceptions:
61 ui.write("%s\n" % inst)
61 ui.write("%s\n" % inst)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
62 raise util.Abort(_('%s: missing or unsupported repository') % path)
63
63
64 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]:
65 if type and type not in [s[0] for s in sink_converters]:
66 raise util.Abort(_('%s: invalid destination repository type') % type)
66 raise util.Abort(_('%s: invalid destination repository type') % type)
67 for name, sink in sink_converters:
67 for name, sink in sink_converters:
68 try:
68 try:
69 if not type or name == type:
69 if not type or name == type:
70 return sink(ui, path)
70 return sink(ui, path)
71 except NoRepo, inst:
71 except NoRepo, inst:
72 ui.note(_("convert: %s\n") % inst)
72 ui.note(_("convert: %s\n") % inst)
73 except MissingTool, inst:
73 except MissingTool, inst:
74 raise util.Abort('%s\n' % inst)
74 raise util.Abort('%s\n' % inst)
75 raise util.Abort(_('%s: unknown repository type') % path)
75 raise util.Abort(_('%s: unknown repository type') % path)
76
76
77 class progresssource(object):
77 class progresssource(object):
78 def __init__(self, ui, source, filecount):
78 def __init__(self, ui, source, filecount):
79 self.ui = ui
79 self.ui = ui
80 self.source = source
80 self.source = source
81 self.filecount = filecount
81 self.filecount = filecount
82 self.retrieved = 0
82 self.retrieved = 0
83
83
84 def getfile(self, file, rev):
84 def getfile(self, file, rev):
85 self.retrieved += 1
85 self.retrieved += 1
86 self.ui.progress(_('getting files'), self.retrieved,
86 self.ui.progress(_('getting files'), self.retrieved,
87 item=file, total=self.filecount)
87 item=file, total=self.filecount)
88 return self.source.getfile(file, rev)
88 return self.source.getfile(file, rev)
89
89
90 def lookuprev(self, rev):
90 def lookuprev(self, rev):
91 return self.source.lookuprev(rev)
91 return self.source.lookuprev(rev)
92
92
93 def close(self):
93 def close(self):
94 self.ui.progress(_('getting files'), None)
94 self.ui.progress(_('getting files'), None)
95
95
96 class converter(object):
96 class converter(object):
97 def __init__(self, ui, source, dest, revmapfile, opts):
97 def __init__(self, ui, source, dest, revmapfile, opts):
98
98
99 self.source = source
99 self.source = source
100 self.dest = dest
100 self.dest = dest
101 self.ui = ui
101 self.ui = ui
102 self.opts = opts
102 self.opts = opts
103 self.commitcache = {}
103 self.commitcache = {}
104 self.authors = {}
104 self.authors = {}
105 self.authorfile = None
105 self.authorfile = None
106
106
107 # Record converted revisions persistently: maps source revision
107 # Record converted revisions persistently: maps source revision
108 # ID to target revision ID (both strings). (This is how
108 # ID to target revision ID (both strings). (This is how
109 # incremental conversions work.)
109 # incremental conversions work.)
110 self.map = mapfile(ui, revmapfile)
110 self.map = mapfile(ui, revmapfile)
111
111
112 # Read first the dst author map if any
112 # Read first the dst author map if any
113 authorfile = self.dest.authorfile()
113 authorfile = self.dest.authorfile()
114 if authorfile and os.path.exists(authorfile):
114 if authorfile and os.path.exists(authorfile):
115 self.readauthormap(authorfile)
115 self.readauthormap(authorfile)
116 # Extend/Override with new author map if necessary
116 # Extend/Override with new author map if necessary
117 if opts.get('authormap'):
117 if opts.get('authormap'):
118 self.readauthormap(opts.get('authormap'))
118 self.readauthormap(opts.get('authormap'))
119 self.authorfile = self.dest.authorfile()
119 self.authorfile = self.dest.authorfile()
120
120
121 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
121 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
122 self.branchmap = mapfile(ui, opts.get('branchmap'))
123
123
124 def parsesplicemap(self, path):
124 def parsesplicemap(self, path):
125 """ check and validate the splicemap format and
125 """ check and validate the splicemap format and
126 return a child/parents dictionary.
126 return a child/parents dictionary.
127 Format checking has two parts.
127 Format checking has two parts.
128 1. generic format which is same across all source types
128 1. generic format which is same across all source types
129 2. specific format checking which may be different for
129 2. specific format checking which may be different for
130 different source type. This logic is implemented in
130 different source type. This logic is implemented in
131 checkrevformat function in source files like
131 checkrevformat function in source files like
132 hg.py, subversion.py etc.
132 hg.py, subversion.py etc.
133 """
133 """
134
134
135 if not path:
135 if not path:
136 return {}
136 return {}
137 m = {}
137 m = {}
138 try:
138 try:
139 fp = open(path, 'r')
139 fp = open(path, 'r')
140 for i, line in enumerate(fp):
140 for i, line in enumerate(fp):
141 line = line.splitlines()[0].rstrip()
141 line = line.splitlines()[0].rstrip()
142 if not line:
142 if not line:
143 # Ignore blank lines
143 # Ignore blank lines
144 continue
144 continue
145 # split line
145 # split line
146 lex = shlex.shlex(line, posix=True)
146 lex = shlex.shlex(line, posix=True)
147 lex.whitespace_split = True
147 lex.whitespace_split = True
148 lex.whitespace += ','
148 lex.whitespace += ','
149 line = list(lex)
149 line = list(lex)
150 # check number of parents
150 # check number of parents
151 if not (2 <= len(line) <= 3):
151 if not (2 <= len(line) <= 3):
152 raise util.Abort(_('syntax error in %s(%d): child parent1'
152 raise util.Abort(_('syntax error in %s(%d): child parent1'
153 '[,parent2] expected') % (path, i + 1))
153 '[,parent2] expected') % (path, i + 1))
154 for part in line:
154 for part in line:
155 self.source.checkrevformat(part)
155 self.source.checkrevformat(part)
156 child, p1, p2 = line[0], line[1:2], line[2:]
156 child, p1, p2 = line[0], line[1:2], line[2:]
157 if p1 == p2:
157 if p1 == p2:
158 m[child] = p1
158 m[child] = p1
159 else:
159 else:
160 m[child] = p1 + p2
160 m[child] = p1 + p2
161 # if file does not exist or error reading, exit
161 # if file does not exist or error reading, exit
162 except IOError:
162 except IOError:
163 raise util.Abort(_('splicemap file not found or error reading %s:')
163 raise util.Abort(_('splicemap file not found or error reading %s:')
164 % path)
164 % path)
165 return m
165 return m
166
166
167
167
168 def walktree(self, heads):
168 def walktree(self, heads):
169 '''Return a mapping that identifies the uncommitted parents of every
169 '''Return a mapping that identifies the uncommitted parents of every
170 uncommitted changeset.'''
170 uncommitted changeset.'''
171 visit = heads
171 visit = heads
172 known = set()
172 known = set()
173 parents = {}
173 parents = {}
174 while visit:
174 while visit:
175 n = visit.pop(0)
175 n = visit.pop(0)
176 if n in known or n in self.map:
176 if n in known or n in self.map:
177 continue
177 continue
178 known.add(n)
178 known.add(n)
179 self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
179 self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
180 commit = self.cachecommit(n)
180 commit = self.cachecommit(n)
181 parents[n] = []
181 parents[n] = []
182 for p in commit.parents:
182 for p in commit.parents:
183 parents[n].append(p)
183 parents[n].append(p)
184 visit.append(p)
184 visit.append(p)
185 self.ui.progress(_('scanning'), None)
185 self.ui.progress(_('scanning'), None)
186
186
187 return parents
187 return parents
188
188
189 def mergesplicemap(self, parents, splicemap):
189 def mergesplicemap(self, parents, splicemap):
190 """A splicemap redefines child/parent relationships. Check the
190 """A splicemap redefines child/parent relationships. Check the
191 map contains valid revision identifiers and merge the new
191 map contains valid revision identifiers and merge the new
192 links in the source graph.
192 links in the source graph.
193 """
193 """
194 for c in sorted(splicemap):
194 for c in sorted(splicemap):
195 if c not in parents:
195 if c not in parents:
196 if not self.dest.hascommit(self.map.get(c, c)):
196 if not self.dest.hascommit(self.map.get(c, c)):
197 # Could be in source but not converted during this run
197 # Could be in source but not converted during this run
198 self.ui.warn(_('splice map revision %s is not being '
198 self.ui.warn(_('splice map revision %s is not being '
199 'converted, ignoring\n') % c)
199 'converted, ignoring\n') % c)
200 continue
200 continue
201 pc = []
201 pc = []
202 for p in splicemap[c]:
202 for p in splicemap[c]:
203 # We do not have to wait for nodes already in dest.
203 # We do not have to wait for nodes already in dest.
204 if self.dest.hascommit(self.map.get(p, p)):
204 if self.dest.hascommit(self.map.get(p, p)):
205 continue
205 continue
206 # Parent is not in dest and not being converted, not good
206 # Parent is not in dest and not being converted, not good
207 if p not in parents:
207 if p not in parents:
208 raise util.Abort(_('unknown splice map parent: %s') % p)
208 raise util.Abort(_('unknown splice map parent: %s') % p)
209 pc.append(p)
209 pc.append(p)
210 parents[c] = pc
210 parents[c] = pc
211
211
212 def toposort(self, parents, sortmode):
212 def toposort(self, parents, sortmode):
213 '''Return an ordering such that every uncommitted changeset is
213 '''Return an ordering such that every uncommitted changeset is
214 preceded by all its uncommitted ancestors.'''
214 preceded by all its uncommitted ancestors.'''
215
215
216 def mapchildren(parents):
216 def mapchildren(parents):
217 """Return a (children, roots) tuple where 'children' maps parent
217 """Return a (children, roots) tuple where 'children' maps parent
218 revision identifiers to children ones, and 'roots' is the list of
218 revision identifiers to children ones, and 'roots' is the list of
219 revisions without parents. 'parents' must be a mapping of revision
219 revisions without parents. 'parents' must be a mapping of revision
220 identifier to its parents ones.
220 identifier to its parents ones.
221 """
221 """
222 visit = sorted(parents)
222 visit = sorted(parents)
223 seen = set()
223 seen = set()
224 children = {}
224 children = {}
225 roots = []
225 roots = []
226
226
227 while visit:
227 while visit:
228 n = visit.pop(0)
228 n = visit.pop(0)
229 if n in seen:
229 if n in seen:
230 continue
230 continue
231 seen.add(n)
231 seen.add(n)
232 # Ensure that nodes without parents are present in the
232 # Ensure that nodes without parents are present in the
233 # 'children' mapping.
233 # 'children' mapping.
234 children.setdefault(n, [])
234 children.setdefault(n, [])
235 hasparent = False
235 hasparent = False
236 for p in parents[n]:
236 for p in parents[n]:
237 if p not in self.map:
237 if p not in self.map:
238 visit.append(p)
238 visit.append(p)
239 hasparent = True
239 hasparent = True
240 children.setdefault(p, []).append(n)
240 children.setdefault(p, []).append(n)
241 if not hasparent:
241 if not hasparent:
242 roots.append(n)
242 roots.append(n)
243
243
244 return children, roots
244 return children, roots
245
245
246 # Sort functions are supposed to take a list of revisions which
246 # Sort functions are supposed to take a list of revisions which
247 # can be converted immediately and pick one
247 # can be converted immediately and pick one
248
248
249 def makebranchsorter():
249 def makebranchsorter():
250 """If the previously converted revision has a child in the
250 """If the previously converted revision has a child in the
251 eligible revisions list, pick it. Return the list head
251 eligible revisions list, pick it. Return the list head
252 otherwise. Branch sort attempts to minimize branch
252 otherwise. Branch sort attempts to minimize branch
253 switching, which is harmful for Mercurial backend
253 switching, which is harmful for Mercurial backend
254 compression.
254 compression.
255 """
255 """
256 prev = [None]
256 prev = [None]
257 def picknext(nodes):
257 def picknext(nodes):
258 next = nodes[0]
258 next = nodes[0]
259 for n in nodes:
259 for n in nodes:
260 if prev[0] in parents[n]:
260 if prev[0] in parents[n]:
261 next = n
261 next = n
262 break
262 break
263 prev[0] = next
263 prev[0] = next
264 return next
264 return next
265 return picknext
265 return picknext
266
266
267 def makesourcesorter():
267 def makesourcesorter():
268 """Source specific sort."""
268 """Source specific sort."""
269 keyfn = lambda n: self.commitcache[n].sortkey
269 keyfn = lambda n: self.commitcache[n].sortkey
270 def picknext(nodes):
270 def picknext(nodes):
271 return sorted(nodes, key=keyfn)[0]
271 return sorted(nodes, key=keyfn)[0]
272 return picknext
272 return picknext
273
273
274 def makeclosesorter():
274 def makeclosesorter():
275 """Close order sort."""
275 """Close order sort."""
276 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
276 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
277 self.commitcache[n].sortkey)
277 self.commitcache[n].sortkey)
278 def picknext(nodes):
278 def picknext(nodes):
279 return sorted(nodes, key=keyfn)[0]
279 return sorted(nodes, key=keyfn)[0]
280 return picknext
280 return picknext
281
281
282 def makedatesorter():
282 def makedatesorter():
283 """Sort revisions by date."""
283 """Sort revisions by date."""
284 dates = {}
284 dates = {}
285 def getdate(n):
285 def getdate(n):
286 if n not in dates:
286 if n not in dates:
287 dates[n] = util.parsedate(self.commitcache[n].date)
287 dates[n] = util.parsedate(self.commitcache[n].date)
288 return dates[n]
288 return dates[n]
289
289
290 def picknext(nodes):
290 def picknext(nodes):
291 return min([(getdate(n), n) for n in nodes])[1]
291 return min([(getdate(n), n) for n in nodes])[1]
292
292
293 return picknext
293 return picknext
294
294
295 if sortmode == 'branchsort':
295 if sortmode == 'branchsort':
296 picknext = makebranchsorter()
296 picknext = makebranchsorter()
297 elif sortmode == 'datesort':
297 elif sortmode == 'datesort':
298 picknext = makedatesorter()
298 picknext = makedatesorter()
299 elif sortmode == 'sourcesort':
299 elif sortmode == 'sourcesort':
300 picknext = makesourcesorter()
300 picknext = makesourcesorter()
301 elif sortmode == 'closesort':
301 elif sortmode == 'closesort':
302 picknext = makeclosesorter()
302 picknext = makeclosesorter()
303 else:
303 else:
304 raise util.Abort(_('unknown sort mode: %s') % sortmode)
304 raise util.Abort(_('unknown sort mode: %s') % sortmode)
305
305
306 children, actives = mapchildren(parents)
306 children, actives = mapchildren(parents)
307
307
308 s = []
308 s = []
309 pendings = {}
309 pendings = {}
310 while actives:
310 while actives:
311 n = picknext(actives)
311 n = picknext(actives)
312 actives.remove(n)
312 actives.remove(n)
313 s.append(n)
313 s.append(n)
314
314
315 # Update dependents list
315 # Update dependents list
316 for c in children.get(n, []):
316 for c in children.get(n, []):
317 if c not in pendings:
317 if c not in pendings:
318 pendings[c] = [p for p in parents[c] if p not in self.map]
318 pendings[c] = [p for p in parents[c] if p not in self.map]
319 try:
319 try:
320 pendings[c].remove(n)
320 pendings[c].remove(n)
321 except ValueError:
321 except ValueError:
322 raise util.Abort(_('cycle detected between %s and %s')
322 raise util.Abort(_('cycle detected between %s and %s')
323 % (recode(c), recode(n)))
323 % (recode(c), recode(n)))
324 if not pendings[c]:
324 if not pendings[c]:
325 # Parents are converted, node is eligible
325 # Parents are converted, node is eligible
326 actives.insert(0, c)
326 actives.insert(0, c)
327 pendings[c] = None
327 pendings[c] = None
328
328
329 if len(s) != len(parents):
329 if len(s) != len(parents):
330 raise util.Abort(_("not all revisions were sorted"))
330 raise util.Abort(_("not all revisions were sorted"))
331
331
332 return s
332 return s
333
333
334 def writeauthormap(self):
334 def writeauthormap(self):
335 authorfile = self.authorfile
335 authorfile = self.authorfile
336 if authorfile:
336 if authorfile:
337 self.ui.status(_('writing author map file %s\n') % authorfile)
337 self.ui.status(_('writing author map file %s\n') % authorfile)
338 ofile = open(authorfile, 'w+')
338 ofile = open(authorfile, 'w+')
339 for author in self.authors:
339 for author in self.authors:
340 ofile.write("%s=%s\n" % (author, self.authors[author]))
340 ofile.write("%s=%s\n" % (author, self.authors[author]))
341 ofile.close()
341 ofile.close()
342
342
343 def readauthormap(self, authorfile):
343 def readauthormap(self, authorfile):
344 afile = open(authorfile, 'r')
344 afile = open(authorfile, 'r')
345 for line in afile:
345 for line in afile:
346
346
347 line = line.strip()
347 line = line.strip()
348 if not line or line.startswith('#'):
348 if not line or line.startswith('#'):
349 continue
349 continue
350
350
351 try:
351 try:
352 srcauthor, dstauthor = line.split('=', 1)
352 srcauthor, dstauthor = line.split('=', 1)
353 except ValueError:
353 except ValueError:
354 msg = _('ignoring bad line in author map file %s: %s\n')
354 msg = _('ignoring bad line in author map file %s: %s\n')
355 self.ui.warn(msg % (authorfile, line.rstrip()))
355 self.ui.warn(msg % (authorfile, line.rstrip()))
356 continue
356 continue
357
357
358 srcauthor = srcauthor.strip()
358 srcauthor = srcauthor.strip()
359 dstauthor = dstauthor.strip()
359 dstauthor = dstauthor.strip()
360 if self.authors.get(srcauthor) in (None, dstauthor):
360 if self.authors.get(srcauthor) in (None, dstauthor):
361 msg = _('mapping author %s to %s\n')
361 msg = _('mapping author %s to %s\n')
362 self.ui.debug(msg % (srcauthor, dstauthor))
362 self.ui.debug(msg % (srcauthor, dstauthor))
363 self.authors[srcauthor] = dstauthor
363 self.authors[srcauthor] = dstauthor
364 continue
364 continue
365
365
366 m = _('overriding mapping for author %s, was %s, will be %s\n')
366 m = _('overriding mapping for author %s, was %s, will be %s\n')
367 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
367 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
368
368
369 afile.close()
369 afile.close()
370
370
371 def cachecommit(self, rev):
371 def cachecommit(self, rev):
372 commit = self.source.getcommit(rev)
372 commit = self.source.getcommit(rev)
373 commit.author = self.authors.get(commit.author, commit.author)
373 commit.author = self.authors.get(commit.author, commit.author)
374 commit.branch = self.branchmap.get(commit.branch, commit.branch)
374 # If commit.branch is None, this commit is coming from the source
375 # repository's default branch and destined for the default branch in the
376 # destination repository. For such commits, passing a literal "None"
377 # string to branchmap.get() below allows the user to map "None" to an
378 # alternate default branch in the destination repository.
379 commit.branch = self.branchmap.get(str(commit.branch), commit.branch)
375 self.commitcache[rev] = commit
380 self.commitcache[rev] = commit
376 return commit
381 return commit
377
382
378 def copy(self, rev):
383 def copy(self, rev):
379 commit = self.commitcache[rev]
384 commit = self.commitcache[rev]
380
385
381 changes = self.source.getchanges(rev)
386 changes = self.source.getchanges(rev)
382 if isinstance(changes, basestring):
387 if isinstance(changes, basestring):
383 if changes == SKIPREV:
388 if changes == SKIPREV:
384 dest = SKIPREV
389 dest = SKIPREV
385 else:
390 else:
386 dest = self.map[changes]
391 dest = self.map[changes]
387 self.map[rev] = dest
392 self.map[rev] = dest
388 return
393 return
389 files, copies = changes
394 files, copies = changes
390 pbranches = []
395 pbranches = []
391 if commit.parents:
396 if commit.parents:
392 for prev in commit.parents:
397 for prev in commit.parents:
393 if prev not in self.commitcache:
398 if prev not in self.commitcache:
394 self.cachecommit(prev)
399 self.cachecommit(prev)
395 pbranches.append((self.map[prev],
400 pbranches.append((self.map[prev],
396 self.commitcache[prev].branch))
401 self.commitcache[prev].branch))
397 self.dest.setbranch(commit.branch, pbranches)
402 self.dest.setbranch(commit.branch, pbranches)
398 try:
403 try:
399 parents = self.splicemap[rev]
404 parents = self.splicemap[rev]
400 self.ui.status(_('spliced in %s as parents of %s\n') %
405 self.ui.status(_('spliced in %s as parents of %s\n') %
401 (parents, rev))
406 (parents, rev))
402 parents = [self.map.get(p, p) for p in parents]
407 parents = [self.map.get(p, p) for p in parents]
403 except KeyError:
408 except KeyError:
404 parents = [b[0] for b in pbranches]
409 parents = [b[0] for b in pbranches]
405 source = progresssource(self.ui, self.source, len(files))
410 source = progresssource(self.ui, self.source, len(files))
406 newnode = self.dest.putcommit(files, copies, parents, commit,
411 newnode = self.dest.putcommit(files, copies, parents, commit,
407 source, self.map)
412 source, self.map)
408 source.close()
413 source.close()
409 self.source.converted(rev, newnode)
414 self.source.converted(rev, newnode)
410 self.map[rev] = newnode
415 self.map[rev] = newnode
411
416
412 def convert(self, sortmode):
417 def convert(self, sortmode):
413 try:
418 try:
414 self.source.before()
419 self.source.before()
415 self.dest.before()
420 self.dest.before()
416 self.source.setrevmap(self.map)
421 self.source.setrevmap(self.map)
417 self.ui.status(_("scanning source...\n"))
422 self.ui.status(_("scanning source...\n"))
418 heads = self.source.getheads()
423 heads = self.source.getheads()
419 parents = self.walktree(heads)
424 parents = self.walktree(heads)
420 self.mergesplicemap(parents, self.splicemap)
425 self.mergesplicemap(parents, self.splicemap)
421 self.ui.status(_("sorting...\n"))
426 self.ui.status(_("sorting...\n"))
422 t = self.toposort(parents, sortmode)
427 t = self.toposort(parents, sortmode)
423 num = len(t)
428 num = len(t)
424 c = None
429 c = None
425
430
426 self.ui.status(_("converting...\n"))
431 self.ui.status(_("converting...\n"))
427 for i, c in enumerate(t):
432 for i, c in enumerate(t):
428 num -= 1
433 num -= 1
429 desc = self.commitcache[c].desc
434 desc = self.commitcache[c].desc
430 if "\n" in desc:
435 if "\n" in desc:
431 desc = desc.splitlines()[0]
436 desc = desc.splitlines()[0]
432 # convert log message to local encoding without using
437 # convert log message to local encoding without using
433 # tolocal() because the encoding.encoding convert()
438 # tolocal() because the encoding.encoding convert()
434 # uses is 'utf-8'
439 # uses is 'utf-8'
435 self.ui.status("%d %s\n" % (num, recode(desc)))
440 self.ui.status("%d %s\n" % (num, recode(desc)))
436 self.ui.note(_("source: %s\n") % recode(c))
441 self.ui.note(_("source: %s\n") % recode(c))
437 self.ui.progress(_('converting'), i, unit=_('revisions'),
442 self.ui.progress(_('converting'), i, unit=_('revisions'),
438 total=len(t))
443 total=len(t))
439 self.copy(c)
444 self.copy(c)
440 self.ui.progress(_('converting'), None)
445 self.ui.progress(_('converting'), None)
441
446
442 tags = self.source.gettags()
447 tags = self.source.gettags()
443 ctags = {}
448 ctags = {}
444 for k in tags:
449 for k in tags:
445 v = tags[k]
450 v = tags[k]
446 if self.map.get(v, SKIPREV) != SKIPREV:
451 if self.map.get(v, SKIPREV) != SKIPREV:
447 ctags[k] = self.map[v]
452 ctags[k] = self.map[v]
448
453
449 if c and ctags:
454 if c and ctags:
450 nrev, tagsparent = self.dest.puttags(ctags)
455 nrev, tagsparent = self.dest.puttags(ctags)
451 if nrev and tagsparent:
456 if nrev and tagsparent:
452 # write another hash correspondence to override the previous
457 # write another hash correspondence to override the previous
453 # one so we don't end up with extra tag heads
458 # one so we don't end up with extra tag heads
454 tagsparents = [e for e in self.map.iteritems()
459 tagsparents = [e for e in self.map.iteritems()
455 if e[1] == tagsparent]
460 if e[1] == tagsparent]
456 if tagsparents:
461 if tagsparents:
457 self.map[tagsparents[0][0]] = nrev
462 self.map[tagsparents[0][0]] = nrev
458
463
459 bookmarks = self.source.getbookmarks()
464 bookmarks = self.source.getbookmarks()
460 cbookmarks = {}
465 cbookmarks = {}
461 for k in bookmarks:
466 for k in bookmarks:
462 v = bookmarks[k]
467 v = bookmarks[k]
463 if self.map.get(v, SKIPREV) != SKIPREV:
468 if self.map.get(v, SKIPREV) != SKIPREV:
464 cbookmarks[k] = self.map[v]
469 cbookmarks[k] = self.map[v]
465
470
466 if c and cbookmarks:
471 if c and cbookmarks:
467 self.dest.putbookmarks(cbookmarks)
472 self.dest.putbookmarks(cbookmarks)
468
473
469 self.writeauthormap()
474 self.writeauthormap()
470 finally:
475 finally:
471 self.cleanup()
476 self.cleanup()
472
477
473 def cleanup(self):
478 def cleanup(self):
474 try:
479 try:
475 self.dest.after()
480 self.dest.after()
476 finally:
481 finally:
477 self.source.after()
482 self.source.after()
478 self.map.close()
483 self.map.close()
479
484
480 def convert(ui, src, dest=None, revmapfile=None, **opts):
485 def convert(ui, src, dest=None, revmapfile=None, **opts):
481 global orig_encoding
486 global orig_encoding
482 orig_encoding = encoding.encoding
487 orig_encoding = encoding.encoding
483 encoding.encoding = 'UTF-8'
488 encoding.encoding = 'UTF-8'
484
489
485 # support --authors as an alias for --authormap
490 # support --authors as an alias for --authormap
486 if not opts.get('authormap'):
491 if not opts.get('authormap'):
487 opts['authormap'] = opts.get('authors')
492 opts['authormap'] = opts.get('authors')
488
493
489 if not dest:
494 if not dest:
490 dest = hg.defaultdest(src) + "-hg"
495 dest = hg.defaultdest(src) + "-hg"
491 ui.status(_("assuming destination %s\n") % dest)
496 ui.status(_("assuming destination %s\n") % dest)
492
497
493 destc = convertsink(ui, dest, opts.get('dest_type'))
498 destc = convertsink(ui, dest, opts.get('dest_type'))
494
499
495 try:
500 try:
496 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
501 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
497 opts.get('rev'))
502 opts.get('rev'))
498 except Exception:
503 except Exception:
499 for path in destc.created:
504 for path in destc.created:
500 shutil.rmtree(path, True)
505 shutil.rmtree(path, True)
501 raise
506 raise
502
507
503 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
508 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
504 sortmode = [m for m in sortmodes if opts.get(m)]
509 sortmode = [m for m in sortmodes if opts.get(m)]
505 if len(sortmode) > 1:
510 if len(sortmode) > 1:
506 raise util.Abort(_('more than one sort mode specified'))
511 raise util.Abort(_('more than one sort mode specified'))
507 sortmode = sortmode and sortmode[0] or defaultsort
512 sortmode = sortmode and sortmode[0] or defaultsort
508 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
513 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
509 raise util.Abort(_('--sourcesort is not supported by this data source'))
514 raise util.Abort(_('--sourcesort is not supported by this data source'))
510 if sortmode == 'closesort' and not srcc.hasnativeclose():
515 if sortmode == 'closesort' and not srcc.hasnativeclose():
511 raise util.Abort(_('--closesort is not supported by this data source'))
516 raise util.Abort(_('--closesort is not supported by this data source'))
512
517
513 fmap = opts.get('filemap')
518 fmap = opts.get('filemap')
514 if fmap:
519 if fmap:
515 srcc = filemap.filemap_source(ui, srcc, fmap)
520 srcc = filemap.filemap_source(ui, srcc, fmap)
516 destc.setfilemapmode(True)
521 destc.setfilemapmode(True)
517
522
518 if not revmapfile:
523 if not revmapfile:
519 revmapfile = destc.revmapfile()
524 revmapfile = destc.revmapfile()
520
525
521 c = converter(ui, srcc, destc, revmapfile, opts)
526 c = converter(ui, srcc, destc, revmapfile, opts)
522 c.convert(sortmode)
527 c.convert(sortmode)
523
528
@@ -1,98 +1,130 b''
1
1
2 $ "$TESTDIR/hghave" svn svn-bindings || exit 80
2 $ "$TESTDIR/hghave" svn svn-bindings || exit 80
3
3
4 $ cat >> $HGRCPATH <<EOF
4 $ cat >> $HGRCPATH <<EOF
5 > [extensions]
5 > [extensions]
6 > convert =
6 > convert =
7 > EOF
7 > EOF
8
8
9 $ svnadmin create svn-repo
9 $ svnadmin create svn-repo
10 $ svnadmin load -q svn-repo < "$TESTDIR/svn/branches.svndump"
10 $ svnadmin load -q svn-repo < "$TESTDIR/svn/branches.svndump"
11
11
12 Convert trunk and branches
12 Convert trunk and branches
13
13
14 $ cat > branchmap <<EOF
14 $ cat > branchmap <<EOF
15 > old3 newbranch
15 > old3 newbranch
16 >
16 >
17 >
17 >
18 > EOF
18 > EOF
19 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo A-hg
19 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo A-hg
20 initializing destination A-hg repository
20 initializing destination A-hg repository
21 scanning source...
21 scanning source...
22 sorting...
22 sorting...
23 converting...
23 converting...
24 10 init projA
24 10 init projA
25 9 hello
25 9 hello
26 8 branch trunk, remove c and dir
26 8 branch trunk, remove c and dir
27 7 change a
27 7 change a
28 6 change b
28 6 change b
29 5 move and update c
29 5 move and update c
30 4 move and update c
30 4 move and update c
31 3 change b again
31 3 change b again
32 2 move to old2
32 2 move to old2
33 1 move back to old
33 1 move back to old
34 0 last change to a
34 0 last change to a
35
35
36 Test template keywords
36 Test template keywords
37
37
38 $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n'
38 $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n'
39 10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10
39 10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10
40 9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9
40 9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9
41 8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8
41 8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8
42 7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7
42 7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7
43 6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6
43 6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6
44 5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6
44 5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6
45 4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5
45 4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5
46 3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4
46 3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4
47 2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3
47 2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3
48 1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2
48 1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2
49 0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1
49 0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1
50
50
51 Convert again
51 Convert again
52
52
53 $ hg convert --branchmap=branchmap --datesort svn-repo A-hg
53 $ hg convert --branchmap=branchmap --datesort svn-repo A-hg
54 scanning source...
54 scanning source...
55 sorting...
55 sorting...
56 converting...
56 converting...
57 0 branch trunk@1 into old3
57 0 branch trunk@1 into old3
58
58
59 $ cd A-hg
59 $ cd A-hg
60 $ hg log -G --template 'branch={branches} {rev} {desc|firstline} files: {files}\n'
60 $ hg log -G --template 'branch={branches} {rev} {desc|firstline} files: {files}\n'
61 o branch=newbranch 11 branch trunk@1 into old3 files:
61 o branch=newbranch 11 branch trunk@1 into old3 files:
62 |
62 |
63 | o branch= 10 last change to a files: a
63 | o branch= 10 last change to a files: a
64 | |
64 | |
65 | | o branch=old 9 move back to old files:
65 | | o branch=old 9 move back to old files:
66 | | |
66 | | |
67 | | o branch=old2 8 move to old2 files:
67 | | o branch=old2 8 move to old2 files:
68 | | |
68 | | |
69 | | o branch=old 7 change b again files: b
69 | | o branch=old 7 change b again files: b
70 | | |
70 | | |
71 | o | branch= 6 move and update c files: b
71 | o | branch= 6 move and update c files: b
72 | | |
72 | | |
73 | | o branch=old 5 move and update c files: c
73 | | o branch=old 5 move and update c files: c
74 | | |
74 | | |
75 | | o branch=old 4 change b files: b
75 | | o branch=old 4 change b files: b
76 | | |
76 | | |
77 | o | branch= 3 change a files: a
77 | o | branch= 3 change a files: a
78 | | |
78 | | |
79 | | o branch=old 2 branch trunk, remove c and dir files: c
79 | | o branch=old 2 branch trunk, remove c and dir files: c
80 | |/
80 | |/
81 | o branch= 1 hello files: a b c dir/e
81 | o branch= 1 hello files: a b c dir/e
82 |/
82 |/
83 o branch= 0 init projA files:
83 o branch= 0 init projA files:
84
84
85
85
86 $ hg branches
86 $ hg branches
87 newbranch 11:a6d7cc050ad1
87 newbranch 11:a6d7cc050ad1
88 default 10:6e2b33404495
88 default 10:6e2b33404495
89 old 9:93c4b0f99529
89 old 9:93c4b0f99529
90 old2 8:b52884d7bead (inactive)
90 old2 8:b52884d7bead (inactive)
91 $ hg tags -q
91 $ hg tags -q
92 tip
92 tip
93 $ cd ..
93 $ cd ..
94
94
95 Test hg failing to call itself
95 Test hg failing to call itself
96
96
97 $ HG=foobar hg convert svn-repo B-hg 2>&1 | grep abort
97 $ HG=foobar hg convert svn-repo B-hg 2>&1 | grep abort
98 abort: Mercurial failed to run itself, check hg executable is in PATH
98 abort: Mercurial failed to run itself, check hg executable is in PATH
99
100 Convert 'trunk' to branch other than 'default'
101
102 $ cat > branchmap <<EOF
103 > None hgtrunk
104 >
105 >
106 > EOF
107 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo C-hg
108 initializing destination C-hg repository
109 scanning source...
110 sorting...
111 converting...
112 10 init projA
113 9 hello
114 8 branch trunk, remove c and dir
115 7 change a
116 6 change b
117 5 move and update c
118 4 move and update c
119 3 change b again
120 2 move to old2
121 1 move back to old
122 0 last change to a
123
124 $ cd C-hg
125 $ hg branches
126 hgtrunk 10:745f063703b4
127 old 9:aa50d7b8d922
128 old2 8:c85a22267b6e (inactive)
129 $ cd ..
130
General Comments 0
You need to be logged in to leave comments. Login now