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