##// END OF EJS Templates
convert: use 'default' for specifying branch name in branchmap (issue4753)...
Eugene Baranov -
r25805:584044e5 default
parent child Browse files
Show More
@@ -1,548 +1,576 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, shlex
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 def mapbranch(branch, branchmap):
33 '''
34 >>> bmap = {'default': 'branch1'}
35 >>> for i in ['', None]:
36 ... mapbranch(i, bmap)
37 'branch1'
38 'branch1'
39 >>> bmap = {'None': 'branch2'}
40 >>> for i in ['', None]:
41 ... mapbranch(i, bmap)
42 'branch2'
43 'branch2'
44 >>> bmap = {'None': 'branch3', 'default': 'branch4'}
45 >>> for i in ['None', '', None, 'default', 'branch5']:
46 ... mapbranch(i, bmap)
47 'branch3'
48 'branch4'
49 'branch4'
50 'branch4'
51 'branch5'
52 '''
53 # If branch is None or empty, this commit is coming from the source
54 # repository's default branch and destined for the default branch in the
55 # destination repository. For such commits, using a literal "default"
56 # in branchmap below allows the user to map "default" to an alternate
57 # default branch in the destination repository.
58 branch = branchmap.get(branch or 'default', branch)
59 # At some point we used "None" literal to denote the default branch,
60 # attempt to use that for backward compatibility.
61 if (not branch):
62 branch = branchmap.get(str(None), branch)
63 return branch
64
32 65 source_converters = [
33 66 ('cvs', convert_cvs, 'branchsort'),
34 67 ('git', convert_git, 'branchsort'),
35 68 ('svn', svn_source, 'branchsort'),
36 69 ('hg', mercurial_source, 'sourcesort'),
37 70 ('darcs', darcs_source, 'branchsort'),
38 71 ('mtn', monotone_source, 'branchsort'),
39 72 ('gnuarch', gnuarch_source, 'branchsort'),
40 73 ('bzr', bzr_source, 'branchsort'),
41 74 ('p4', p4_source, 'branchsort'),
42 75 ]
43 76
44 77 sink_converters = [
45 78 ('hg', mercurial_sink),
46 79 ('svn', svn_sink),
47 80 ]
48 81
49 82 def convertsource(ui, path, type, revs):
50 83 exceptions = []
51 84 if type and type not in [s[0] for s in source_converters]:
52 85 raise util.Abort(_('%s: invalid source repository type') % type)
53 86 for name, source, sortmode in source_converters:
54 87 try:
55 88 if not type or name == type:
56 89 return source(ui, path, revs), sortmode
57 90 except (NoRepo, MissingTool) as inst:
58 91 exceptions.append(inst)
59 92 if not ui.quiet:
60 93 for inst in exceptions:
61 94 ui.write("%s\n" % inst)
62 95 raise util.Abort(_('%s: missing or unsupported repository') % path)
63 96
64 97 def convertsink(ui, path, type):
65 98 if type and type not in [s[0] for s in sink_converters]:
66 99 raise util.Abort(_('%s: invalid destination repository type') % type)
67 100 for name, sink in sink_converters:
68 101 try:
69 102 if not type or name == type:
70 103 return sink(ui, path)
71 104 except NoRepo as inst:
72 105 ui.note(_("convert: %s\n") % inst)
73 106 except MissingTool as inst:
74 107 raise util.Abort('%s\n' % inst)
75 108 raise util.Abort(_('%s: unknown repository type') % path)
76 109
77 110 class progresssource(object):
78 111 def __init__(self, ui, source, filecount):
79 112 self.ui = ui
80 113 self.source = source
81 114 self.filecount = filecount
82 115 self.retrieved = 0
83 116
84 117 def getfile(self, file, rev):
85 118 self.retrieved += 1
86 119 self.ui.progress(_('getting files'), self.retrieved,
87 120 item=file, total=self.filecount)
88 121 return self.source.getfile(file, rev)
89 122
90 123 def lookuprev(self, rev):
91 124 return self.source.lookuprev(rev)
92 125
93 126 def close(self):
94 127 self.ui.progress(_('getting files'), None)
95 128
96 129 class converter(object):
97 130 def __init__(self, ui, source, dest, revmapfile, opts):
98 131
99 132 self.source = source
100 133 self.dest = dest
101 134 self.ui = ui
102 135 self.opts = opts
103 136 self.commitcache = {}
104 137 self.authors = {}
105 138 self.authorfile = None
106 139
107 140 # Record converted revisions persistently: maps source revision
108 141 # ID to target revision ID (both strings). (This is how
109 142 # incremental conversions work.)
110 143 self.map = mapfile(ui, revmapfile)
111 144
112 145 # Read first the dst author map if any
113 146 authorfile = self.dest.authorfile()
114 147 if authorfile and os.path.exists(authorfile):
115 148 self.readauthormap(authorfile)
116 149 # Extend/Override with new author map if necessary
117 150 if opts.get('authormap'):
118 151 self.readauthormap(opts.get('authormap'))
119 152 self.authorfile = self.dest.authorfile()
120 153
121 154 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
122 155 self.branchmap = mapfile(ui, opts.get('branchmap'))
123 156
124 157 def parsesplicemap(self, path):
125 158 """ check and validate the splicemap format and
126 159 return a child/parents dictionary.
127 160 Format checking has two parts.
128 161 1. generic format which is same across all source types
129 162 2. specific format checking which may be different for
130 163 different source type. This logic is implemented in
131 164 checkrevformat function in source files like
132 165 hg.py, subversion.py etc.
133 166 """
134 167
135 168 if not path:
136 169 return {}
137 170 m = {}
138 171 try:
139 172 fp = open(path, 'r')
140 173 for i, line in enumerate(fp):
141 174 line = line.splitlines()[0].rstrip()
142 175 if not line:
143 176 # Ignore blank lines
144 177 continue
145 178 # split line
146 179 lex = shlex.shlex(line, posix=True)
147 180 lex.whitespace_split = True
148 181 lex.whitespace += ','
149 182 line = list(lex)
150 183 # check number of parents
151 184 if not (2 <= len(line) <= 3):
152 185 raise util.Abort(_('syntax error in %s(%d): child parent1'
153 186 '[,parent2] expected') % (path, i + 1))
154 187 for part in line:
155 188 self.source.checkrevformat(part)
156 189 child, p1, p2 = line[0], line[1:2], line[2:]
157 190 if p1 == p2:
158 191 m[child] = p1
159 192 else:
160 193 m[child] = p1 + p2
161 194 # if file does not exist or error reading, exit
162 195 except IOError:
163 196 raise util.Abort(_('splicemap file not found or error reading %s:')
164 197 % path)
165 198 return m
166 199
167 200
168 201 def walktree(self, heads):
169 202 '''Return a mapping that identifies the uncommitted parents of every
170 203 uncommitted changeset.'''
171 204 visit = heads
172 205 known = set()
173 206 parents = {}
174 207 numcommits = self.source.numcommits()
175 208 while visit:
176 209 n = visit.pop(0)
177 210 if n in known:
178 211 continue
179 212 if n in self.map:
180 213 m = self.map[n]
181 214 if m == SKIPREV or self.dest.hascommitfrommap(m):
182 215 continue
183 216 known.add(n)
184 217 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
185 218 total=numcommits)
186 219 commit = self.cachecommit(n)
187 220 parents[n] = []
188 221 for p in commit.parents:
189 222 parents[n].append(p)
190 223 visit.append(p)
191 224 self.ui.progress(_('scanning'), None)
192 225
193 226 return parents
194 227
195 228 def mergesplicemap(self, parents, splicemap):
196 229 """A splicemap redefines child/parent relationships. Check the
197 230 map contains valid revision identifiers and merge the new
198 231 links in the source graph.
199 232 """
200 233 for c in sorted(splicemap):
201 234 if c not in parents:
202 235 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
203 236 # Could be in source but not converted during this run
204 237 self.ui.warn(_('splice map revision %s is not being '
205 238 'converted, ignoring\n') % c)
206 239 continue
207 240 pc = []
208 241 for p in splicemap[c]:
209 242 # We do not have to wait for nodes already in dest.
210 243 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
211 244 continue
212 245 # Parent is not in dest and not being converted, not good
213 246 if p not in parents:
214 247 raise util.Abort(_('unknown splice map parent: %s') % p)
215 248 pc.append(p)
216 249 parents[c] = pc
217 250
218 251 def toposort(self, parents, sortmode):
219 252 '''Return an ordering such that every uncommitted changeset is
220 253 preceded by all its uncommitted ancestors.'''
221 254
222 255 def mapchildren(parents):
223 256 """Return a (children, roots) tuple where 'children' maps parent
224 257 revision identifiers to children ones, and 'roots' is the list of
225 258 revisions without parents. 'parents' must be a mapping of revision
226 259 identifier to its parents ones.
227 260 """
228 261 visit = sorted(parents)
229 262 seen = set()
230 263 children = {}
231 264 roots = []
232 265
233 266 while visit:
234 267 n = visit.pop(0)
235 268 if n in seen:
236 269 continue
237 270 seen.add(n)
238 271 # Ensure that nodes without parents are present in the
239 272 # 'children' mapping.
240 273 children.setdefault(n, [])
241 274 hasparent = False
242 275 for p in parents[n]:
243 276 if p not in self.map:
244 277 visit.append(p)
245 278 hasparent = True
246 279 children.setdefault(p, []).append(n)
247 280 if not hasparent:
248 281 roots.append(n)
249 282
250 283 return children, roots
251 284
252 285 # Sort functions are supposed to take a list of revisions which
253 286 # can be converted immediately and pick one
254 287
255 288 def makebranchsorter():
256 289 """If the previously converted revision has a child in the
257 290 eligible revisions list, pick it. Return the list head
258 291 otherwise. Branch sort attempts to minimize branch
259 292 switching, which is harmful for Mercurial backend
260 293 compression.
261 294 """
262 295 prev = [None]
263 296 def picknext(nodes):
264 297 next = nodes[0]
265 298 for n in nodes:
266 299 if prev[0] in parents[n]:
267 300 next = n
268 301 break
269 302 prev[0] = next
270 303 return next
271 304 return picknext
272 305
273 306 def makesourcesorter():
274 307 """Source specific sort."""
275 308 keyfn = lambda n: self.commitcache[n].sortkey
276 309 def picknext(nodes):
277 310 return sorted(nodes, key=keyfn)[0]
278 311 return picknext
279 312
280 313 def makeclosesorter():
281 314 """Close order sort."""
282 315 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
283 316 self.commitcache[n].sortkey)
284 317 def picknext(nodes):
285 318 return sorted(nodes, key=keyfn)[0]
286 319 return picknext
287 320
288 321 def makedatesorter():
289 322 """Sort revisions by date."""
290 323 dates = {}
291 324 def getdate(n):
292 325 if n not in dates:
293 326 dates[n] = util.parsedate(self.commitcache[n].date)
294 327 return dates[n]
295 328
296 329 def picknext(nodes):
297 330 return min([(getdate(n), n) for n in nodes])[1]
298 331
299 332 return picknext
300 333
301 334 if sortmode == 'branchsort':
302 335 picknext = makebranchsorter()
303 336 elif sortmode == 'datesort':
304 337 picknext = makedatesorter()
305 338 elif sortmode == 'sourcesort':
306 339 picknext = makesourcesorter()
307 340 elif sortmode == 'closesort':
308 341 picknext = makeclosesorter()
309 342 else:
310 343 raise util.Abort(_('unknown sort mode: %s') % sortmode)
311 344
312 345 children, actives = mapchildren(parents)
313 346
314 347 s = []
315 348 pendings = {}
316 349 while actives:
317 350 n = picknext(actives)
318 351 actives.remove(n)
319 352 s.append(n)
320 353
321 354 # Update dependents list
322 355 for c in children.get(n, []):
323 356 if c not in pendings:
324 357 pendings[c] = [p for p in parents[c] if p not in self.map]
325 358 try:
326 359 pendings[c].remove(n)
327 360 except ValueError:
328 361 raise util.Abort(_('cycle detected between %s and %s')
329 362 % (recode(c), recode(n)))
330 363 if not pendings[c]:
331 364 # Parents are converted, node is eligible
332 365 actives.insert(0, c)
333 366 pendings[c] = None
334 367
335 368 if len(s) != len(parents):
336 369 raise util.Abort(_("not all revisions were sorted"))
337 370
338 371 return s
339 372
340 373 def writeauthormap(self):
341 374 authorfile = self.authorfile
342 375 if authorfile:
343 376 self.ui.status(_('writing author map file %s\n') % authorfile)
344 377 ofile = open(authorfile, 'w+')
345 378 for author in self.authors:
346 379 ofile.write("%s=%s\n" % (author, self.authors[author]))
347 380 ofile.close()
348 381
349 382 def readauthormap(self, authorfile):
350 383 afile = open(authorfile, 'r')
351 384 for line in afile:
352 385
353 386 line = line.strip()
354 387 if not line or line.startswith('#'):
355 388 continue
356 389
357 390 try:
358 391 srcauthor, dstauthor = line.split('=', 1)
359 392 except ValueError:
360 393 msg = _('ignoring bad line in author map file %s: %s\n')
361 394 self.ui.warn(msg % (authorfile, line.rstrip()))
362 395 continue
363 396
364 397 srcauthor = srcauthor.strip()
365 398 dstauthor = dstauthor.strip()
366 399 if self.authors.get(srcauthor) in (None, dstauthor):
367 400 msg = _('mapping author %s to %s\n')
368 401 self.ui.debug(msg % (srcauthor, dstauthor))
369 402 self.authors[srcauthor] = dstauthor
370 403 continue
371 404
372 405 m = _('overriding mapping for author %s, was %s, will be %s\n')
373 406 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
374 407
375 408 afile.close()
376 409
377 410 def cachecommit(self, rev):
378 411 commit = self.source.getcommit(rev)
379 412 commit.author = self.authors.get(commit.author, commit.author)
380 # If commit.branch is None, this commit is coming from the source
381 # repository's default branch and destined for the default branch in the
382 # destination repository. For such commits, passing a literal "None"
383 # string to branchmap.get() below allows the user to map "None" to an
384 # alternate default branch in the destination repository.
385 commit.branch = self.branchmap.get(str(commit.branch), commit.branch)
413 commit.branch = mapbranch(commit.branch, self.branchmap)
386 414 self.commitcache[rev] = commit
387 415 return commit
388 416
389 417 def copy(self, rev):
390 418 commit = self.commitcache[rev]
391 419 full = self.opts.get('full')
392 420 changes = self.source.getchanges(rev, full)
393 421 if isinstance(changes, basestring):
394 422 if changes == SKIPREV:
395 423 dest = SKIPREV
396 424 else:
397 425 dest = self.map[changes]
398 426 self.map[rev] = dest
399 427 return
400 428 files, copies, cleanp2 = changes
401 429 pbranches = []
402 430 if commit.parents:
403 431 for prev in commit.parents:
404 432 if prev not in self.commitcache:
405 433 self.cachecommit(prev)
406 434 pbranches.append((self.map[prev],
407 435 self.commitcache[prev].branch))
408 436 self.dest.setbranch(commit.branch, pbranches)
409 437 try:
410 438 parents = self.splicemap[rev]
411 439 self.ui.status(_('spliced in %s as parents of %s\n') %
412 440 (parents, rev))
413 441 parents = [self.map.get(p, p) for p in parents]
414 442 except KeyError:
415 443 parents = [b[0] for b in pbranches]
416 444 if len(pbranches) != 2:
417 445 cleanp2 = set()
418 446 if len(parents) < 3:
419 447 source = progresssource(self.ui, self.source, len(files))
420 448 else:
421 449 # For an octopus merge, we end up traversing the list of
422 450 # changed files N-1 times. This tweak to the number of
423 451 # files makes it so the progress bar doesn't overflow
424 452 # itself.
425 453 source = progresssource(self.ui, self.source,
426 454 len(files) * (len(parents) - 1))
427 455 newnode = self.dest.putcommit(files, copies, parents, commit,
428 456 source, self.map, full, cleanp2)
429 457 source.close()
430 458 self.source.converted(rev, newnode)
431 459 self.map[rev] = newnode
432 460
433 461 def convert(self, sortmode):
434 462 try:
435 463 self.source.before()
436 464 self.dest.before()
437 465 self.source.setrevmap(self.map)
438 466 self.ui.status(_("scanning source...\n"))
439 467 heads = self.source.getheads()
440 468 parents = self.walktree(heads)
441 469 self.mergesplicemap(parents, self.splicemap)
442 470 self.ui.status(_("sorting...\n"))
443 471 t = self.toposort(parents, sortmode)
444 472 num = len(t)
445 473 c = None
446 474
447 475 self.ui.status(_("converting...\n"))
448 476 for i, c in enumerate(t):
449 477 num -= 1
450 478 desc = self.commitcache[c].desc
451 479 if "\n" in desc:
452 480 desc = desc.splitlines()[0]
453 481 # convert log message to local encoding without using
454 482 # tolocal() because the encoding.encoding convert()
455 483 # uses is 'utf-8'
456 484 self.ui.status("%d %s\n" % (num, recode(desc)))
457 485 self.ui.note(_("source: %s\n") % recode(c))
458 486 self.ui.progress(_('converting'), i, unit=_('revisions'),
459 487 total=len(t))
460 488 self.copy(c)
461 489 self.ui.progress(_('converting'), None)
462 490
463 491 if not self.ui.configbool('convert', 'skiptags'):
464 492 tags = self.source.gettags()
465 493 ctags = {}
466 494 for k in tags:
467 495 v = tags[k]
468 496 if self.map.get(v, SKIPREV) != SKIPREV:
469 497 ctags[k] = self.map[v]
470 498
471 499 if c and ctags:
472 500 nrev, tagsparent = self.dest.puttags(ctags)
473 501 if nrev and tagsparent:
474 502 # write another hash correspondence to override the
475 503 # previous one so we don't end up with extra tag heads
476 504 tagsparents = [e for e in self.map.iteritems()
477 505 if e[1] == tagsparent]
478 506 if tagsparents:
479 507 self.map[tagsparents[0][0]] = nrev
480 508
481 509 bookmarks = self.source.getbookmarks()
482 510 cbookmarks = {}
483 511 for k in bookmarks:
484 512 v = bookmarks[k]
485 513 if self.map.get(v, SKIPREV) != SKIPREV:
486 514 cbookmarks[k] = self.map[v]
487 515
488 516 if c and cbookmarks:
489 517 self.dest.putbookmarks(cbookmarks)
490 518
491 519 self.writeauthormap()
492 520 finally:
493 521 self.cleanup()
494 522
495 523 def cleanup(self):
496 524 try:
497 525 self.dest.after()
498 526 finally:
499 527 self.source.after()
500 528 self.map.close()
501 529
502 530 def convert(ui, src, dest=None, revmapfile=None, **opts):
503 531 global orig_encoding
504 532 orig_encoding = encoding.encoding
505 533 encoding.encoding = 'UTF-8'
506 534
507 535 # support --authors as an alias for --authormap
508 536 if not opts.get('authormap'):
509 537 opts['authormap'] = opts.get('authors')
510 538
511 539 if not dest:
512 540 dest = hg.defaultdest(src) + "-hg"
513 541 ui.status(_("assuming destination %s\n") % dest)
514 542
515 543 destc = convertsink(ui, dest, opts.get('dest_type'))
516 544
517 545 try:
518 546 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
519 547 opts.get('rev'))
520 548 except Exception:
521 549 for path in destc.created:
522 550 shutil.rmtree(path, True)
523 551 raise
524 552
525 553 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
526 554 sortmode = [m for m in sortmodes if opts.get(m)]
527 555 if len(sortmode) > 1:
528 556 raise util.Abort(_('more than one sort mode specified'))
529 557 if sortmode:
530 558 sortmode = sortmode[0]
531 559 else:
532 560 sortmode = defaultsort
533 561
534 562 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
535 563 raise util.Abort(_('--sourcesort is not supported by this data source'))
536 564 if sortmode == 'closesort' and not srcc.hasnativeclose():
537 565 raise util.Abort(_('--closesort is not supported by this data source'))
538 566
539 567 fmap = opts.get('filemap')
540 568 if fmap:
541 569 srcc = filemap.filemap_source(ui, srcc, fmap)
542 570 destc.setfilemapmode(True)
543 571
544 572 if not revmapfile:
545 573 revmapfile = destc.revmapfile()
546 574
547 575 c = converter(ui, srcc, destc, revmapfile, opts)
548 576 c.convert(sortmode)
@@ -1,284 +1,284 b''
1 1 # Perforce source for convert extension.
2 2 #
3 3 # Copyright 2009, Frank Kingswood <frank@kingswood-consulting.co.uk>
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 mercurial import util
9 9 from mercurial.i18n import _
10 10
11 11 from common import commit, converter_source, checktool, NoRepo
12 12 import marshal
13 13 import re
14 14
15 15 def loaditer(f):
16 16 "Yield the dictionary objects generated by p4"
17 17 try:
18 18 while True:
19 19 d = marshal.load(f)
20 20 if not d:
21 21 break
22 22 yield d
23 23 except EOFError:
24 24 pass
25 25
26 26 def decodefilename(filename):
27 27 """Perforce escapes special characters @, #, *, or %
28 28 with %40, %23, %2A, or %25 respectively
29 29
30 30 >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
31 31 'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
32 32 >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
33 33 '//Depot/Directory/%25/%23/#@.*'
34 34 """
35 35 replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
36 36 for k, v in replacements:
37 37 filename = filename.replace(k, v)
38 38 return filename
39 39
40 40 class p4_source(converter_source):
41 41 def __init__(self, ui, path, revs=None):
42 42 super(p4_source, self).__init__(ui, path, revs=revs)
43 43
44 44 if "/" in path and not path.startswith('//'):
45 45 raise NoRepo(_('%s does not look like a P4 repository') % path)
46 46
47 47 checktool('p4', abort=False)
48 48
49 49 self.p4changes = {}
50 50 self.heads = {}
51 51 self.changeset = {}
52 52 self.files = {}
53 53 self.copies = {}
54 54 self.tags = {}
55 55 self.lastbranch = {}
56 56 self.parent = {}
57 57 self.encoding = "latin_1"
58 58 self.depotname = {} # mapping from local name to depot name
59 59 self.localname = {} # mapping from depot name to local name
60 60 self.re_type = re.compile(
61 61 "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
62 62 "(\+\w+)?$")
63 63 self.re_keywords = re.compile(
64 64 r"\$(Id|Header|Date|DateTime|Change|File|Revision|Author)"
65 65 r":[^$\n]*\$")
66 66 self.re_keywords_old = re.compile("\$(Id|Header):[^$\n]*\$")
67 67
68 68 if revs and len(revs) > 1:
69 69 raise util.Abort(_("p4 source does not support specifying "
70 70 "multiple revisions"))
71 71 self._parse(ui, path)
72 72
73 73 def _parse_view(self, path):
74 74 "Read changes affecting the path"
75 75 cmd = 'p4 -G changes -s submitted %s' % util.shellquote(path)
76 76 stdout = util.popen(cmd, mode='rb')
77 77 for d in loaditer(stdout):
78 78 c = d.get("change", None)
79 79 if c:
80 80 self.p4changes[c] = True
81 81
82 82 def _parse(self, ui, path):
83 83 "Prepare list of P4 filenames and revisions to import"
84 84 ui.status(_('reading p4 views\n'))
85 85
86 86 # read client spec or view
87 87 if "/" in path:
88 88 self._parse_view(path)
89 89 if path.startswith("//") and path.endswith("/..."):
90 90 views = {path[:-3]:""}
91 91 else:
92 92 views = {"//": ""}
93 93 else:
94 94 cmd = 'p4 -G client -o %s' % util.shellquote(path)
95 95 clientspec = marshal.load(util.popen(cmd, mode='rb'))
96 96
97 97 views = {}
98 98 for client in clientspec:
99 99 if client.startswith("View"):
100 100 sview, cview = clientspec[client].split()
101 101 self._parse_view(sview)
102 102 if sview.endswith("...") and cview.endswith("..."):
103 103 sview = sview[:-3]
104 104 cview = cview[:-3]
105 105 cview = cview[2:]
106 106 cview = cview[cview.find("/") + 1:]
107 107 views[sview] = cview
108 108
109 109 # list of changes that affect our source files
110 110 self.p4changes = self.p4changes.keys()
111 111 self.p4changes.sort(key=int)
112 112
113 113 # list with depot pathnames, longest first
114 114 vieworder = views.keys()
115 115 vieworder.sort(key=len, reverse=True)
116 116
117 117 # handle revision limiting
118 118 startrev = self.ui.config('convert', 'p4.startrev', default=0)
119 119 self.p4changes = [x for x in self.p4changes
120 120 if ((not startrev or int(x) >= int(startrev)) and
121 121 (not self.revs or int(x) <= int(self.revs[0])))]
122 122
123 123 # now read the full changelists to get the list of file revisions
124 124 ui.status(_('collecting p4 changelists\n'))
125 125 lastid = None
126 126 for change in self.p4changes:
127 127 cmd = "p4 -G describe -s %s" % change
128 128 stdout = util.popen(cmd, mode='rb')
129 129 d = marshal.load(stdout)
130 130 desc = self.recode(d.get("desc", ""))
131 131 shortdesc = desc.split("\n", 1)[0]
132 132 t = '%s %s' % (d["change"], repr(shortdesc)[1:-1])
133 133 ui.status(util.ellipsis(t, 80) + '\n')
134 134
135 135 if lastid:
136 136 parents = [lastid]
137 137 else:
138 138 parents = []
139 139
140 140 date = (int(d["time"]), 0) # timezone not set
141 141 c = commit(author=self.recode(d["user"]),
142 142 date=util.datestr(date, '%Y-%m-%d %H:%M:%S %1%2'),
143 parents=parents, desc=desc, branch='',
143 parents=parents, desc=desc, branch=None,
144 144 extra={"p4": change})
145 145
146 146 files = []
147 147 copies = {}
148 148 copiedfiles = []
149 149 i = 0
150 150 while ("depotFile%d" % i) in d and ("rev%d" % i) in d:
151 151 oldname = d["depotFile%d" % i]
152 152 filename = None
153 153 for v in vieworder:
154 154 if oldname.lower().startswith(v.lower()):
155 155 filename = decodefilename(views[v] + oldname[len(v):])
156 156 break
157 157 if filename:
158 158 files.append((filename, d["rev%d" % i]))
159 159 self.depotname[filename] = oldname
160 160 if (d.get("action%d" % i) == "move/add"):
161 161 copiedfiles.append(filename)
162 162 self.localname[oldname] = filename
163 163 i += 1
164 164
165 165 # Collect information about copied files
166 166 for filename in copiedfiles:
167 167 oldname = self.depotname[filename]
168 168
169 169 flcmd = 'p4 -G filelog %s' \
170 170 % util.shellquote(oldname)
171 171 flstdout = util.popen(flcmd, mode='rb')
172 172
173 173 copiedfilename = None
174 174 for d in loaditer(flstdout):
175 175 copiedoldname = None
176 176
177 177 i = 0
178 178 while ("change%d" % i) in d:
179 179 if (d["change%d" % i] == change and
180 180 d["action%d" % i] == "move/add"):
181 181 j = 0
182 182 while ("file%d,%d" % (i, j)) in d:
183 183 if d["how%d,%d" % (i, j)] == "moved from":
184 184 copiedoldname = d["file%d,%d" % (i, j)]
185 185 break
186 186 j += 1
187 187 i += 1
188 188
189 189 if copiedoldname and copiedoldname in self.localname:
190 190 copiedfilename = self.localname[copiedoldname]
191 191 break
192 192
193 193 if copiedfilename:
194 194 copies[filename] = copiedfilename
195 195 else:
196 196 ui.warn(_("cannot find source for copied file: %s@%s\n")
197 197 % (filename, change))
198 198
199 199 self.changeset[change] = c
200 200 self.files[change] = files
201 201 self.copies[change] = copies
202 202 lastid = change
203 203
204 204 if lastid:
205 205 self.heads = [lastid]
206 206
207 207 def getheads(self):
208 208 return self.heads
209 209
210 210 def getfile(self, name, rev):
211 211 cmd = 'p4 -G print %s' \
212 212 % util.shellquote("%s#%s" % (self.depotname[name], rev))
213 213
214 214 lasterror = None
215 215 while True:
216 216 stdout = util.popen(cmd, mode='rb')
217 217
218 218 mode = None
219 219 contents = ""
220 220 keywords = None
221 221
222 222 for d in loaditer(stdout):
223 223 code = d["code"]
224 224 data = d.get("data")
225 225
226 226 if code == "error":
227 227 # if this is the first time error happened
228 228 # re-attempt getting the file
229 229 if not lasterror:
230 230 lasterror = IOError(d["generic"], data)
231 231 # this will exit inner-most for-loop
232 232 break
233 233 else:
234 234 raise lasterror
235 235
236 236 elif code == "stat":
237 237 action = d.get("action")
238 238 if action in ["purge", "delete", "move/delete"]:
239 239 return None, None
240 240 p4type = self.re_type.match(d["type"])
241 241 if p4type:
242 242 mode = ""
243 243 flags = ((p4type.group(1) or "")
244 244 + (p4type.group(3) or ""))
245 245 if "x" in flags:
246 246 mode = "x"
247 247 if p4type.group(2) == "symlink":
248 248 mode = "l"
249 249 if "ko" in flags:
250 250 keywords = self.re_keywords_old
251 251 elif "k" in flags:
252 252 keywords = self.re_keywords
253 253
254 254 elif code == "text" or code == "binary":
255 255 contents += data
256 256
257 257 lasterror = None
258 258
259 259 if not lasterror:
260 260 break
261 261
262 262 if mode is None:
263 263 return None, None
264 264
265 265 if keywords:
266 266 contents = keywords.sub("$\\1$", contents)
267 267 if mode == "l" and contents.endswith("\n"):
268 268 contents = contents[:-1]
269 269
270 270 return contents, mode
271 271
272 272 def getchanges(self, rev, full):
273 273 if full:
274 274 raise util.Abort(_("convert from p4 do not support --full"))
275 275 return self.files[rev], self.copies[rev], set()
276 276
277 277 def getcommit(self, rev):
278 278 return self.changeset[rev]
279 279
280 280 def gettags(self):
281 281 return self.tags
282 282
283 283 def getchangedfiles(self, rev, i):
284 284 return sorted([x[0] for x in self.files[rev]])
@@ -1,129 +1,128 b''
1 1 #require svn svn-bindings
2 2
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [extensions]
5 5 > convert =
6 6 > EOF
7 7
8 8 $ svnadmin create svn-repo
9 9 $ svnadmin load -q svn-repo < "$TESTDIR/svn/branches.svndump"
10 10
11 11 Convert trunk and branches
12 12
13 13 $ cat > branchmap <<EOF
14 14 > old3 newbranch
15 15 >
16 16 >
17 17 > EOF
18 18 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo A-hg
19 19 initializing destination A-hg repository
20 20 scanning source...
21 21 sorting...
22 22 converting...
23 23 10 init projA
24 24 9 hello
25 25 8 branch trunk, remove c and dir
26 26 7 change a
27 27 6 change b
28 28 5 move and update c
29 29 4 move and update c
30 30 3 change b again
31 31 2 move to old2
32 32 1 move back to old
33 33 0 last change to a
34 34
35 35 Test template keywords
36 36
37 37 $ hg -R A-hg log --template '{rev} {svnuuid}{svnpath}@{svnrev}\n'
38 38 10 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@10
39 39 9 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@9
40 40 8 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old2@8
41 41 7 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@7
42 42 6 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@6
43 43 5 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@6
44 44 4 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@5
45 45 3 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@4
46 46 2 644ede6c-2b81-4367-9dc8-d786514f2cde/branches/old@3
47 47 1 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@2
48 48 0 644ede6c-2b81-4367-9dc8-d786514f2cde/trunk@1
49 49
50 50 Convert again
51 51
52 52 $ hg convert --branchmap=branchmap --datesort svn-repo A-hg
53 53 scanning source...
54 54 sorting...
55 55 converting...
56 56 0 branch trunk@1 into old3
57 57
58 58 $ cd A-hg
59 59 $ hg log -G --template 'branch={branches} {rev} {desc|firstline} files: {files}\n'
60 60 o branch=newbranch 11 branch trunk@1 into old3 files:
61 61 |
62 62 | o branch= 10 last change to a files: a
63 63 | |
64 64 | | o branch=old 9 move back to old files:
65 65 | | |
66 66 | | o branch=old2 8 move to old2 files:
67 67 | | |
68 68 | | o branch=old 7 change b again files: b
69 69 | | |
70 70 | o | branch= 6 move and update c files: b
71 71 | | |
72 72 | | o branch=old 5 move and update c files: c
73 73 | | |
74 74 | | o branch=old 4 change b files: b
75 75 | | |
76 76 | o | branch= 3 change a files: a
77 77 | | |
78 78 | | o branch=old 2 branch trunk, remove c and dir files: c
79 79 | |/
80 80 | o branch= 1 hello files: a b c dir/e
81 81 |/
82 82 o branch= 0 init projA files:
83 83
84 84
85 85 $ hg branches
86 86 newbranch 11:a6d7cc050ad1
87 87 default 10:6e2b33404495
88 88 old 9:93c4b0f99529
89 89 old2 8:b52884d7bead (inactive)
90 90 $ hg tags -q
91 91 tip
92 92 $ cd ..
93 93
94 94 Test hg failing to call itself
95 95
96 96 $ HG=foobar hg convert svn-repo B-hg 2>&1 | grep abort
97 97 abort: Mercurial failed to run itself, check hg executable is in PATH
98 98
99 99 Convert 'trunk' to branch other than 'default'
100 100
101 101 $ cat > branchmap <<EOF
102 > None hgtrunk
102 > default hgtrunk
103 103 >
104 104 >
105 105 > EOF
106 106 $ hg convert --branchmap=branchmap --datesort -r 10 svn-repo C-hg
107 107 initializing destination C-hg repository
108 108 scanning source...
109 109 sorting...
110 110 converting...
111 111 10 init projA
112 112 9 hello
113 113 8 branch trunk, remove c and dir
114 114 7 change a
115 115 6 change b
116 116 5 move and update c
117 117 4 move and update c
118 118 3 change b again
119 119 2 move to old2
120 120 1 move back to old
121 121 0 last change to a
122 122
123 123 $ cd C-hg
124 $ hg branches
125 hgtrunk 10:745f063703b4
126 old 9:aa50d7b8d922
127 old2 8:c85a22267b6e (inactive)
124 $ hg branches --template '{branch}\n'
125 hgtrunk
126 old
127 old2
128 128 $ cd ..
129
@@ -1,38 +1,39 b''
1 1 # this is hack to make sure no escape characters are inserted into the output
2 2 import os, sys
3 3 if 'TERM' in os.environ:
4 4 del os.environ['TERM']
5 5 import doctest
6 6
7 7 def testmod(name, optionflags=0, testtarget=None):
8 8 __import__(name)
9 9 mod = sys.modules[name]
10 10 if testtarget is not None:
11 11 mod = getattr(mod, testtarget)
12 12 doctest.testmod(mod, optionflags=optionflags)
13 13
14 14 testmod('mercurial.changelog')
15 15 testmod('mercurial.dagparser', optionflags=doctest.NORMALIZE_WHITESPACE)
16 16 testmod('mercurial.dispatch')
17 17 testmod('mercurial.encoding')
18 18 testmod('mercurial.hg')
19 19 testmod('mercurial.hgweb.hgwebdir_mod')
20 20 testmod('mercurial.match')
21 21 testmod('mercurial.minirst')
22 22 testmod('mercurial.patch')
23 23 testmod('mercurial.pathutil')
24 24 testmod('mercurial.parser')
25 25 testmod('mercurial.revset')
26 26 testmod('mercurial.store')
27 27 testmod('mercurial.subrepo')
28 28 testmod('mercurial.templatefilters')
29 29 testmod('mercurial.templater')
30 30 testmod('mercurial.ui')
31 31 testmod('mercurial.url')
32 32 testmod('mercurial.util')
33 33 testmod('mercurial.util', testtarget='platform')
34 testmod('hgext.convert.convcmd')
34 35 testmod('hgext.convert.cvsps')
35 36 testmod('hgext.convert.filemap')
36 37 testmod('hgext.convert.p4')
37 38 testmod('hgext.convert.subversion')
38 39 testmod('hgext.mq')
General Comments 0
You need to be logged in to leave comments. Login now