##// END OF EJS Templates
convert: add bookmark support to main command...
Edouard Gomez -
r13745:9ff22f60 default
parent child Browse files
Show More
@@ -1,436 +1,446 b''
1 1 # convcmd - convert extension commands definition
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from common import NoRepo, MissingTool, SKIPREV, mapfile
9 9 from cvs import convert_cvs
10 10 from darcs import darcs_source
11 11 from git import convert_git
12 12 from hg import mercurial_source, mercurial_sink
13 13 from subversion import svn_source, svn_sink
14 14 from monotone import monotone_source
15 15 from gnuarch import gnuarch_source
16 16 from bzr import bzr_source
17 17 from p4 import p4_source
18 18 import filemap
19 19
20 20 import os, shutil
21 21 from mercurial import hg, util, encoding
22 22 from mercurial.i18n import _
23 23
24 24 orig_encoding = 'ascii'
25 25
26 26 def recode(s):
27 27 if isinstance(s, unicode):
28 28 return s.encode(orig_encoding, 'replace')
29 29 else:
30 30 return s.decode('utf-8').encode(orig_encoding, 'replace')
31 31
32 32 source_converters = [
33 33 ('cvs', convert_cvs, 'branchsort'),
34 34 ('git', convert_git, 'branchsort'),
35 35 ('svn', svn_source, 'branchsort'),
36 36 ('hg', mercurial_source, 'sourcesort'),
37 37 ('darcs', darcs_source, 'branchsort'),
38 38 ('mtn', monotone_source, 'branchsort'),
39 39 ('gnuarch', gnuarch_source, 'branchsort'),
40 40 ('bzr', bzr_source, 'branchsort'),
41 41 ('p4', p4_source, 'branchsort'),
42 42 ]
43 43
44 44 sink_converters = [
45 45 ('hg', mercurial_sink),
46 46 ('svn', svn_sink),
47 47 ]
48 48
49 49 def convertsource(ui, path, type, rev):
50 50 exceptions = []
51 51 if type and type not in [s[0] for s in source_converters]:
52 52 raise util.Abort(_('%s: invalid source repository type') % type)
53 53 for name, source, sortmode in source_converters:
54 54 try:
55 55 if not type or name == type:
56 56 return source(ui, path, rev), sortmode
57 57 except (NoRepo, MissingTool), inst:
58 58 exceptions.append(inst)
59 59 if not ui.quiet:
60 60 for inst in exceptions:
61 61 ui.write("%s\n" % inst)
62 62 raise util.Abort(_('%s: missing or unsupported repository') % path)
63 63
64 64 def convertsink(ui, path, type):
65 65 if type and type not in [s[0] for s in sink_converters]:
66 66 raise util.Abort(_('%s: invalid destination repository type') % type)
67 67 for name, sink in sink_converters:
68 68 try:
69 69 if not type or name == type:
70 70 return sink(ui, path)
71 71 except NoRepo, inst:
72 72 ui.note(_("convert: %s\n") % inst)
73 73 except MissingTool, inst:
74 74 raise util.Abort('%s\n' % inst)
75 75 raise util.Abort(_('%s: unknown repository type') % path)
76 76
77 77 class progresssource(object):
78 78 def __init__(self, ui, source, filecount):
79 79 self.ui = ui
80 80 self.source = source
81 81 self.filecount = filecount
82 82 self.retrieved = 0
83 83
84 84 def getfile(self, file, rev):
85 85 self.retrieved += 1
86 86 self.ui.progress(_('getting files'), self.retrieved,
87 87 item=file, total=self.filecount)
88 88 return self.source.getfile(file, rev)
89 89
90 90 def lookuprev(self, rev):
91 91 return self.source.lookuprev(rev)
92 92
93 93 def close(self):
94 94 self.ui.progress(_('getting files'), None)
95 95
96 96 class converter(object):
97 97 def __init__(self, ui, source, dest, revmapfile, opts):
98 98
99 99 self.source = source
100 100 self.dest = dest
101 101 self.ui = ui
102 102 self.opts = opts
103 103 self.commitcache = {}
104 104 self.authors = {}
105 105 self.authorfile = None
106 106
107 107 # Record converted revisions persistently: maps source revision
108 108 # ID to target revision ID (both strings). (This is how
109 109 # incremental conversions work.)
110 110 self.map = mapfile(ui, revmapfile)
111 111
112 112 # Read first the dst author map if any
113 113 authorfile = self.dest.authorfile()
114 114 if authorfile and os.path.exists(authorfile):
115 115 self.readauthormap(authorfile)
116 116 # Extend/Override with new author map if necessary
117 117 if opts.get('authormap'):
118 118 self.readauthormap(opts.get('authormap'))
119 119 self.authorfile = self.dest.authorfile()
120 120
121 121 self.splicemap = mapfile(ui, opts.get('splicemap'))
122 122 self.branchmap = mapfile(ui, opts.get('branchmap'))
123 123
124 124 def walktree(self, heads):
125 125 '''Return a mapping that identifies the uncommitted parents of every
126 126 uncommitted changeset.'''
127 127 visit = heads
128 128 known = set()
129 129 parents = {}
130 130 while visit:
131 131 n = visit.pop(0)
132 132 if n in known or n in self.map:
133 133 continue
134 134 known.add(n)
135 135 self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
136 136 commit = self.cachecommit(n)
137 137 parents[n] = []
138 138 for p in commit.parents:
139 139 parents[n].append(p)
140 140 visit.append(p)
141 141 self.ui.progress(_('scanning'), None)
142 142
143 143 return parents
144 144
145 145 def toposort(self, parents, sortmode):
146 146 '''Return an ordering such that every uncommitted changeset is
147 147 preceeded by all its uncommitted ancestors.'''
148 148
149 149 def mapchildren(parents):
150 150 """Return a (children, roots) tuple where 'children' maps parent
151 151 revision identifiers to children ones, and 'roots' is the list of
152 152 revisions without parents. 'parents' must be a mapping of revision
153 153 identifier to its parents ones.
154 154 """
155 155 visit = parents.keys()
156 156 seen = set()
157 157 children = {}
158 158 roots = []
159 159
160 160 while visit:
161 161 n = visit.pop(0)
162 162 if n in seen:
163 163 continue
164 164 seen.add(n)
165 165 # Ensure that nodes without parents are present in the
166 166 # 'children' mapping.
167 167 children.setdefault(n, [])
168 168 hasparent = False
169 169 for p in parents[n]:
170 170 if not p in self.map:
171 171 visit.append(p)
172 172 hasparent = True
173 173 children.setdefault(p, []).append(n)
174 174 if not hasparent:
175 175 roots.append(n)
176 176
177 177 return children, roots
178 178
179 179 # Sort functions are supposed to take a list of revisions which
180 180 # can be converted immediately and pick one
181 181
182 182 def makebranchsorter():
183 183 """If the previously converted revision has a child in the
184 184 eligible revisions list, pick it. Return the list head
185 185 otherwise. Branch sort attempts to minimize branch
186 186 switching, which is harmful for Mercurial backend
187 187 compression.
188 188 """
189 189 prev = [None]
190 190 def picknext(nodes):
191 191 next = nodes[0]
192 192 for n in nodes:
193 193 if prev[0] in parents[n]:
194 194 next = n
195 195 break
196 196 prev[0] = next
197 197 return next
198 198 return picknext
199 199
200 200 def makesourcesorter():
201 201 """Source specific sort."""
202 202 keyfn = lambda n: self.commitcache[n].sortkey
203 203 def picknext(nodes):
204 204 return sorted(nodes, key=keyfn)[0]
205 205 return picknext
206 206
207 207 def makedatesorter():
208 208 """Sort revisions by date."""
209 209 dates = {}
210 210 def getdate(n):
211 211 if n not in dates:
212 212 dates[n] = util.parsedate(self.commitcache[n].date)
213 213 return dates[n]
214 214
215 215 def picknext(nodes):
216 216 return min([(getdate(n), n) for n in nodes])[1]
217 217
218 218 return picknext
219 219
220 220 if sortmode == 'branchsort':
221 221 picknext = makebranchsorter()
222 222 elif sortmode == 'datesort':
223 223 picknext = makedatesorter()
224 224 elif sortmode == 'sourcesort':
225 225 picknext = makesourcesorter()
226 226 else:
227 227 raise util.Abort(_('unknown sort mode: %s') % sortmode)
228 228
229 229 children, actives = mapchildren(parents)
230 230
231 231 s = []
232 232 pendings = {}
233 233 while actives:
234 234 n = picknext(actives)
235 235 actives.remove(n)
236 236 s.append(n)
237 237
238 238 # Update dependents list
239 239 for c in children.get(n, []):
240 240 if c not in pendings:
241 241 pendings[c] = [p for p in parents[c] if p not in self.map]
242 242 try:
243 243 pendings[c].remove(n)
244 244 except ValueError:
245 245 raise util.Abort(_('cycle detected between %s and %s')
246 246 % (recode(c), recode(n)))
247 247 if not pendings[c]:
248 248 # Parents are converted, node is eligible
249 249 actives.insert(0, c)
250 250 pendings[c] = None
251 251
252 252 if len(s) != len(parents):
253 253 raise util.Abort(_("not all revisions were sorted"))
254 254
255 255 return s
256 256
257 257 def writeauthormap(self):
258 258 authorfile = self.authorfile
259 259 if authorfile:
260 260 self.ui.status(_('Writing author map file %s\n') % authorfile)
261 261 ofile = open(authorfile, 'w+')
262 262 for author in self.authors:
263 263 ofile.write("%s=%s\n" % (author, self.authors[author]))
264 264 ofile.close()
265 265
266 266 def readauthormap(self, authorfile):
267 267 afile = open(authorfile, 'r')
268 268 for line in afile:
269 269
270 270 line = line.strip()
271 271 if not line or line.startswith('#'):
272 272 continue
273 273
274 274 try:
275 275 srcauthor, dstauthor = line.split('=', 1)
276 276 except ValueError:
277 277 msg = _('Ignoring bad line in author map file %s: %s\n')
278 278 self.ui.warn(msg % (authorfile, line.rstrip()))
279 279 continue
280 280
281 281 srcauthor = srcauthor.strip()
282 282 dstauthor = dstauthor.strip()
283 283 if self.authors.get(srcauthor) in (None, dstauthor):
284 284 msg = _('mapping author %s to %s\n')
285 285 self.ui.debug(msg % (srcauthor, dstauthor))
286 286 self.authors[srcauthor] = dstauthor
287 287 continue
288 288
289 289 m = _('overriding mapping for author %s, was %s, will be %s\n')
290 290 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
291 291
292 292 afile.close()
293 293
294 294 def cachecommit(self, rev):
295 295 commit = self.source.getcommit(rev)
296 296 commit.author = self.authors.get(commit.author, commit.author)
297 297 commit.branch = self.branchmap.get(commit.branch, commit.branch)
298 298 self.commitcache[rev] = commit
299 299 return commit
300 300
301 301 def copy(self, rev):
302 302 commit = self.commitcache[rev]
303 303
304 304 changes = self.source.getchanges(rev)
305 305 if isinstance(changes, basestring):
306 306 if changes == SKIPREV:
307 307 dest = SKIPREV
308 308 else:
309 309 dest = self.map[changes]
310 310 self.map[rev] = dest
311 311 return
312 312 files, copies = changes
313 313 pbranches = []
314 314 if commit.parents:
315 315 for prev in commit.parents:
316 316 if prev not in self.commitcache:
317 317 self.cachecommit(prev)
318 318 pbranches.append((self.map[prev],
319 319 self.commitcache[prev].branch))
320 320 self.dest.setbranch(commit.branch, pbranches)
321 321 try:
322 322 parents = self.splicemap[rev].replace(',', ' ').split()
323 323 self.ui.status(_('spliced in %s as parents of %s\n') %
324 324 (parents, rev))
325 325 parents = [self.map.get(p, p) for p in parents]
326 326 except KeyError:
327 327 parents = [b[0] for b in pbranches]
328 328 source = progresssource(self.ui, self.source, len(files))
329 329 newnode = self.dest.putcommit(files, copies, parents, commit,
330 330 source, self.map)
331 331 source.close()
332 332 self.source.converted(rev, newnode)
333 333 self.map[rev] = newnode
334 334
335 335 def convert(self, sortmode):
336 336 try:
337 337 self.source.before()
338 338 self.dest.before()
339 339 self.source.setrevmap(self.map)
340 340 self.ui.status(_("scanning source...\n"))
341 341 heads = self.source.getheads()
342 342 parents = self.walktree(heads)
343 343 self.ui.status(_("sorting...\n"))
344 344 t = self.toposort(parents, sortmode)
345 345 num = len(t)
346 346 c = None
347 347
348 348 self.ui.status(_("converting...\n"))
349 349 for i, c in enumerate(t):
350 350 num -= 1
351 351 desc = self.commitcache[c].desc
352 352 if "\n" in desc:
353 353 desc = desc.splitlines()[0]
354 354 # convert log message to local encoding without using
355 355 # tolocal() because the encoding.encoding convert()
356 356 # uses is 'utf-8'
357 357 self.ui.status("%d %s\n" % (num, recode(desc)))
358 358 self.ui.note(_("source: %s\n") % recode(c))
359 359 self.ui.progress(_('converting'), i, unit=_('revisions'),
360 360 total=len(t))
361 361 self.copy(c)
362 362 self.ui.progress(_('converting'), None)
363 363
364 364 tags = self.source.gettags()
365 365 ctags = {}
366 366 for k in tags:
367 367 v = tags[k]
368 368 if self.map.get(v, SKIPREV) != SKIPREV:
369 369 ctags[k] = self.map[v]
370 370
371 371 if c and ctags:
372 372 nrev, tagsparent = self.dest.puttags(ctags)
373 373 if nrev and tagsparent:
374 374 # write another hash correspondence to override the previous
375 375 # one so we don't end up with extra tag heads
376 376 tagsparents = [e for e in self.map.iteritems()
377 377 if e[1] == tagsparent]
378 378 if tagsparents:
379 379 self.map[tagsparents[0][0]] = nrev
380 380
381 bookmarks = self.source.getbookmarks()
382 cbookmarks = {}
383 for k in bookmarks:
384 v = bookmarks[k]
385 if self.map.get(v, SKIPREV) != SKIPREV:
386 cbookmarks[k] = self.map[v]
387
388 if c and cbookmarks:
389 self.dest.putbookmarks(cbookmarks)
390
381 391 self.writeauthormap()
382 392 finally:
383 393 self.cleanup()
384 394
385 395 def cleanup(self):
386 396 try:
387 397 self.dest.after()
388 398 finally:
389 399 self.source.after()
390 400 self.map.close()
391 401
392 402 def convert(ui, src, dest=None, revmapfile=None, **opts):
393 403 global orig_encoding
394 404 orig_encoding = encoding.encoding
395 405 encoding.encoding = 'UTF-8'
396 406
397 407 # support --authors as an alias for --authormap
398 408 if not opts.get('authormap'):
399 409 opts['authormap'] = opts.get('authors')
400 410
401 411 if not dest:
402 412 dest = hg.defaultdest(src) + "-hg"
403 413 ui.status(_("assuming destination %s\n") % dest)
404 414
405 415 destc = convertsink(ui, dest, opts.get('dest_type'))
406 416
407 417 try:
408 418 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
409 419 opts.get('rev'))
410 420 except Exception:
411 421 for path in destc.created:
412 422 shutil.rmtree(path, True)
413 423 raise
414 424
415 425 sortmodes = ('branchsort', 'datesort', 'sourcesort')
416 426 sortmode = [m for m in sortmodes if opts.get(m)]
417 427 if len(sortmode) > 1:
418 428 raise util.Abort(_('more than one sort mode specified'))
419 429 sortmode = sortmode and sortmode[0] or defaultsort
420 430 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
421 431 raise util.Abort(_('--sourcesort is not supported by this data source'))
422 432
423 433 fmap = opts.get('filemap')
424 434 if fmap:
425 435 srcc = filemap.filemap_source(ui, srcc, fmap)
426 436 destc.setfilemapmode(True)
427 437
428 438 if not revmapfile:
429 439 try:
430 440 revmapfile = destc.revmapfile()
431 441 except:
432 442 revmapfile = os.path.join(destc, "map")
433 443
434 444 c = converter(ui, srcc, destc, revmapfile, opts)
435 445 c.convert(sortmode)
436 446
General Comments 0
You need to be logged in to leave comments. Login now