##// END OF EJS Templates
Turns convert.py into a real extension
Edouard Gomez -
r4513:ac2fe196 default
parent child Browse files
Show More
@@ -1,52 +1,24 b''
1 #!/usr/bin/env python
2 #
3 # This is a generalized framework for converting between SCM
4 # repository formats.
5 #
6 # To use, run:
7 #
8 # convert-repo <source> [<dest> [<mapfile>]]
9 #
10 # Currently accepted source formats: git, cvs
11 # Currently accepted destination formats: hg
1 # convert.py Foreign SCM converter
12 2 #
13 # If destination isn't given, a new Mercurial repo named <src>-hg will
14 # be created. If <mapfile> isn't given, it will be put in a default
15 # location (<dest>/.hg/shamap by default)
3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
16 4 #
17 # The <mapfile> is a simple text file that maps each source commit ID to
18 # the destination ID for that revision, like so:
19 #
20 # <source ID> <destination ID>
21 #
22 # If the file doesn't exist, it's automatically created. It's updated
23 # on each commit copied, so convert-repo can be interrupted and can
24 # be run repeatedly to copy new commits.
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
25 7
26 8 import sys, os, zlib, sha, time, re, locale, socket
27 os.environ["HGENCODING"] = "utf-8"
28 from mercurial import hg, ui, util, fancyopts
9 from mercurial import hg, ui, util, commands
29 10
30 class Abort(Exception): pass
11 commands.norepo += " convert"
12
31 13 class NoRepo(Exception): pass
32 14
33 15 class commit(object):
34 16 def __init__(self, **parts):
35 17 for x in "author date desc parents".split():
36 18 if not x in parts:
37 abort("commit missing field %s\n" % x)
19 raise util.Abort("commit missing field %s\n" % x)
38 20 self.__dict__.update(parts)
39 21
40 quiet = 0
41 def status(msg):
42 if not quiet: sys.stdout.write(str(msg))
43
44 def warn(msg):
45 sys.stderr.write(str(msg))
46
47 def abort(msg):
48 raise Abort(msg)
49
50 22 def recode(s):
51 23 try:
52 24 return s.decode("utf-8").encode("utf-8")
@@ -59,7 +31,7 b' def recode(s):'
59 31 class converter_source(object):
60 32 """Conversion source interface"""
61 33
62 def __init__(self, path):
34 def __init__(self, ui, path):
63 35 """Initialize conversion source (or raise NoRepo("message")
64 36 exception if path is not a valid repository)"""
65 37 raise NotImplementedError()
@@ -94,7 +66,7 b' class converter_source(object):'
94 66 class converter_sink(object):
95 67 """Conversion sink (target) interface"""
96 68
97 def __init__(self, path):
69 def __init__(self, ui, path):
98 70 """Initialize conversion sink (or raise NoRepo("message")
99 71 exception if path is not a valid repository)"""
100 72 raise NotImplementedError()
@@ -139,8 +111,9 b' class converter_sink(object):'
139 111
140 112 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
141 113 class convert_cvs(converter_source):
142 def __init__(self, path):
114 def __init__(self, ui, path):
143 115 self.path = path
116 self.ui = ui
144 117 cvs = os.path.join(path, "CVS")
145 118 if not os.path.exists(cvs):
146 119 raise NoRepo("couldn't open CVS repo %s" % path)
@@ -223,7 +196,7 b' class convert_cvs(converter_source):'
223 196 user, host = None, None
224 197 cmd = ['cvs', 'server']
225 198
226 status("connecting to %s\n" % root)
199 self.ui.status("connecting to %s\n" % root)
227 200
228 201 if root.startswith(":pserver:"):
229 202 root = root[9:]
@@ -296,7 +269,7 b' class convert_cvs(converter_source):'
296 269 self.writep.flush()
297 270 r = self.readp.readline()
298 271 if not r.startswith("Valid-requests"):
299 abort("server sucks\n")
272 raise util.Abort("server sucks\n")
300 273 if "UseUnchanged" in r:
301 274 self.writep.write("UseUnchanged\n")
302 275 self.writep.flush()
@@ -336,14 +309,14 b' class convert_cvs(converter_source):'
336 309 if line == "ok\n":
337 310 return (data, "x" in mode and "x" or "")
338 311 elif line.startswith("E "):
339 warn("cvs server: %s\n" % line[2:])
312 self.ui.warn("cvs server: %s\n" % line[2:])
340 313 elif line.startswith("Remove"):
341 314 l = self.readp.readline()
342 315 l = self.readp.readline()
343 316 if l != "ok\n":
344 abort("unknown CVS response: %s\n" % l)
317 raise util.Abort("unknown CVS response: %s\n" % l)
345 318 else:
346 abort("unknown CVS response: %s\n" % line)
319 raise util.Abort("unknown CVS response: %s\n" % line)
347 320
348 321 def getfile(self, file, rev):
349 322 data, mode = self._getfile(file, rev)
@@ -370,10 +343,11 b' class convert_cvs(converter_source):'
370 343 return self.tags
371 344
372 345 class convert_git(converter_source):
373 def __init__(self, path):
346 def __init__(self, ui, path):
374 347 if os.path.isdir(path + "/.git"):
375 348 path += "/.git"
376 349 self.path = path
350 self.ui = ui
377 351 if not os.path.exists(path + "/objects"):
378 352 raise NoRepo("couldn't open GIT repo %s" % path)
379 353
@@ -456,11 +430,11 b' class convert_git(converter_source):'
456 430 return tags
457 431
458 432 class convert_mercurial(converter_sink):
459 def __init__(self, path):
433 def __init__(self, ui, path):
460 434 self.path = path
461 u = ui.ui()
435 self.ui = ui
462 436 try:
463 self.repo = hg.repository(u, path)
437 self.repo = hg.repository(self.ui, path)
464 438 except:
465 439 raise NoRepo("could open hg repo %s" % path)
466 440
@@ -530,7 +504,7 b' class convert_mercurial(converter_sink):'
530 504 newlines.sort()
531 505
532 506 if newlines != oldlines:
533 status("updating tags\n")
507 self.ui.status("updating tags\n")
534 508 f = self.repo.wfile(".hgtags", "w")
535 509 f.write("".join(newlines))
536 510 f.close()
@@ -542,21 +516,22 b' class convert_mercurial(converter_sink):'
542 516
543 517 converters = [convert_cvs, convert_git, convert_mercurial]
544 518
545 def converter(path):
519 def converter(ui, path):
546 520 if not os.path.isdir(path):
547 abort("%s: not a directory\n" % path)
521 raise util.Abort("%s: not a directory\n" % path)
548 522 for c in converters:
549 523 try:
550 return c(path)
524 return c(ui, path)
551 525 except NoRepo:
552 526 pass
553 abort("%s: unknown repository type\n" % path)
527 raise util.Abort("%s: unknown repository type\n" % path)
554 528
555 529 class convert(object):
556 def __init__(self, source, dest, mapfile, opts):
530 def __init__(self, ui, source, dest, mapfile, opts):
557 531
558 532 self.source = source
559 533 self.dest = dest
534 self.ui = ui
560 535 self.mapfile = mapfile
561 536 self.opts = opts
562 537 self.commitcache = {}
@@ -627,7 +602,7 b' class convert(object):'
627 602 for c in children[n]:
628 603 visit.insert(0, c)
629 604
630 if opts.get('datesort'):
605 if self.opts.get('datesort'):
631 606 depth = {}
632 607 for n in s:
633 608 depth[n] = 0
@@ -660,21 +635,21 b' class convert(object):'
660 635 file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
661 636
662 637 def convert(self):
663 status("scanning source...\n")
638 self.ui.status("scanning source...\n")
664 639 heads = self.source.getheads()
665 640 parents = self.walktree(heads)
666 status("sorting...\n")
641 self.ui.status("sorting...\n")
667 642 t = self.toposort(parents)
668 643 num = len(t)
669 644 c = None
670 645
671 status("converting...\n")
646 self.ui.status("converting...\n")
672 647 for c in t:
673 648 num -= 1
674 649 desc = self.commitcache[c].desc
675 650 if "\n" in desc:
676 651 desc = desc.splitlines()[0]
677 status("%d %s\n" % (num, desc))
652 self.ui.status("%d %s\n" % (num, desc))
678 653 self.copy(c)
679 654
680 655 tags = self.source.gettags()
@@ -691,20 +666,43 b' class convert(object):'
691 666 if nrev:
692 667 file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
693 668
694 def command(src, dest=None, mapfile=None, **opts):
695 srcc = converter(src)
669 def _convert(ui, src, dest=None, mapfile=None, **opts):
670 '''Convert a foreign SCM repository to a Mercurial one.
671
672 Accepted source formats:
673 - GIT
674 - CVS
675
676 Accepted destination formats:
677 - Mercurial
678
679 If destination isn't given, a new Mercurial repo named <src>-hg will
680 be created. If <mapfile> isn't given, it will be put in a default
681 location (<dest>/.hg/shamap by default)
682
683 The <mapfile> is a simple text file that maps each source commit ID to
684 the destination ID for that revision, like so:
685
686 <source ID> <destination ID>
687
688 If the file doesn't exist, it's automatically created. It's updated
689 on each commit copied, so convert-repo can be interrupted and can
690 be run repeatedly to copy new commits.
691 '''
692
693 srcc = converter(ui, src)
696 694 if not hasattr(srcc, "getcommit"):
697 abort("%s: can't read from this repo type\n" % src)
695 raise util.Abort("%s: can't read from this repo type\n" % src)
698 696
699 697 if not dest:
700 698 dest = src + "-hg"
701 status("assuming destination %s\n" % dest)
699 ui.status("assuming destination %s\n" % dest)
702 700 if not os.path.isdir(dest):
703 status("creating repository %s\n" % dest)
701 ui.status("creating repository %s\n" % dest)
704 702 os.system("hg init " + dest)
705 destc = converter(dest)
703 destc = converter(ui, dest)
706 704 if not hasattr(destc, "putcommit"):
707 abort("%s: can't write to this repo type\n" % src)
705 raise util.Abort("%s: can't write to this repo type\n" % src)
708 706
709 707 if not mapfile:
710 708 try:
@@ -712,20 +710,11 b' def command(src, dest=None, mapfile=None'
712 710 except:
713 711 mapfile = os.path.join(destc, "map")
714 712
715 c = convert(srcc, destc, mapfile, opts)
713 c = convert(ui, srcc, destc, mapfile, opts)
716 714 c.convert()
717 715
718 options = [('q', 'quiet', None, 'suppress output'),
719 ('', 'datesort', None, 'try to sort changesets by date')]
720 opts = {}
721 args = fancyopts.fancyopts(sys.argv[1:], options, opts)
722
723 if opts['quiet']:
724 quiet = 1
725
726 try:
727 command(*args, **opts)
728 except Abort, inst:
729 warn(inst)
730 except KeyboardInterrupt:
731 status("interrupted\n")
716 cmdtable = {
717 "convert": (_convert,
718 [('', 'datesort', None, 'try to sort changesets by date')],
719 'hg convert [OPTIONS] <src> [dst [map]]'),
720 }
General Comments 0
You need to be logged in to leave comments. Login now