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 |
|
|
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 |
|
|
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