changeset 4534:cc9b79216a76

Split convert extension into common and repository type modules
author Brendan Cully <brendan@kublai.com>
date Sun, 10 Jun 2007 20:08:47 -0700
parents 36abb07c79d4
children 2f489b00f8eb
files hgext/convert/__init__.py hgext/convert/common.py hgext/convert/cvs.py hgext/convert/git.py hgext/convert/hg.py
diffstat 5 files changed, 18 insertions(+), 2993 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/__init__.py
@@ -5,523 +5,16 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import sys, os, zlib, sha, time, re, locale, socket
+from common import NoRepo
+from cvs import convert_cvs
+from git import convert_git
+from hg import convert_mercurial
+
+import os
 from mercurial import hg, ui, util, commands
 
 commands.norepo += " convert"
 
-class NoRepo(Exception): pass
-
-class commit(object):
-    def __init__(self, **parts):
-        for x in "author date desc parents".split():
-            if not x in parts:
-                raise util.Abort("commit missing field %s" % x)
-        self.__dict__.update(parts)
-
-def recode(s):
-    try:
-        return s.decode("utf-8").encode("utf-8")
-    except:
-        try:
-            return s.decode("latin-1").encode("utf-8")
-        except:
-            return s.decode("utf-8", "replace").encode("utf-8")
-
-class converter_source(object):
-    """Conversion source interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion source (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def getfile(self, name, rev):
-        """Return file contents as a string"""
-        raise NotImplementedError()
-
-    def getmode(self, name, rev):
-        """Return file mode, eg. '', 'x', or 'l'"""
-        raise NotImplementedError()
-
-    def getchanges(self, version):
-        """Return sorted list of (filename, id) tuples for all files changed in rev.
-
-        id just tells us which revision to return in getfile(), e.g. in
-        git it's an object hash."""
-        raise NotImplementedError()
-
-    def getcommit(self, version):
-        """Return the commit object for version"""
-        raise NotImplementedError()
-
-    def gettags(self):
-        """Return the tags as a dictionary of name: revision"""
-        raise NotImplementedError()
-
-class converter_sink(object):
-    """Conversion sink (target) interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion sink (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def mapfile(self):
-        """Path to a file that will contain lines
-        source_rev_id sink_rev_id
-        mapping equivalent revision identifiers for each system."""
-        raise NotImplementedError()
-
-    def putfile(self, f, e, data):
-        """Put file for next putcommit().
-        f: path to file
-        e: '', 'x', or 'l' (regular file, executable, or symlink)
-        data: file contents"""
-        raise NotImplementedError()
-
-    def delfile(self, f):
-        """Delete file for next putcommit().
-        f: path to file"""
-        raise NotImplementedError()
-
-    def putcommit(self, files, parents, commit):
-        """Create a revision with all changed files listed in 'files'
-        and having listed parents. 'commit' is a commit object containing
-        at a minimum the author, date, and message for this changeset.
-        Called after putfile() and delfile() calls. Note that the sink
-        repository is not told to update itself to a particular revision
-        (or even what that revision would be) before it receives the
-        file data."""
-        raise NotImplementedError()
-
-    def puttags(self, tags):
-        """Put tags into sink.
-        tags: {tagname: sink_rev_id, ...}"""
-        raise NotImplementedError()
-
-
-# CVS conversion code inspired by hg-cvs-import and git-cvsimport
-class convert_cvs(converter_source):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        cvs = os.path.join(path, "CVS")
-        if not os.path.exists(cvs):
-            raise NoRepo("couldn't open CVS repo %s" % path)
-
-        self.changeset = {}
-        self.files = {}
-        self.tags = {}
-        self.lastbranch = {}
-        self.parent = {}
-        self.socket = None
-        self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
-        self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
-        self.encoding = locale.getpreferredencoding()
-        self._parse()
-        self._connect()
-
-    def _parse(self):
-        if self.changeset:
-            return
-
-        d = os.getcwd()
-        try:
-            os.chdir(self.path)
-            id = None
-            state = 0
-            for l in os.popen("cvsps -A -u --cvs-direct -q"):
-                if state == 0: # header
-                    if l.startswith("PatchSet"):
-                        id = l[9:-2]
-                    elif l.startswith("Date"):
-                        date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
-                        date = util.datestr(date)
-                    elif l.startswith("Branch"):
-                        branch = l[8:-1]
-                        self.parent[id] = self.lastbranch.get(branch, 'bad')
-                        self.lastbranch[branch] = id
-                    elif l.startswith("Ancestor branch"):
-                        ancestor = l[17:-1]
-                        self.parent[id] = self.lastbranch[ancestor]
-                    elif l.startswith("Author"):
-                        author = self.recode(l[8:-1])
-                    elif l.startswith("Tag: "):
-                        t = l[5:-1].rstrip()
-                        if t != "(none)":
-                            self.tags[t] = id
-                    elif l.startswith("Log:"):
-                        state = 1
-                        log = ""
-                elif state == 1: # log
-                    if l == "Members: \n":
-                        files = {}
-                        log = self.recode(log[:-1])
-                        if log.isspace():
-                            log = "*** empty log message ***\n"
-                        state = 2
-                    else:
-                        log += l
-                elif state == 2:
-                    if l == "\n": #
-                        state = 0
-                        p = [self.parent[id]]
-                        if id == "1":
-                            p = []
-                        if branch == "HEAD":
-                            branch = ""
-                        c = commit(author=author, date=date, parents=p,
-                                   desc=log, branch=branch)
-                        self.changeset[id] = c
-                        self.files[id] = files
-                    else:
-                        colon = l.rfind(':')
-                        file = l[1:colon]
-                        rev = l[colon+1:-2]
-                        rev = rev.split("->")[1]
-                        files[file] = rev
-
-            self.heads = self.lastbranch.values()
-        finally:
-            os.chdir(d)
-
-    def _connect(self):
-        root = self.cvsroot
-        conntype = None
-        user, host = None, None
-        cmd = ['cvs', 'server']
-
-        self.ui.status("connecting to %s\n" % root)
-
-        if root.startswith(":pserver:"):
-            root = root[9:]
-            m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
-                         root)
-            if m:
-                conntype = "pserver"
-                user, passw, serv, port, root = m.groups()
-                if not user:
-                    user = "anonymous"
-                rr = ":pserver:" + user + "@" + serv + ":" +  root
-                if port:
-                    rr2, port = "-", int(port)
-                else:
-                    rr2, port = rr, 2401
-                rr += str(port)
-
-                if not passw:
-                    passw = "A"
-                    pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
-                    for l in pf:
-                        # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-                        m = re.match(r'(/\d+\s+/)?(.*)', l)
-                        l = m.group(2)
-                        w, p = l.split(' ', 1)
-                        if w in [rr, rr2]:
-                            passw = p
-                            break
-                    pf.close()
-
-                sck = socket.socket()
-                sck.connect((serv, port))
-                sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
-                                    "END AUTH REQUEST", ""]))
-                if sck.recv(128) != "I LOVE YOU\n":
-                    raise NoRepo("CVS pserver authentication failed")
-
-                self.writep = self.readp = sck.makefile('r+')
-
-        if not conntype and root.startswith(":local:"):
-            conntype = "local"
-            root = root[7:]
-
-        if not conntype:
-            # :ext:user@host/home/user/path/to/cvsroot
-            if root.startswith(":ext:"):
-                root = root[5:]
-            m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
-            if not m:
-                conntype = "local"
-            else:
-                conntype = "rsh"
-                user, host, root = m.group(1), m.group(2), m.group(3)
-
-        if conntype != "pserver":
-            if conntype == "rsh":
-                rsh = os.environ.get("CVS_RSH" or "rsh")
-                if user:
-                    cmd = [rsh, '-l', user, host] + cmd
-                else:
-                    cmd = [rsh, host] + cmd
-
-            self.writep, self.readp = os.popen2(cmd)
-
-        self.realroot = root
-
-        self.writep.write("Root %s\n" % root)
-        self.writep.write("Valid-responses ok error Valid-requests Mode"
-                          " M Mbinary E Checked-in Created Updated"
-                          " Merged Removed\n")
-        self.writep.write("valid-requests\n")
-        self.writep.flush()
-        r = self.readp.readline()
-        if not r.startswith("Valid-requests"):
-            raise util.Abort("server sucks")
-        if "UseUnchanged" in r:
-            self.writep.write("UseUnchanged\n")
-            self.writep.flush()
-            r = self.readp.readline()
-
-    def getheads(self):
-        return self.heads
-
-    def _getfile(self, name, rev):
-        if rev.endswith("(DEAD)"):
-            raise IOError
-
-        args = ("-N -P -kk -r %s --" % rev).split()
-        args.append(os.path.join(self.cvsrepo, name))
-        for x in args:
-            self.writep.write("Argument %s\n" % x)
-        self.writep.write("Directory .\n%s\nco\n" % self.realroot)
-        self.writep.flush()
-
-        data = ""
-        while 1:
-            line = self.readp.readline()
-            if line.startswith("Created ") or line.startswith("Updated "):
-                self.readp.readline() # path
-                self.readp.readline() # entries
-                mode = self.readp.readline()[:-1]
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            elif line.startswith(" "):
-                data += line[1:]
-            elif line.startswith("M "):
-                pass
-            elif line.startswith("Mbinary "):
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            else:
-                if line == "ok\n":
-                    return (data, "x" in mode and "x" or "")
-                elif line.startswith("E "):
-                    self.ui.warn("cvs server: %s\n" % line[2:])
-                elif line.startswith("Remove"):
-                    l = self.readp.readline()
-                    l = self.readp.readline()
-                    if l != "ok\n":
-                        raise util.Abort("unknown CVS response: %s" % l)
-                else:
-                    raise util.Abort("unknown CVS response: %s" % line)
-
-    def getfile(self, file, rev):
-        data, mode = self._getfile(file, rev)
-        self.modecache[(file, rev)] = mode
-        return data
-
-    def getmode(self, file, rev):
-        return self.modecache[(file, rev)]
-
-    def getchanges(self, rev):
-        self.modecache = {}
-        files = self.files[rev]
-        cl = files.items()
-        cl.sort()
-        return cl
-
-    def recode(self, text):
-        return text.decode(self.encoding, "replace").encode("utf-8")
-
-    def getcommit(self, rev):
-        return self.changeset[rev]
-
-    def gettags(self):
-        return self.tags
-
-class convert_git(converter_source):
-    def __init__(self, ui, path):
-        if os.path.isdir(path + "/.git"):
-            path += "/.git"
-        self.path = path
-        self.ui = ui
-        if not os.path.exists(path + "/objects"):
-            raise NoRepo("couldn't open GIT repo %s" % path)
-
-    def getheads(self):
-        fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
-        return [fh.read()[:-1]]
-
-    def catfile(self, rev, type):
-        if rev == "0" * 40: raise IOError()
-        fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
-                      % (self.path, type, rev))
-        return fh.read()
-
-    def getfile(self, name, rev):
-        return self.catfile(rev, "blob")
-
-    def getmode(self, name, rev):
-        return self.modecache[(name, rev)]
-
-    def getchanges(self, version):
-        self.modecache = {}
-        fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
-                      % (self.path, version))
-        changes = []
-        for l in fh:
-            if "\t" not in l: continue
-            m, f = l[:-1].split("\t")
-            m = m.split()
-            h = m[3]
-            p = (m[1] == "100755")
-            s = (m[1] == "120000")
-            self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
-            changes.append((f, h))
-        return changes
-
-    def getcommit(self, version):
-        c = self.catfile(version, "commit") # read the commit hash
-        end = c.find("\n\n")
-        message = c[end+2:]
-        message = recode(message)
-        l = c[:end].splitlines()
-        manifest = l[0].split()[1]
-        parents = []
-        for e in l[1:]:
-            n, v = e.split(" ", 1)
-            if n == "author":
-                p = v.split()
-                tm, tz = p[-2:]
-                author = " ".join(p[:-2])
-                if author[0] == "<": author = author[1:-1]
-                author = recode(author)
-            if n == "committer":
-                p = v.split()
-                tm, tz = p[-2:]
-                committer = " ".join(p[:-2])
-                if committer[0] == "<": committer = committer[1:-1]
-                committer = recode(committer)
-                message += "\ncommitter: %s\n" % committer
-            if n == "parent": parents.append(v)
-
-        tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
-        tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
-        date = tm + " " + str(tz)
-
-        c = commit(parents=parents, date=date, author=author, desc=message)
-        return c
-
-    def gettags(self):
-        tags = {}
-        fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
-        prefix = 'refs/tags/'
-        for line in fh:
-            line = line.strip()
-            if not line.endswith("^{}"):
-                continue
-            node, tag = line.split(None, 1)
-            if not tag.startswith(prefix):
-                continue
-            tag = tag[len(prefix):-3]
-            tags[tag] = node
-
-        return tags
-
-class convert_mercurial(converter_sink):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        try:
-            self.repo = hg.repository(self.ui, path)
-        except:
-            raise NoRepo("could open hg repo %s" % path)
-
-    def mapfile(self):
-        return os.path.join(self.path, ".hg", "shamap")
-
-    def getheads(self):
-        h = self.repo.changelog.heads()
-        return [ hg.hex(x) for x in h ]
-
-    def putfile(self, f, e, data):
-        self.repo.wwrite(f, data, e)
-        if self.repo.dirstate.state(f) == '?':
-            self.repo.dirstate.update([f], "a")
-
-    def delfile(self, f):
-        try:
-            os.unlink(self.repo.wjoin(f))
-            #self.repo.remove([f])
-        except:
-            pass
-
-    def putcommit(self, files, parents, commit):
-        seen = {}
-        pl = []
-        for p in parents:
-            if p not in seen:
-                pl.append(p)
-                seen[p] = 1
-        parents = pl
-
-        if len(parents) < 2: parents.append("0" * 40)
-        if len(parents) < 2: parents.append("0" * 40)
-        p2 = parents.pop(0)
-
-        text = commit.desc
-        extra = {}
-        try:
-            extra["branch"] = commit.branch
-        except AttributeError:
-            pass
-
-        while parents:
-            p1 = p2
-            p2 = parents.pop(0)
-            a = self.repo.rawcommit(files, text, commit.author, commit.date,
-                                    hg.bin(p1), hg.bin(p2), extra=extra)
-            text = "(octopus merge fixup)\n"
-            p2 = hg.hex(self.repo.changelog.tip())
-
-        return p2
-
-    def puttags(self, tags):
-        try:
-            old = self.repo.wfile(".hgtags").read()
-            oldlines = old.splitlines(1)
-            oldlines.sort()
-        except:
-            oldlines = []
-
-        k = tags.keys()
-        k.sort()
-        newlines = []
-        for tag in k:
-            newlines.append("%s %s\n" % (tags[tag], tag))
-
-        newlines.sort()
-
-        if newlines != oldlines:
-            self.ui.status("updating tags\n")
-            f = self.repo.wfile(".hgtags", "w")
-            f.write("".join(newlines))
-            f.close()
-            if not oldlines: self.repo.add([".hgtags"])
-            date = "%s 0" % int(time.mktime(time.gmtime()))
-            self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
-                                date, self.repo.changelog.tip(), hg.nullid)
-            return hg.hex(self.repo.changelog.tip())
-
 converters = [convert_cvs, convert_git, convert_mercurial]
 
 def converter(ui, path):
copy from hgext/convert/__init__.py
copy to hgext/convert/common.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/common.py
@@ -1,14 +1,4 @@
-# convert.py Foreign SCM converter
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-import sys, os, zlib, sha, time, re, locale, socket
-from mercurial import hg, ui, util, commands
-
-commands.norepo += " convert"
+# common code for the convert extension
 
 class NoRepo(Exception): pass
 
@@ -19,15 +9,6 @@ class commit(object):
                 raise util.Abort("commit missing field %s" % x)
         self.__dict__.update(parts)
 
-def recode(s):
-    try:
-        return s.decode("utf-8").encode("utf-8")
-    except:
-        try:
-            return s.decode("latin-1").encode("utf-8")
-        except:
-            return s.decode("utf-8", "replace").encode("utf-8")
-
 class converter_source(object):
     """Conversion source interface"""
 
@@ -107,643 +88,3 @@ class converter_sink(object):
         """Put tags into sink.
         tags: {tagname: sink_rev_id, ...}"""
         raise NotImplementedError()
-
-
-# CVS conversion code inspired by hg-cvs-import and git-cvsimport
-class convert_cvs(converter_source):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        cvs = os.path.join(path, "CVS")
-        if not os.path.exists(cvs):
-            raise NoRepo("couldn't open CVS repo %s" % path)
-
-        self.changeset = {}
-        self.files = {}
-        self.tags = {}
-        self.lastbranch = {}
-        self.parent = {}
-        self.socket = None
-        self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
-        self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
-        self.encoding = locale.getpreferredencoding()
-        self._parse()
-        self._connect()
-
-    def _parse(self):
-        if self.changeset:
-            return
-
-        d = os.getcwd()
-        try:
-            os.chdir(self.path)
-            id = None
-            state = 0
-            for l in os.popen("cvsps -A -u --cvs-direct -q"):
-                if state == 0: # header
-                    if l.startswith("PatchSet"):
-                        id = l[9:-2]
-                    elif l.startswith("Date"):
-                        date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
-                        date = util.datestr(date)
-                    elif l.startswith("Branch"):
-                        branch = l[8:-1]
-                        self.parent[id] = self.lastbranch.get(branch, 'bad')
-                        self.lastbranch[branch] = id
-                    elif l.startswith("Ancestor branch"):
-                        ancestor = l[17:-1]
-                        self.parent[id] = self.lastbranch[ancestor]
-                    elif l.startswith("Author"):
-                        author = self.recode(l[8:-1])
-                    elif l.startswith("Tag: "):
-                        t = l[5:-1].rstrip()
-                        if t != "(none)":
-                            self.tags[t] = id
-                    elif l.startswith("Log:"):
-                        state = 1
-                        log = ""
-                elif state == 1: # log
-                    if l == "Members: \n":
-                        files = {}
-                        log = self.recode(log[:-1])
-                        if log.isspace():
-                            log = "*** empty log message ***\n"
-                        state = 2
-                    else:
-                        log += l
-                elif state == 2:
-                    if l == "\n": #
-                        state = 0
-                        p = [self.parent[id]]
-                        if id == "1":
-                            p = []
-                        if branch == "HEAD":
-                            branch = ""
-                        c = commit(author=author, date=date, parents=p,
-                                   desc=log, branch=branch)
-                        self.changeset[id] = c
-                        self.files[id] = files
-                    else:
-                        colon = l.rfind(':')
-                        file = l[1:colon]
-                        rev = l[colon+1:-2]
-                        rev = rev.split("->")[1]
-                        files[file] = rev
-
-            self.heads = self.lastbranch.values()
-        finally:
-            os.chdir(d)
-
-    def _connect(self):
-        root = self.cvsroot
-        conntype = None
-        user, host = None, None
-        cmd = ['cvs', 'server']
-
-        self.ui.status("connecting to %s\n" % root)
-
-        if root.startswith(":pserver:"):
-            root = root[9:]
-            m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
-                         root)
-            if m:
-                conntype = "pserver"
-                user, passw, serv, port, root = m.groups()
-                if not user:
-                    user = "anonymous"
-                rr = ":pserver:" + user + "@" + serv + ":" +  root
-                if port:
-                    rr2, port = "-", int(port)
-                else:
-                    rr2, port = rr, 2401
-                rr += str(port)
-
-                if not passw:
-                    passw = "A"
-                    pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
-                    for l in pf:
-                        # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-                        m = re.match(r'(/\d+\s+/)?(.*)', l)
-                        l = m.group(2)
-                        w, p = l.split(' ', 1)
-                        if w in [rr, rr2]:
-                            passw = p
-                            break
-                    pf.close()
-
-                sck = socket.socket()
-                sck.connect((serv, port))
-                sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
-                                    "END AUTH REQUEST", ""]))
-                if sck.recv(128) != "I LOVE YOU\n":
-                    raise NoRepo("CVS pserver authentication failed")
-
-                self.writep = self.readp = sck.makefile('r+')
-
-        if not conntype and root.startswith(":local:"):
-            conntype = "local"
-            root = root[7:]
-
-        if not conntype:
-            # :ext:user@host/home/user/path/to/cvsroot
-            if root.startswith(":ext:"):
-                root = root[5:]
-            m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
-            if not m:
-                conntype = "local"
-            else:
-                conntype = "rsh"
-                user, host, root = m.group(1), m.group(2), m.group(3)
-
-        if conntype != "pserver":
-            if conntype == "rsh":
-                rsh = os.environ.get("CVS_RSH" or "rsh")
-                if user:
-                    cmd = [rsh, '-l', user, host] + cmd
-                else:
-                    cmd = [rsh, host] + cmd
-
-            self.writep, self.readp = os.popen2(cmd)
-
-        self.realroot = root
-
-        self.writep.write("Root %s\n" % root)
-        self.writep.write("Valid-responses ok error Valid-requests Mode"
-                          " M Mbinary E Checked-in Created Updated"
-                          " Merged Removed\n")
-        self.writep.write("valid-requests\n")
-        self.writep.flush()
-        r = self.readp.readline()
-        if not r.startswith("Valid-requests"):
-            raise util.Abort("server sucks")
-        if "UseUnchanged" in r:
-            self.writep.write("UseUnchanged\n")
-            self.writep.flush()
-            r = self.readp.readline()
-
-    def getheads(self):
-        return self.heads
-
-    def _getfile(self, name, rev):
-        if rev.endswith("(DEAD)"):
-            raise IOError
-
-        args = ("-N -P -kk -r %s --" % rev).split()
-        args.append(os.path.join(self.cvsrepo, name))
-        for x in args:
-            self.writep.write("Argument %s\n" % x)
-        self.writep.write("Directory .\n%s\nco\n" % self.realroot)
-        self.writep.flush()
-
-        data = ""
-        while 1:
-            line = self.readp.readline()
-            if line.startswith("Created ") or line.startswith("Updated "):
-                self.readp.readline() # path
-                self.readp.readline() # entries
-                mode = self.readp.readline()[:-1]
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            elif line.startswith(" "):
-                data += line[1:]
-            elif line.startswith("M "):
-                pass
-            elif line.startswith("Mbinary "):
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            else:
-                if line == "ok\n":
-                    return (data, "x" in mode and "x" or "")
-                elif line.startswith("E "):
-                    self.ui.warn("cvs server: %s\n" % line[2:])
-                elif line.startswith("Remove"):
-                    l = self.readp.readline()
-                    l = self.readp.readline()
-                    if l != "ok\n":
-                        raise util.Abort("unknown CVS response: %s" % l)
-                else:
-                    raise util.Abort("unknown CVS response: %s" % line)
-
-    def getfile(self, file, rev):
-        data, mode = self._getfile(file, rev)
-        self.modecache[(file, rev)] = mode
-        return data
-
-    def getmode(self, file, rev):
-        return self.modecache[(file, rev)]
-
-    def getchanges(self, rev):
-        self.modecache = {}
-        files = self.files[rev]
-        cl = files.items()
-        cl.sort()
-        return cl
-
-    def recode(self, text):
-        return text.decode(self.encoding, "replace").encode("utf-8")
-
-    def getcommit(self, rev):
-        return self.changeset[rev]
-
-    def gettags(self):
-        return self.tags
-
-class convert_git(converter_source):
-    def __init__(self, ui, path):
-        if os.path.isdir(path + "/.git"):
-            path += "/.git"
-        self.path = path
-        self.ui = ui
-        if not os.path.exists(path + "/objects"):
-            raise NoRepo("couldn't open GIT repo %s" % path)
-
-    def getheads(self):
-        fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
-        return [fh.read()[:-1]]
-
-    def catfile(self, rev, type):
-        if rev == "0" * 40: raise IOError()
-        fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
-                      % (self.path, type, rev))
-        return fh.read()
-
-    def getfile(self, name, rev):
-        return self.catfile(rev, "blob")
-
-    def getmode(self, name, rev):
-        return self.modecache[(name, rev)]
-
-    def getchanges(self, version):
-        self.modecache = {}
-        fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
-                      % (self.path, version))
-        changes = []
-        for l in fh:
-            if "\t" not in l: continue
-            m, f = l[:-1].split("\t")
-            m = m.split()
-            h = m[3]
-            p = (m[1] == "100755")
-            s = (m[1] == "120000")
-            self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
-            changes.append((f, h))
-        return changes
-
-    def getcommit(self, version):
-        c = self.catfile(version, "commit") # read the commit hash
-        end = c.find("\n\n")
-        message = c[end+2:]
-        message = recode(message)
-        l = c[:end].splitlines()
-        manifest = l[0].split()[1]
-        parents = []
-        for e in l[1:]:
-            n, v = e.split(" ", 1)
-            if n == "author":
-                p = v.split()
-                tm, tz = p[-2:]
-                author = " ".join(p[:-2])
-                if author[0] == "<": author = author[1:-1]
-                author = recode(author)
-            if n == "committer":
-                p = v.split()
-                tm, tz = p[-2:]
-                committer = " ".join(p[:-2])
-                if committer[0] == "<": committer = committer[1:-1]
-                committer = recode(committer)
-                message += "\ncommitter: %s\n" % committer
-            if n == "parent": parents.append(v)
-
-        tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
-        tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
-        date = tm + " " + str(tz)
-
-        c = commit(parents=parents, date=date, author=author, desc=message)
-        return c
-
-    def gettags(self):
-        tags = {}
-        fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
-        prefix = 'refs/tags/'
-        for line in fh:
-            line = line.strip()
-            if not line.endswith("^{}"):
-                continue
-            node, tag = line.split(None, 1)
-            if not tag.startswith(prefix):
-                continue
-            tag = tag[len(prefix):-3]
-            tags[tag] = node
-
-        return tags
-
-class convert_mercurial(converter_sink):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        try:
-            self.repo = hg.repository(self.ui, path)
-        except:
-            raise NoRepo("could open hg repo %s" % path)
-
-    def mapfile(self):
-        return os.path.join(self.path, ".hg", "shamap")
-
-    def getheads(self):
-        h = self.repo.changelog.heads()
-        return [ hg.hex(x) for x in h ]
-
-    def putfile(self, f, e, data):
-        self.repo.wwrite(f, data, e)
-        if self.repo.dirstate.state(f) == '?':
-            self.repo.dirstate.update([f], "a")
-
-    def delfile(self, f):
-        try:
-            os.unlink(self.repo.wjoin(f))
-            #self.repo.remove([f])
-        except:
-            pass
-
-    def putcommit(self, files, parents, commit):
-        seen = {}
-        pl = []
-        for p in parents:
-            if p not in seen:
-                pl.append(p)
-                seen[p] = 1
-        parents = pl
-
-        if len(parents) < 2: parents.append("0" * 40)
-        if len(parents) < 2: parents.append("0" * 40)
-        p2 = parents.pop(0)
-
-        text = commit.desc
-        extra = {}
-        try:
-            extra["branch"] = commit.branch
-        except AttributeError:
-            pass
-
-        while parents:
-            p1 = p2
-            p2 = parents.pop(0)
-            a = self.repo.rawcommit(files, text, commit.author, commit.date,
-                                    hg.bin(p1), hg.bin(p2), extra=extra)
-            text = "(octopus merge fixup)\n"
-            p2 = hg.hex(self.repo.changelog.tip())
-
-        return p2
-
-    def puttags(self, tags):
-        try:
-            old = self.repo.wfile(".hgtags").read()
-            oldlines = old.splitlines(1)
-            oldlines.sort()
-        except:
-            oldlines = []
-
-        k = tags.keys()
-        k.sort()
-        newlines = []
-        for tag in k:
-            newlines.append("%s %s\n" % (tags[tag], tag))
-
-        newlines.sort()
-
-        if newlines != oldlines:
-            self.ui.status("updating tags\n")
-            f = self.repo.wfile(".hgtags", "w")
-            f.write("".join(newlines))
-            f.close()
-            if not oldlines: self.repo.add([".hgtags"])
-            date = "%s 0" % int(time.mktime(time.gmtime()))
-            self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
-                                date, self.repo.changelog.tip(), hg.nullid)
-            return hg.hex(self.repo.changelog.tip())
-
-converters = [convert_cvs, convert_git, convert_mercurial]
-
-def converter(ui, path):
-    if not os.path.isdir(path):
-        raise util.Abort("%s: not a directory" % path)
-    for c in converters:
-        try:
-            return c(ui, path)
-        except NoRepo:
-            pass
-    raise util.Abort("%s: unknown repository type" % path)
-
-class convert(object):
-    def __init__(self, ui, source, dest, mapfile, opts):
-
-        self.source = source
-        self.dest = dest
-        self.ui = ui
-        self.mapfile = mapfile
-        self.opts = opts
-        self.commitcache = {}
-
-        self.map = {}
-        try:
-            for l in file(self.mapfile):
-                sv, dv = l[:-1].split()
-                self.map[sv] = dv
-        except IOError:
-            pass
-
-    def walktree(self, heads):
-        visit = heads
-        known = {}
-        parents = {}
-        while visit:
-            n = visit.pop(0)
-            if n in known or n in self.map: continue
-            known[n] = 1
-            self.commitcache[n] = self.source.getcommit(n)
-            cp = self.commitcache[n].parents
-            for p in cp:
-                parents.setdefault(n, []).append(p)
-                visit.append(p)
-
-        return parents
-
-    def toposort(self, parents):
-        visit = parents.keys()
-        seen = {}
-        children = {}
-
-        while visit:
-            n = visit.pop(0)
-            if n in seen: continue
-            seen[n] = 1
-            pc = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p not in self.map: pc += 1
-                    visit.append(p)
-                    children.setdefault(p, []).append(n)
-            if not pc: root = n
-
-        s = []
-        removed = {}
-        visit = children.keys()
-        while visit:
-            n = visit.pop(0)
-            if n in removed: continue
-            dep = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p in self.map: continue
-                    if p not in removed:
-                        # we're still dependent
-                        visit.append(n)
-                        dep = 1
-                        break
-
-            if not dep:
-                # all n's parents are in the list
-                removed[n] = 1
-                if n not in self.map:
-                    s.append(n)
-                if n in children:
-                    for c in children[n]:
-                        visit.insert(0, c)
-
-        if self.opts.get('datesort'):
-            depth = {}
-            for n in s:
-                depth[n] = 0
-                pl = [p for p in self.commitcache[n].parents
-                      if p not in self.map]
-                if pl:
-                    depth[n] = max([depth[p] for p in pl]) + 1
-
-            s = [(depth[n], self.commitcache[n].date, n) for n in s]
-            s.sort()
-            s = [e[2] for e in s]
-
-        return s
-
-    def copy(self, rev):
-        c = self.commitcache[rev]
-        files = self.source.getchanges(rev)
-
-        for f, v in files:
-            try:
-                data = self.source.getfile(f, v)
-            except IOError, inst:
-                self.dest.delfile(f)
-            else:
-                e = self.source.getmode(f, v)
-                self.dest.putfile(f, e, data)
-
-        r = [self.map[v] for v in c.parents]
-        f = [f for f, v in files]
-        self.map[rev] = self.dest.putcommit(f, r, c)
-        file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
-
-    def convert(self):
-        self.ui.status("scanning source...\n")
-        heads = self.source.getheads()
-        parents = self.walktree(heads)
-        self.ui.status("sorting...\n")
-        t = self.toposort(parents)
-        num = len(t)
-        c = None
-
-        self.ui.status("converting...\n")
-        for c in t:
-            num -= 1
-            desc = self.commitcache[c].desc
-            if "\n" in desc:
-                desc = desc.splitlines()[0]
-            self.ui.status("%d %s\n" % (num, desc))
-            self.copy(c)
-
-        tags = self.source.gettags()
-        ctags = {}
-        for k in tags:
-            v = tags[k]
-            if v in self.map:
-                ctags[k] = self.map[v]
-
-        if c and ctags:
-            nrev = self.dest.puttags(ctags)
-            # write another hash correspondence to override the previous
-            # one so we don't end up with extra tag heads
-            if nrev:
-                file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
-
-def _convert(ui, src, dest=None, mapfile=None, **opts):
-    '''Convert a foreign SCM repository to a Mercurial one.
-
-    Accepted source formats:
-    - GIT
-    - CVS
-
-    Accepted destination formats:
-    - Mercurial
-
-    If destination isn't given, a new Mercurial repo named <src>-hg will
-    be created. If <mapfile> isn't given, it will be put in a default
-    location (<dest>/.hg/shamap by default)
-
-    The <mapfile> is a simple text file that maps each source commit ID to
-    the destination ID for that revision, like so:
-
-    <source ID> <destination ID>
-
-    If the file doesn't exist, it's automatically created.  It's updated
-    on each commit copied, so convert-repo can be interrupted and can
-    be run repeatedly to copy new commits.
-    '''
-
-    srcc = converter(ui, src)
-    if not hasattr(srcc, "getcommit"):
-        raise util.Abort("%s: can't read from this repo type" % src)
-
-    if not dest:
-        dest = src + "-hg"
-        ui.status("assuming destination %s\n" % dest)
-
-    # Try to be smart and initalize things when required
-    if os.path.isdir(dest):
-        if len(os.listdir(dest)) > 0:
-            try:
-                hg.repository(ui, dest)
-                ui.status("destination %s is a Mercurial repository\n" % dest)
-            except hg.RepoError:
-                raise util.Abort(
-                    "destination directory %s is not empty.\n"
-                    "Please specify an empty directory to be initialized\n"
-                    "or an already initialized mercurial repository"
-                    % dest)
-        else:
-            ui.status("initializing destination %s repository\n" % dest)
-            hg.repository(ui, dest, create=True)
-    elif os.path.exists(dest):
-        raise util.Abort("destination %s exists and is not a directory" % dest)
-    else:
-        ui.status("initializing destination %s repository\n" % dest)
-        hg.repository(ui, dest, create=True)
-
-    destc = converter(ui, dest)
-    if not hasattr(destc, "putcommit"):
-        raise util.Abort("%s: can't write to this repo type" % src)
-
-    if not mapfile:
-        try:
-            mapfile = destc.mapfile()
-        except:
-            mapfile = os.path.join(destc, "map")
-
-    c = convert(ui, srcc, destc, mapfile, opts)
-    c.convert()
-
-cmdtable = {
-    "convert":
-        (_convert,
-         [('', 'datesort', None, 'try to sort changesets by date')],
-         'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
-}
copy from hgext/convert/__init__.py
copy to hgext/convert/cvs.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/cvs.py
@@ -1,115 +1,10 @@
-# convert.py Foreign SCM converter
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-import sys, os, zlib, sha, time, re, locale, socket
-from mercurial import hg, ui, util, commands
-
-commands.norepo += " convert"
-
-class NoRepo(Exception): pass
-
-class commit(object):
-    def __init__(self, **parts):
-        for x in "author date desc parents".split():
-            if not x in parts:
-                raise util.Abort("commit missing field %s" % x)
-        self.__dict__.update(parts)
-
-def recode(s):
-    try:
-        return s.decode("utf-8").encode("utf-8")
-    except:
-        try:
-            return s.decode("latin-1").encode("utf-8")
-        except:
-            return s.decode("utf-8", "replace").encode("utf-8")
-
-class converter_source(object):
-    """Conversion source interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion source (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def getfile(self, name, rev):
-        """Return file contents as a string"""
-        raise NotImplementedError()
-
-    def getmode(self, name, rev):
-        """Return file mode, eg. '', 'x', or 'l'"""
-        raise NotImplementedError()
-
-    def getchanges(self, version):
-        """Return sorted list of (filename, id) tuples for all files changed in rev.
+# CVS conversion code inspired by hg-cvs-import and git-cvsimport
 
-        id just tells us which revision to return in getfile(), e.g. in
-        git it's an object hash."""
-        raise NotImplementedError()
-
-    def getcommit(self, version):
-        """Return the commit object for version"""
-        raise NotImplementedError()
-
-    def gettags(self):
-        """Return the tags as a dictionary of name: revision"""
-        raise NotImplementedError()
-
-class converter_sink(object):
-    """Conversion sink (target) interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion sink (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def mapfile(self):
-        """Path to a file that will contain lines
-        source_rev_id sink_rev_id
-        mapping equivalent revision identifiers for each system."""
-        raise NotImplementedError()
+import os, locale, re, socket
+from mercurial import util
 
-    def putfile(self, f, e, data):
-        """Put file for next putcommit().
-        f: path to file
-        e: '', 'x', or 'l' (regular file, executable, or symlink)
-        data: file contents"""
-        raise NotImplementedError()
-
-    def delfile(self, f):
-        """Delete file for next putcommit().
-        f: path to file"""
-        raise NotImplementedError()
+from common import NoRepo, commit, converter_source
 
-    def putcommit(self, files, parents, commit):
-        """Create a revision with all changed files listed in 'files'
-        and having listed parents. 'commit' is a commit object containing
-        at a minimum the author, date, and message for this changeset.
-        Called after putfile() and delfile() calls. Note that the sink
-        repository is not told to update itself to a particular revision
-        (or even what that revision would be) before it receives the
-        file data."""
-        raise NotImplementedError()
-
-    def puttags(self, tags):
-        """Put tags into sink.
-        tags: {tagname: sink_rev_id, ...}"""
-        raise NotImplementedError()
-
-
-# CVS conversion code inspired by hg-cvs-import and git-cvsimport
 class convert_cvs(converter_source):
     def __init__(self, ui, path):
         self.path = path
@@ -347,403 +242,3 @@ class convert_cvs(converter_source):
 
     def gettags(self):
         return self.tags
-
-class convert_git(converter_source):
-    def __init__(self, ui, path):
-        if os.path.isdir(path + "/.git"):
-            path += "/.git"
-        self.path = path
-        self.ui = ui
-        if not os.path.exists(path + "/objects"):
-            raise NoRepo("couldn't open GIT repo %s" % path)
-
-    def getheads(self):
-        fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
-        return [fh.read()[:-1]]
-
-    def catfile(self, rev, type):
-        if rev == "0" * 40: raise IOError()
-        fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
-                      % (self.path, type, rev))
-        return fh.read()
-
-    def getfile(self, name, rev):
-        return self.catfile(rev, "blob")
-
-    def getmode(self, name, rev):
-        return self.modecache[(name, rev)]
-
-    def getchanges(self, version):
-        self.modecache = {}
-        fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
-                      % (self.path, version))
-        changes = []
-        for l in fh:
-            if "\t" not in l: continue
-            m, f = l[:-1].split("\t")
-            m = m.split()
-            h = m[3]
-            p = (m[1] == "100755")
-            s = (m[1] == "120000")
-            self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
-            changes.append((f, h))
-        return changes
-
-    def getcommit(self, version):
-        c = self.catfile(version, "commit") # read the commit hash
-        end = c.find("\n\n")
-        message = c[end+2:]
-        message = recode(message)
-        l = c[:end].splitlines()
-        manifest = l[0].split()[1]
-        parents = []
-        for e in l[1:]:
-            n, v = e.split(" ", 1)
-            if n == "author":
-                p = v.split()
-                tm, tz = p[-2:]
-                author = " ".join(p[:-2])
-                if author[0] == "<": author = author[1:-1]
-                author = recode(author)
-            if n == "committer":
-                p = v.split()
-                tm, tz = p[-2:]
-                committer = " ".join(p[:-2])
-                if committer[0] == "<": committer = committer[1:-1]
-                committer = recode(committer)
-                message += "\ncommitter: %s\n" % committer
-            if n == "parent": parents.append(v)
-
-        tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
-        tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
-        date = tm + " " + str(tz)
-
-        c = commit(parents=parents, date=date, author=author, desc=message)
-        return c
-
-    def gettags(self):
-        tags = {}
-        fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
-        prefix = 'refs/tags/'
-        for line in fh:
-            line = line.strip()
-            if not line.endswith("^{}"):
-                continue
-            node, tag = line.split(None, 1)
-            if not tag.startswith(prefix):
-                continue
-            tag = tag[len(prefix):-3]
-            tags[tag] = node
-
-        return tags
-
-class convert_mercurial(converter_sink):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        try:
-            self.repo = hg.repository(self.ui, path)
-        except:
-            raise NoRepo("could open hg repo %s" % path)
-
-    def mapfile(self):
-        return os.path.join(self.path, ".hg", "shamap")
-
-    def getheads(self):
-        h = self.repo.changelog.heads()
-        return [ hg.hex(x) for x in h ]
-
-    def putfile(self, f, e, data):
-        self.repo.wwrite(f, data, e)
-        if self.repo.dirstate.state(f) == '?':
-            self.repo.dirstate.update([f], "a")
-
-    def delfile(self, f):
-        try:
-            os.unlink(self.repo.wjoin(f))
-            #self.repo.remove([f])
-        except:
-            pass
-
-    def putcommit(self, files, parents, commit):
-        seen = {}
-        pl = []
-        for p in parents:
-            if p not in seen:
-                pl.append(p)
-                seen[p] = 1
-        parents = pl
-
-        if len(parents) < 2: parents.append("0" * 40)
-        if len(parents) < 2: parents.append("0" * 40)
-        p2 = parents.pop(0)
-
-        text = commit.desc
-        extra = {}
-        try:
-            extra["branch"] = commit.branch
-        except AttributeError:
-            pass
-
-        while parents:
-            p1 = p2
-            p2 = parents.pop(0)
-            a = self.repo.rawcommit(files, text, commit.author, commit.date,
-                                    hg.bin(p1), hg.bin(p2), extra=extra)
-            text = "(octopus merge fixup)\n"
-            p2 = hg.hex(self.repo.changelog.tip())
-
-        return p2
-
-    def puttags(self, tags):
-        try:
-            old = self.repo.wfile(".hgtags").read()
-            oldlines = old.splitlines(1)
-            oldlines.sort()
-        except:
-            oldlines = []
-
-        k = tags.keys()
-        k.sort()
-        newlines = []
-        for tag in k:
-            newlines.append("%s %s\n" % (tags[tag], tag))
-
-        newlines.sort()
-
-        if newlines != oldlines:
-            self.ui.status("updating tags\n")
-            f = self.repo.wfile(".hgtags", "w")
-            f.write("".join(newlines))
-            f.close()
-            if not oldlines: self.repo.add([".hgtags"])
-            date = "%s 0" % int(time.mktime(time.gmtime()))
-            self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
-                                date, self.repo.changelog.tip(), hg.nullid)
-            return hg.hex(self.repo.changelog.tip())
-
-converters = [convert_cvs, convert_git, convert_mercurial]
-
-def converter(ui, path):
-    if not os.path.isdir(path):
-        raise util.Abort("%s: not a directory" % path)
-    for c in converters:
-        try:
-            return c(ui, path)
-        except NoRepo:
-            pass
-    raise util.Abort("%s: unknown repository type" % path)
-
-class convert(object):
-    def __init__(self, ui, source, dest, mapfile, opts):
-
-        self.source = source
-        self.dest = dest
-        self.ui = ui
-        self.mapfile = mapfile
-        self.opts = opts
-        self.commitcache = {}
-
-        self.map = {}
-        try:
-            for l in file(self.mapfile):
-                sv, dv = l[:-1].split()
-                self.map[sv] = dv
-        except IOError:
-            pass
-
-    def walktree(self, heads):
-        visit = heads
-        known = {}
-        parents = {}
-        while visit:
-            n = visit.pop(0)
-            if n in known or n in self.map: continue
-            known[n] = 1
-            self.commitcache[n] = self.source.getcommit(n)
-            cp = self.commitcache[n].parents
-            for p in cp:
-                parents.setdefault(n, []).append(p)
-                visit.append(p)
-
-        return parents
-
-    def toposort(self, parents):
-        visit = parents.keys()
-        seen = {}
-        children = {}
-
-        while visit:
-            n = visit.pop(0)
-            if n in seen: continue
-            seen[n] = 1
-            pc = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p not in self.map: pc += 1
-                    visit.append(p)
-                    children.setdefault(p, []).append(n)
-            if not pc: root = n
-
-        s = []
-        removed = {}
-        visit = children.keys()
-        while visit:
-            n = visit.pop(0)
-            if n in removed: continue
-            dep = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p in self.map: continue
-                    if p not in removed:
-                        # we're still dependent
-                        visit.append(n)
-                        dep = 1
-                        break
-
-            if not dep:
-                # all n's parents are in the list
-                removed[n] = 1
-                if n not in self.map:
-                    s.append(n)
-                if n in children:
-                    for c in children[n]:
-                        visit.insert(0, c)
-
-        if self.opts.get('datesort'):
-            depth = {}
-            for n in s:
-                depth[n] = 0
-                pl = [p for p in self.commitcache[n].parents
-                      if p not in self.map]
-                if pl:
-                    depth[n] = max([depth[p] for p in pl]) + 1
-
-            s = [(depth[n], self.commitcache[n].date, n) for n in s]
-            s.sort()
-            s = [e[2] for e in s]
-
-        return s
-
-    def copy(self, rev):
-        c = self.commitcache[rev]
-        files = self.source.getchanges(rev)
-
-        for f, v in files:
-            try:
-                data = self.source.getfile(f, v)
-            except IOError, inst:
-                self.dest.delfile(f)
-            else:
-                e = self.source.getmode(f, v)
-                self.dest.putfile(f, e, data)
-
-        r = [self.map[v] for v in c.parents]
-        f = [f for f, v in files]
-        self.map[rev] = self.dest.putcommit(f, r, c)
-        file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
-
-    def convert(self):
-        self.ui.status("scanning source...\n")
-        heads = self.source.getheads()
-        parents = self.walktree(heads)
-        self.ui.status("sorting...\n")
-        t = self.toposort(parents)
-        num = len(t)
-        c = None
-
-        self.ui.status("converting...\n")
-        for c in t:
-            num -= 1
-            desc = self.commitcache[c].desc
-            if "\n" in desc:
-                desc = desc.splitlines()[0]
-            self.ui.status("%d %s\n" % (num, desc))
-            self.copy(c)
-
-        tags = self.source.gettags()
-        ctags = {}
-        for k in tags:
-            v = tags[k]
-            if v in self.map:
-                ctags[k] = self.map[v]
-
-        if c and ctags:
-            nrev = self.dest.puttags(ctags)
-            # write another hash correspondence to override the previous
-            # one so we don't end up with extra tag heads
-            if nrev:
-                file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
-
-def _convert(ui, src, dest=None, mapfile=None, **opts):
-    '''Convert a foreign SCM repository to a Mercurial one.
-
-    Accepted source formats:
-    - GIT
-    - CVS
-
-    Accepted destination formats:
-    - Mercurial
-
-    If destination isn't given, a new Mercurial repo named <src>-hg will
-    be created. If <mapfile> isn't given, it will be put in a default
-    location (<dest>/.hg/shamap by default)
-
-    The <mapfile> is a simple text file that maps each source commit ID to
-    the destination ID for that revision, like so:
-
-    <source ID> <destination ID>
-
-    If the file doesn't exist, it's automatically created.  It's updated
-    on each commit copied, so convert-repo can be interrupted and can
-    be run repeatedly to copy new commits.
-    '''
-
-    srcc = converter(ui, src)
-    if not hasattr(srcc, "getcommit"):
-        raise util.Abort("%s: can't read from this repo type" % src)
-
-    if not dest:
-        dest = src + "-hg"
-        ui.status("assuming destination %s\n" % dest)
-
-    # Try to be smart and initalize things when required
-    if os.path.isdir(dest):
-        if len(os.listdir(dest)) > 0:
-            try:
-                hg.repository(ui, dest)
-                ui.status("destination %s is a Mercurial repository\n" % dest)
-            except hg.RepoError:
-                raise util.Abort(
-                    "destination directory %s is not empty.\n"
-                    "Please specify an empty directory to be initialized\n"
-                    "or an already initialized mercurial repository"
-                    % dest)
-        else:
-            ui.status("initializing destination %s repository\n" % dest)
-            hg.repository(ui, dest, create=True)
-    elif os.path.exists(dest):
-        raise util.Abort("destination %s exists and is not a directory" % dest)
-    else:
-        ui.status("initializing destination %s repository\n" % dest)
-        hg.repository(ui, dest, create=True)
-
-    destc = converter(ui, dest)
-    if not hasattr(destc, "putcommit"):
-        raise util.Abort("%s: can't write to this repo type" % src)
-
-    if not mapfile:
-        try:
-            mapfile = destc.mapfile()
-        except:
-            mapfile = os.path.join(destc, "map")
-
-    c = convert(ui, srcc, destc, mapfile, opts)
-    c.convert()
-
-cmdtable = {
-    "convert":
-        (_convert,
-         [('', 'datesort', None, 'try to sort changesets by date')],
-         'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
-}
copy from hgext/convert/__init__.py
copy to hgext/convert/git.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/git.py
@@ -1,23 +1,8 @@
-# convert.py Foreign SCM converter
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-import sys, os, zlib, sha, time, re, locale, socket
-from mercurial import hg, ui, util, commands
+# git support for the convert extension
 
-commands.norepo += " convert"
-
-class NoRepo(Exception): pass
+import os
 
-class commit(object):
-    def __init__(self, **parts):
-        for x in "author date desc parents".split():
-            if not x in parts:
-                raise util.Abort("commit missing field %s" % x)
-        self.__dict__.update(parts)
+from common import NoRepo, commit, converter_source
 
 def recode(s):
     try:
@@ -28,326 +13,6 @@ def recode(s):
         except:
             return s.decode("utf-8", "replace").encode("utf-8")
 
-class converter_source(object):
-    """Conversion source interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion source (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def getfile(self, name, rev):
-        """Return file contents as a string"""
-        raise NotImplementedError()
-
-    def getmode(self, name, rev):
-        """Return file mode, eg. '', 'x', or 'l'"""
-        raise NotImplementedError()
-
-    def getchanges(self, version):
-        """Return sorted list of (filename, id) tuples for all files changed in rev.
-
-        id just tells us which revision to return in getfile(), e.g. in
-        git it's an object hash."""
-        raise NotImplementedError()
-
-    def getcommit(self, version):
-        """Return the commit object for version"""
-        raise NotImplementedError()
-
-    def gettags(self):
-        """Return the tags as a dictionary of name: revision"""
-        raise NotImplementedError()
-
-class converter_sink(object):
-    """Conversion sink (target) interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion sink (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def mapfile(self):
-        """Path to a file that will contain lines
-        source_rev_id sink_rev_id
-        mapping equivalent revision identifiers for each system."""
-        raise NotImplementedError()
-
-    def putfile(self, f, e, data):
-        """Put file for next putcommit().
-        f: path to file
-        e: '', 'x', or 'l' (regular file, executable, or symlink)
-        data: file contents"""
-        raise NotImplementedError()
-
-    def delfile(self, f):
-        """Delete file for next putcommit().
-        f: path to file"""
-        raise NotImplementedError()
-
-    def putcommit(self, files, parents, commit):
-        """Create a revision with all changed files listed in 'files'
-        and having listed parents. 'commit' is a commit object containing
-        at a minimum the author, date, and message for this changeset.
-        Called after putfile() and delfile() calls. Note that the sink
-        repository is not told to update itself to a particular revision
-        (or even what that revision would be) before it receives the
-        file data."""
-        raise NotImplementedError()
-
-    def puttags(self, tags):
-        """Put tags into sink.
-        tags: {tagname: sink_rev_id, ...}"""
-        raise NotImplementedError()
-
-
-# CVS conversion code inspired by hg-cvs-import and git-cvsimport
-class convert_cvs(converter_source):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        cvs = os.path.join(path, "CVS")
-        if not os.path.exists(cvs):
-            raise NoRepo("couldn't open CVS repo %s" % path)
-
-        self.changeset = {}
-        self.files = {}
-        self.tags = {}
-        self.lastbranch = {}
-        self.parent = {}
-        self.socket = None
-        self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
-        self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
-        self.encoding = locale.getpreferredencoding()
-        self._parse()
-        self._connect()
-
-    def _parse(self):
-        if self.changeset:
-            return
-
-        d = os.getcwd()
-        try:
-            os.chdir(self.path)
-            id = None
-            state = 0
-            for l in os.popen("cvsps -A -u --cvs-direct -q"):
-                if state == 0: # header
-                    if l.startswith("PatchSet"):
-                        id = l[9:-2]
-                    elif l.startswith("Date"):
-                        date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
-                        date = util.datestr(date)
-                    elif l.startswith("Branch"):
-                        branch = l[8:-1]
-                        self.parent[id] = self.lastbranch.get(branch, 'bad')
-                        self.lastbranch[branch] = id
-                    elif l.startswith("Ancestor branch"):
-                        ancestor = l[17:-1]
-                        self.parent[id] = self.lastbranch[ancestor]
-                    elif l.startswith("Author"):
-                        author = self.recode(l[8:-1])
-                    elif l.startswith("Tag: "):
-                        t = l[5:-1].rstrip()
-                        if t != "(none)":
-                            self.tags[t] = id
-                    elif l.startswith("Log:"):
-                        state = 1
-                        log = ""
-                elif state == 1: # log
-                    if l == "Members: \n":
-                        files = {}
-                        log = self.recode(log[:-1])
-                        if log.isspace():
-                            log = "*** empty log message ***\n"
-                        state = 2
-                    else:
-                        log += l
-                elif state == 2:
-                    if l == "\n": #
-                        state = 0
-                        p = [self.parent[id]]
-                        if id == "1":
-                            p = []
-                        if branch == "HEAD":
-                            branch = ""
-                        c = commit(author=author, date=date, parents=p,
-                                   desc=log, branch=branch)
-                        self.changeset[id] = c
-                        self.files[id] = files
-                    else:
-                        colon = l.rfind(':')
-                        file = l[1:colon]
-                        rev = l[colon+1:-2]
-                        rev = rev.split("->")[1]
-                        files[file] = rev
-
-            self.heads = self.lastbranch.values()
-        finally:
-            os.chdir(d)
-
-    def _connect(self):
-        root = self.cvsroot
-        conntype = None
-        user, host = None, None
-        cmd = ['cvs', 'server']
-
-        self.ui.status("connecting to %s\n" % root)
-
-        if root.startswith(":pserver:"):
-            root = root[9:]
-            m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
-                         root)
-            if m:
-                conntype = "pserver"
-                user, passw, serv, port, root = m.groups()
-                if not user:
-                    user = "anonymous"
-                rr = ":pserver:" + user + "@" + serv + ":" +  root
-                if port:
-                    rr2, port = "-", int(port)
-                else:
-                    rr2, port = rr, 2401
-                rr += str(port)
-
-                if not passw:
-                    passw = "A"
-                    pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
-                    for l in pf:
-                        # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-                        m = re.match(r'(/\d+\s+/)?(.*)', l)
-                        l = m.group(2)
-                        w, p = l.split(' ', 1)
-                        if w in [rr, rr2]:
-                            passw = p
-                            break
-                    pf.close()
-
-                sck = socket.socket()
-                sck.connect((serv, port))
-                sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
-                                    "END AUTH REQUEST", ""]))
-                if sck.recv(128) != "I LOVE YOU\n":
-                    raise NoRepo("CVS pserver authentication failed")
-
-                self.writep = self.readp = sck.makefile('r+')
-
-        if not conntype and root.startswith(":local:"):
-            conntype = "local"
-            root = root[7:]
-
-        if not conntype:
-            # :ext:user@host/home/user/path/to/cvsroot
-            if root.startswith(":ext:"):
-                root = root[5:]
-            m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
-            if not m:
-                conntype = "local"
-            else:
-                conntype = "rsh"
-                user, host, root = m.group(1), m.group(2), m.group(3)
-
-        if conntype != "pserver":
-            if conntype == "rsh":
-                rsh = os.environ.get("CVS_RSH" or "rsh")
-                if user:
-                    cmd = [rsh, '-l', user, host] + cmd
-                else:
-                    cmd = [rsh, host] + cmd
-
-            self.writep, self.readp = os.popen2(cmd)
-
-        self.realroot = root
-
-        self.writep.write("Root %s\n" % root)
-        self.writep.write("Valid-responses ok error Valid-requests Mode"
-                          " M Mbinary E Checked-in Created Updated"
-                          " Merged Removed\n")
-        self.writep.write("valid-requests\n")
-        self.writep.flush()
-        r = self.readp.readline()
-        if not r.startswith("Valid-requests"):
-            raise util.Abort("server sucks")
-        if "UseUnchanged" in r:
-            self.writep.write("UseUnchanged\n")
-            self.writep.flush()
-            r = self.readp.readline()
-
-    def getheads(self):
-        return self.heads
-
-    def _getfile(self, name, rev):
-        if rev.endswith("(DEAD)"):
-            raise IOError
-
-        args = ("-N -P -kk -r %s --" % rev).split()
-        args.append(os.path.join(self.cvsrepo, name))
-        for x in args:
-            self.writep.write("Argument %s\n" % x)
-        self.writep.write("Directory .\n%s\nco\n" % self.realroot)
-        self.writep.flush()
-
-        data = ""
-        while 1:
-            line = self.readp.readline()
-            if line.startswith("Created ") or line.startswith("Updated "):
-                self.readp.readline() # path
-                self.readp.readline() # entries
-                mode = self.readp.readline()[:-1]
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            elif line.startswith(" "):
-                data += line[1:]
-            elif line.startswith("M "):
-                pass
-            elif line.startswith("Mbinary "):
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            else:
-                if line == "ok\n":
-                    return (data, "x" in mode and "x" or "")
-                elif line.startswith("E "):
-                    self.ui.warn("cvs server: %s\n" % line[2:])
-                elif line.startswith("Remove"):
-                    l = self.readp.readline()
-                    l = self.readp.readline()
-                    if l != "ok\n":
-                        raise util.Abort("unknown CVS response: %s" % l)
-                else:
-                    raise util.Abort("unknown CVS response: %s" % line)
-
-    def getfile(self, file, rev):
-        data, mode = self._getfile(file, rev)
-        self.modecache[(file, rev)] = mode
-        return data
-
-    def getmode(self, file, rev):
-        return self.modecache[(file, rev)]
-
-    def getchanges(self, rev):
-        self.modecache = {}
-        files = self.files[rev]
-        cl = files.items()
-        cl.sort()
-        return cl
-
-    def recode(self, text):
-        return text.decode(self.encoding, "replace").encode("utf-8")
-
-    def getcommit(self, rev):
-        return self.changeset[rev]
-
-    def gettags(self):
-        return self.tags
-
 class convert_git(converter_source):
     def __init__(self, ui, path):
         if os.path.isdir(path + "/.git"):
@@ -436,314 +101,3 @@ class convert_git(converter_source):
             tags[tag] = node
 
         return tags
-
-class convert_mercurial(converter_sink):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        try:
-            self.repo = hg.repository(self.ui, path)
-        except:
-            raise NoRepo("could open hg repo %s" % path)
-
-    def mapfile(self):
-        return os.path.join(self.path, ".hg", "shamap")
-
-    def getheads(self):
-        h = self.repo.changelog.heads()
-        return [ hg.hex(x) for x in h ]
-
-    def putfile(self, f, e, data):
-        self.repo.wwrite(f, data, e)
-        if self.repo.dirstate.state(f) == '?':
-            self.repo.dirstate.update([f], "a")
-
-    def delfile(self, f):
-        try:
-            os.unlink(self.repo.wjoin(f))
-            #self.repo.remove([f])
-        except:
-            pass
-
-    def putcommit(self, files, parents, commit):
-        seen = {}
-        pl = []
-        for p in parents:
-            if p not in seen:
-                pl.append(p)
-                seen[p] = 1
-        parents = pl
-
-        if len(parents) < 2: parents.append("0" * 40)
-        if len(parents) < 2: parents.append("0" * 40)
-        p2 = parents.pop(0)
-
-        text = commit.desc
-        extra = {}
-        try:
-            extra["branch"] = commit.branch
-        except AttributeError:
-            pass
-
-        while parents:
-            p1 = p2
-            p2 = parents.pop(0)
-            a = self.repo.rawcommit(files, text, commit.author, commit.date,
-                                    hg.bin(p1), hg.bin(p2), extra=extra)
-            text = "(octopus merge fixup)\n"
-            p2 = hg.hex(self.repo.changelog.tip())
-
-        return p2
-
-    def puttags(self, tags):
-        try:
-            old = self.repo.wfile(".hgtags").read()
-            oldlines = old.splitlines(1)
-            oldlines.sort()
-        except:
-            oldlines = []
-
-        k = tags.keys()
-        k.sort()
-        newlines = []
-        for tag in k:
-            newlines.append("%s %s\n" % (tags[tag], tag))
-
-        newlines.sort()
-
-        if newlines != oldlines:
-            self.ui.status("updating tags\n")
-            f = self.repo.wfile(".hgtags", "w")
-            f.write("".join(newlines))
-            f.close()
-            if not oldlines: self.repo.add([".hgtags"])
-            date = "%s 0" % int(time.mktime(time.gmtime()))
-            self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
-                                date, self.repo.changelog.tip(), hg.nullid)
-            return hg.hex(self.repo.changelog.tip())
-
-converters = [convert_cvs, convert_git, convert_mercurial]
-
-def converter(ui, path):
-    if not os.path.isdir(path):
-        raise util.Abort("%s: not a directory" % path)
-    for c in converters:
-        try:
-            return c(ui, path)
-        except NoRepo:
-            pass
-    raise util.Abort("%s: unknown repository type" % path)
-
-class convert(object):
-    def __init__(self, ui, source, dest, mapfile, opts):
-
-        self.source = source
-        self.dest = dest
-        self.ui = ui
-        self.mapfile = mapfile
-        self.opts = opts
-        self.commitcache = {}
-
-        self.map = {}
-        try:
-            for l in file(self.mapfile):
-                sv, dv = l[:-1].split()
-                self.map[sv] = dv
-        except IOError:
-            pass
-
-    def walktree(self, heads):
-        visit = heads
-        known = {}
-        parents = {}
-        while visit:
-            n = visit.pop(0)
-            if n in known or n in self.map: continue
-            known[n] = 1
-            self.commitcache[n] = self.source.getcommit(n)
-            cp = self.commitcache[n].parents
-            for p in cp:
-                parents.setdefault(n, []).append(p)
-                visit.append(p)
-
-        return parents
-
-    def toposort(self, parents):
-        visit = parents.keys()
-        seen = {}
-        children = {}
-
-        while visit:
-            n = visit.pop(0)
-            if n in seen: continue
-            seen[n] = 1
-            pc = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p not in self.map: pc += 1
-                    visit.append(p)
-                    children.setdefault(p, []).append(n)
-            if not pc: root = n
-
-        s = []
-        removed = {}
-        visit = children.keys()
-        while visit:
-            n = visit.pop(0)
-            if n in removed: continue
-            dep = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p in self.map: continue
-                    if p not in removed:
-                        # we're still dependent
-                        visit.append(n)
-                        dep = 1
-                        break
-
-            if not dep:
-                # all n's parents are in the list
-                removed[n] = 1
-                if n not in self.map:
-                    s.append(n)
-                if n in children:
-                    for c in children[n]:
-                        visit.insert(0, c)
-
-        if self.opts.get('datesort'):
-            depth = {}
-            for n in s:
-                depth[n] = 0
-                pl = [p for p in self.commitcache[n].parents
-                      if p not in self.map]
-                if pl:
-                    depth[n] = max([depth[p] for p in pl]) + 1
-
-            s = [(depth[n], self.commitcache[n].date, n) for n in s]
-            s.sort()
-            s = [e[2] for e in s]
-
-        return s
-
-    def copy(self, rev):
-        c = self.commitcache[rev]
-        files = self.source.getchanges(rev)
-
-        for f, v in files:
-            try:
-                data = self.source.getfile(f, v)
-            except IOError, inst:
-                self.dest.delfile(f)
-            else:
-                e = self.source.getmode(f, v)
-                self.dest.putfile(f, e, data)
-
-        r = [self.map[v] for v in c.parents]
-        f = [f for f, v in files]
-        self.map[rev] = self.dest.putcommit(f, r, c)
-        file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
-
-    def convert(self):
-        self.ui.status("scanning source...\n")
-        heads = self.source.getheads()
-        parents = self.walktree(heads)
-        self.ui.status("sorting...\n")
-        t = self.toposort(parents)
-        num = len(t)
-        c = None
-
-        self.ui.status("converting...\n")
-        for c in t:
-            num -= 1
-            desc = self.commitcache[c].desc
-            if "\n" in desc:
-                desc = desc.splitlines()[0]
-            self.ui.status("%d %s\n" % (num, desc))
-            self.copy(c)
-
-        tags = self.source.gettags()
-        ctags = {}
-        for k in tags:
-            v = tags[k]
-            if v in self.map:
-                ctags[k] = self.map[v]
-
-        if c and ctags:
-            nrev = self.dest.puttags(ctags)
-            # write another hash correspondence to override the previous
-            # one so we don't end up with extra tag heads
-            if nrev:
-                file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
-
-def _convert(ui, src, dest=None, mapfile=None, **opts):
-    '''Convert a foreign SCM repository to a Mercurial one.
-
-    Accepted source formats:
-    - GIT
-    - CVS
-
-    Accepted destination formats:
-    - Mercurial
-
-    If destination isn't given, a new Mercurial repo named <src>-hg will
-    be created. If <mapfile> isn't given, it will be put in a default
-    location (<dest>/.hg/shamap by default)
-
-    The <mapfile> is a simple text file that maps each source commit ID to
-    the destination ID for that revision, like so:
-
-    <source ID> <destination ID>
-
-    If the file doesn't exist, it's automatically created.  It's updated
-    on each commit copied, so convert-repo can be interrupted and can
-    be run repeatedly to copy new commits.
-    '''
-
-    srcc = converter(ui, src)
-    if not hasattr(srcc, "getcommit"):
-        raise util.Abort("%s: can't read from this repo type" % src)
-
-    if not dest:
-        dest = src + "-hg"
-        ui.status("assuming destination %s\n" % dest)
-
-    # Try to be smart and initalize things when required
-    if os.path.isdir(dest):
-        if len(os.listdir(dest)) > 0:
-            try:
-                hg.repository(ui, dest)
-                ui.status("destination %s is a Mercurial repository\n" % dest)
-            except hg.RepoError:
-                raise util.Abort(
-                    "destination directory %s is not empty.\n"
-                    "Please specify an empty directory to be initialized\n"
-                    "or an already initialized mercurial repository"
-                    % dest)
-        else:
-            ui.status("initializing destination %s repository\n" % dest)
-            hg.repository(ui, dest, create=True)
-    elif os.path.exists(dest):
-        raise util.Abort("destination %s exists and is not a directory" % dest)
-    else:
-        ui.status("initializing destination %s repository\n" % dest)
-        hg.repository(ui, dest, create=True)
-
-    destc = converter(ui, dest)
-    if not hasattr(destc, "putcommit"):
-        raise util.Abort("%s: can't write to this repo type" % src)
-
-    if not mapfile:
-        try:
-            mapfile = destc.mapfile()
-        except:
-            mapfile = os.path.join(destc, "map")
-
-    c = convert(ui, srcc, destc, mapfile, opts)
-    c.convert()
-
-cmdtable = {
-    "convert":
-        (_convert,
-         [('', 'datesort', None, 'try to sort changesets by date')],
-         'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
-}
copy from hgext/convert/__init__.py
copy to hgext/convert/hg.py
--- a/hgext/convert/__init__.py
+++ b/hgext/convert/hg.py
@@ -1,441 +1,9 @@
-# convert.py Foreign SCM converter
-#
-# Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-import sys, os, zlib, sha, time, re, locale, socket
-from mercurial import hg, ui, util, commands
-
-commands.norepo += " convert"
-
-class NoRepo(Exception): pass
-
-class commit(object):
-    def __init__(self, **parts):
-        for x in "author date desc parents".split():
-            if not x in parts:
-                raise util.Abort("commit missing field %s" % x)
-        self.__dict__.update(parts)
-
-def recode(s):
-    try:
-        return s.decode("utf-8").encode("utf-8")
-    except:
-        try:
-            return s.decode("latin-1").encode("utf-8")
-        except:
-            return s.decode("utf-8", "replace").encode("utf-8")
-
-class converter_source(object):
-    """Conversion source interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion source (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def getfile(self, name, rev):
-        """Return file contents as a string"""
-        raise NotImplementedError()
-
-    def getmode(self, name, rev):
-        """Return file mode, eg. '', 'x', or 'l'"""
-        raise NotImplementedError()
-
-    def getchanges(self, version):
-        """Return sorted list of (filename, id) tuples for all files changed in rev.
-
-        id just tells us which revision to return in getfile(), e.g. in
-        git it's an object hash."""
-        raise NotImplementedError()
-
-    def getcommit(self, version):
-        """Return the commit object for version"""
-        raise NotImplementedError()
-
-    def gettags(self):
-        """Return the tags as a dictionary of name: revision"""
-        raise NotImplementedError()
-
-class converter_sink(object):
-    """Conversion sink (target) interface"""
-
-    def __init__(self, ui, path):
-        """Initialize conversion sink (or raise NoRepo("message")
-        exception if path is not a valid repository)"""
-        raise NotImplementedError()
-
-    def getheads(self):
-        """Return a list of this repository's heads"""
-        raise NotImplementedError()
-
-    def mapfile(self):
-        """Path to a file that will contain lines
-        source_rev_id sink_rev_id
-        mapping equivalent revision identifiers for each system."""
-        raise NotImplementedError()
-
-    def putfile(self, f, e, data):
-        """Put file for next putcommit().
-        f: path to file
-        e: '', 'x', or 'l' (regular file, executable, or symlink)
-        data: file contents"""
-        raise NotImplementedError()
-
-    def delfile(self, f):
-        """Delete file for next putcommit().
-        f: path to file"""
-        raise NotImplementedError()
-
-    def putcommit(self, files, parents, commit):
-        """Create a revision with all changed files listed in 'files'
-        and having listed parents. 'commit' is a commit object containing
-        at a minimum the author, date, and message for this changeset.
-        Called after putfile() and delfile() calls. Note that the sink
-        repository is not told to update itself to a particular revision
-        (or even what that revision would be) before it receives the
-        file data."""
-        raise NotImplementedError()
-
-    def puttags(self, tags):
-        """Put tags into sink.
-        tags: {tagname: sink_rev_id, ...}"""
-        raise NotImplementedError()
-
-
-# CVS conversion code inspired by hg-cvs-import and git-cvsimport
-class convert_cvs(converter_source):
-    def __init__(self, ui, path):
-        self.path = path
-        self.ui = ui
-        cvs = os.path.join(path, "CVS")
-        if not os.path.exists(cvs):
-            raise NoRepo("couldn't open CVS repo %s" % path)
-
-        self.changeset = {}
-        self.files = {}
-        self.tags = {}
-        self.lastbranch = {}
-        self.parent = {}
-        self.socket = None
-        self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
-        self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
-        self.encoding = locale.getpreferredencoding()
-        self._parse()
-        self._connect()
-
-    def _parse(self):
-        if self.changeset:
-            return
-
-        d = os.getcwd()
-        try:
-            os.chdir(self.path)
-            id = None
-            state = 0
-            for l in os.popen("cvsps -A -u --cvs-direct -q"):
-                if state == 0: # header
-                    if l.startswith("PatchSet"):
-                        id = l[9:-2]
-                    elif l.startswith("Date"):
-                        date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
-                        date = util.datestr(date)
-                    elif l.startswith("Branch"):
-                        branch = l[8:-1]
-                        self.parent[id] = self.lastbranch.get(branch, 'bad')
-                        self.lastbranch[branch] = id
-                    elif l.startswith("Ancestor branch"):
-                        ancestor = l[17:-1]
-                        self.parent[id] = self.lastbranch[ancestor]
-                    elif l.startswith("Author"):
-                        author = self.recode(l[8:-1])
-                    elif l.startswith("Tag: "):
-                        t = l[5:-1].rstrip()
-                        if t != "(none)":
-                            self.tags[t] = id
-                    elif l.startswith("Log:"):
-                        state = 1
-                        log = ""
-                elif state == 1: # log
-                    if l == "Members: \n":
-                        files = {}
-                        log = self.recode(log[:-1])
-                        if log.isspace():
-                            log = "*** empty log message ***\n"
-                        state = 2
-                    else:
-                        log += l
-                elif state == 2:
-                    if l == "\n": #
-                        state = 0
-                        p = [self.parent[id]]
-                        if id == "1":
-                            p = []
-                        if branch == "HEAD":
-                            branch = ""
-                        c = commit(author=author, date=date, parents=p,
-                                   desc=log, branch=branch)
-                        self.changeset[id] = c
-                        self.files[id] = files
-                    else:
-                        colon = l.rfind(':')
-                        file = l[1:colon]
-                        rev = l[colon+1:-2]
-                        rev = rev.split("->")[1]
-                        files[file] = rev
-
-            self.heads = self.lastbranch.values()
-        finally:
-            os.chdir(d)
-
-    def _connect(self):
-        root = self.cvsroot
-        conntype = None
-        user, host = None, None
-        cmd = ['cvs', 'server']
-
-        self.ui.status("connecting to %s\n" % root)
+# hg backend for convert extension
 
-        if root.startswith(":pserver:"):
-            root = root[9:]
-            m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
-                         root)
-            if m:
-                conntype = "pserver"
-                user, passw, serv, port, root = m.groups()
-                if not user:
-                    user = "anonymous"
-                rr = ":pserver:" + user + "@" + serv + ":" +  root
-                if port:
-                    rr2, port = "-", int(port)
-                else:
-                    rr2, port = rr, 2401
-                rr += str(port)
-
-                if not passw:
-                    passw = "A"
-                    pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
-                    for l in pf:
-                        # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
-                        m = re.match(r'(/\d+\s+/)?(.*)', l)
-                        l = m.group(2)
-                        w, p = l.split(' ', 1)
-                        if w in [rr, rr2]:
-                            passw = p
-                            break
-                    pf.close()
-
-                sck = socket.socket()
-                sck.connect((serv, port))
-                sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
-                                    "END AUTH REQUEST", ""]))
-                if sck.recv(128) != "I LOVE YOU\n":
-                    raise NoRepo("CVS pserver authentication failed")
-
-                self.writep = self.readp = sck.makefile('r+')
-
-        if not conntype and root.startswith(":local:"):
-            conntype = "local"
-            root = root[7:]
-
-        if not conntype:
-            # :ext:user@host/home/user/path/to/cvsroot
-            if root.startswith(":ext:"):
-                root = root[5:]
-            m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
-            if not m:
-                conntype = "local"
-            else:
-                conntype = "rsh"
-                user, host, root = m.group(1), m.group(2), m.group(3)
-
-        if conntype != "pserver":
-            if conntype == "rsh":
-                rsh = os.environ.get("CVS_RSH" or "rsh")
-                if user:
-                    cmd = [rsh, '-l', user, host] + cmd
-                else:
-                    cmd = [rsh, host] + cmd
-
-            self.writep, self.readp = os.popen2(cmd)
-
-        self.realroot = root
-
-        self.writep.write("Root %s\n" % root)
-        self.writep.write("Valid-responses ok error Valid-requests Mode"
-                          " M Mbinary E Checked-in Created Updated"
-                          " Merged Removed\n")
-        self.writep.write("valid-requests\n")
-        self.writep.flush()
-        r = self.readp.readline()
-        if not r.startswith("Valid-requests"):
-            raise util.Abort("server sucks")
-        if "UseUnchanged" in r:
-            self.writep.write("UseUnchanged\n")
-            self.writep.flush()
-            r = self.readp.readline()
-
-    def getheads(self):
-        return self.heads
-
-    def _getfile(self, name, rev):
-        if rev.endswith("(DEAD)"):
-            raise IOError
-
-        args = ("-N -P -kk -r %s --" % rev).split()
-        args.append(os.path.join(self.cvsrepo, name))
-        for x in args:
-            self.writep.write("Argument %s\n" % x)
-        self.writep.write("Directory .\n%s\nco\n" % self.realroot)
-        self.writep.flush()
+import os, time
+from mercurial import hg
 
-        data = ""
-        while 1:
-            line = self.readp.readline()
-            if line.startswith("Created ") or line.startswith("Updated "):
-                self.readp.readline() # path
-                self.readp.readline() # entries
-                mode = self.readp.readline()[:-1]
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            elif line.startswith(" "):
-                data += line[1:]
-            elif line.startswith("M "):
-                pass
-            elif line.startswith("Mbinary "):
-                count = int(self.readp.readline()[:-1])
-                data = self.readp.read(count)
-            else:
-                if line == "ok\n":
-                    return (data, "x" in mode and "x" or "")
-                elif line.startswith("E "):
-                    self.ui.warn("cvs server: %s\n" % line[2:])
-                elif line.startswith("Remove"):
-                    l = self.readp.readline()
-                    l = self.readp.readline()
-                    if l != "ok\n":
-                        raise util.Abort("unknown CVS response: %s" % l)
-                else:
-                    raise util.Abort("unknown CVS response: %s" % line)
-
-    def getfile(self, file, rev):
-        data, mode = self._getfile(file, rev)
-        self.modecache[(file, rev)] = mode
-        return data
-
-    def getmode(self, file, rev):
-        return self.modecache[(file, rev)]
-
-    def getchanges(self, rev):
-        self.modecache = {}
-        files = self.files[rev]
-        cl = files.items()
-        cl.sort()
-        return cl
-
-    def recode(self, text):
-        return text.decode(self.encoding, "replace").encode("utf-8")
-
-    def getcommit(self, rev):
-        return self.changeset[rev]
-
-    def gettags(self):
-        return self.tags
-
-class convert_git(converter_source):
-    def __init__(self, ui, path):
-        if os.path.isdir(path + "/.git"):
-            path += "/.git"
-        self.path = path
-        self.ui = ui
-        if not os.path.exists(path + "/objects"):
-            raise NoRepo("couldn't open GIT repo %s" % path)
-
-    def getheads(self):
-        fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path)
-        return [fh.read()[:-1]]
-
-    def catfile(self, rev, type):
-        if rev == "0" * 40: raise IOError()
-        fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null"
-                      % (self.path, type, rev))
-        return fh.read()
-
-    def getfile(self, name, rev):
-        return self.catfile(rev, "blob")
-
-    def getmode(self, name, rev):
-        return self.modecache[(name, rev)]
-
-    def getchanges(self, version):
-        self.modecache = {}
-        fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s"
-                      % (self.path, version))
-        changes = []
-        for l in fh:
-            if "\t" not in l: continue
-            m, f = l[:-1].split("\t")
-            m = m.split()
-            h = m[3]
-            p = (m[1] == "100755")
-            s = (m[1] == "120000")
-            self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
-            changes.append((f, h))
-        return changes
-
-    def getcommit(self, version):
-        c = self.catfile(version, "commit") # read the commit hash
-        end = c.find("\n\n")
-        message = c[end+2:]
-        message = recode(message)
-        l = c[:end].splitlines()
-        manifest = l[0].split()[1]
-        parents = []
-        for e in l[1:]:
-            n, v = e.split(" ", 1)
-            if n == "author":
-                p = v.split()
-                tm, tz = p[-2:]
-                author = " ".join(p[:-2])
-                if author[0] == "<": author = author[1:-1]
-                author = recode(author)
-            if n == "committer":
-                p = v.split()
-                tm, tz = p[-2:]
-                committer = " ".join(p[:-2])
-                if committer[0] == "<": committer = committer[1:-1]
-                committer = recode(committer)
-                message += "\ncommitter: %s\n" % committer
-            if n == "parent": parents.append(v)
-
-        tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
-        tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
-        date = tm + " " + str(tz)
-
-        c = commit(parents=parents, date=date, author=author, desc=message)
-        return c
-
-    def gettags(self):
-        tags = {}
-        fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
-        prefix = 'refs/tags/'
-        for line in fh:
-            line = line.strip()
-            if not line.endswith("^{}"):
-                continue
-            node, tag = line.split(None, 1)
-            if not tag.startswith(prefix):
-                continue
-            tag = tag[len(prefix):-3]
-            tags[tag] = node
-
-        return tags
+from common import NoRepo, converter_sink
 
 class convert_mercurial(converter_sink):
     def __init__(self, ui, path):
@@ -521,229 +89,3 @@ class convert_mercurial(converter_sink):
             self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
                                 date, self.repo.changelog.tip(), hg.nullid)
             return hg.hex(self.repo.changelog.tip())
-
-converters = [convert_cvs, convert_git, convert_mercurial]
-
-def converter(ui, path):
-    if not os.path.isdir(path):
-        raise util.Abort("%s: not a directory" % path)
-    for c in converters:
-        try:
-            return c(ui, path)
-        except NoRepo:
-            pass
-    raise util.Abort("%s: unknown repository type" % path)
-
-class convert(object):
-    def __init__(self, ui, source, dest, mapfile, opts):
-
-        self.source = source
-        self.dest = dest
-        self.ui = ui
-        self.mapfile = mapfile
-        self.opts = opts
-        self.commitcache = {}
-
-        self.map = {}
-        try:
-            for l in file(self.mapfile):
-                sv, dv = l[:-1].split()
-                self.map[sv] = dv
-        except IOError:
-            pass
-
-    def walktree(self, heads):
-        visit = heads
-        known = {}
-        parents = {}
-        while visit:
-            n = visit.pop(0)
-            if n in known or n in self.map: continue
-            known[n] = 1
-            self.commitcache[n] = self.source.getcommit(n)
-            cp = self.commitcache[n].parents
-            for p in cp:
-                parents.setdefault(n, []).append(p)
-                visit.append(p)
-
-        return parents
-
-    def toposort(self, parents):
-        visit = parents.keys()
-        seen = {}
-        children = {}
-
-        while visit:
-            n = visit.pop(0)
-            if n in seen: continue
-            seen[n] = 1
-            pc = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p not in self.map: pc += 1
-                    visit.append(p)
-                    children.setdefault(p, []).append(n)
-            if not pc: root = n
-
-        s = []
-        removed = {}
-        visit = children.keys()
-        while visit:
-            n = visit.pop(0)
-            if n in removed: continue
-            dep = 0
-            if n in parents:
-                for p in parents[n]:
-                    if p in self.map: continue
-                    if p not in removed:
-                        # we're still dependent
-                        visit.append(n)
-                        dep = 1
-                        break
-
-            if not dep:
-                # all n's parents are in the list
-                removed[n] = 1
-                if n not in self.map:
-                    s.append(n)
-                if n in children:
-                    for c in children[n]:
-                        visit.insert(0, c)
-
-        if self.opts.get('datesort'):
-            depth = {}
-            for n in s:
-                depth[n] = 0
-                pl = [p for p in self.commitcache[n].parents
-                      if p not in self.map]
-                if pl:
-                    depth[n] = max([depth[p] for p in pl]) + 1
-
-            s = [(depth[n], self.commitcache[n].date, n) for n in s]
-            s.sort()
-            s = [e[2] for e in s]
-
-        return s
-
-    def copy(self, rev):
-        c = self.commitcache[rev]
-        files = self.source.getchanges(rev)
-
-        for f, v in files:
-            try:
-                data = self.source.getfile(f, v)
-            except IOError, inst:
-                self.dest.delfile(f)
-            else:
-                e = self.source.getmode(f, v)
-                self.dest.putfile(f, e, data)
-
-        r = [self.map[v] for v in c.parents]
-        f = [f for f, v in files]
-        self.map[rev] = self.dest.putcommit(f, r, c)
-        file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev]))
-
-    def convert(self):
-        self.ui.status("scanning source...\n")
-        heads = self.source.getheads()
-        parents = self.walktree(heads)
-        self.ui.status("sorting...\n")
-        t = self.toposort(parents)
-        num = len(t)
-        c = None
-
-        self.ui.status("converting...\n")
-        for c in t:
-            num -= 1
-            desc = self.commitcache[c].desc
-            if "\n" in desc:
-                desc = desc.splitlines()[0]
-            self.ui.status("%d %s\n" % (num, desc))
-            self.copy(c)
-
-        tags = self.source.gettags()
-        ctags = {}
-        for k in tags:
-            v = tags[k]
-            if v in self.map:
-                ctags[k] = self.map[v]
-
-        if c and ctags:
-            nrev = self.dest.puttags(ctags)
-            # write another hash correspondence to override the previous
-            # one so we don't end up with extra tag heads
-            if nrev:
-                file(self.mapfile, "a").write("%s %s\n" % (c, nrev))
-
-def _convert(ui, src, dest=None, mapfile=None, **opts):
-    '''Convert a foreign SCM repository to a Mercurial one.
-
-    Accepted source formats:
-    - GIT
-    - CVS
-
-    Accepted destination formats:
-    - Mercurial
-
-    If destination isn't given, a new Mercurial repo named <src>-hg will
-    be created. If <mapfile> isn't given, it will be put in a default
-    location (<dest>/.hg/shamap by default)
-
-    The <mapfile> is a simple text file that maps each source commit ID to
-    the destination ID for that revision, like so:
-
-    <source ID> <destination ID>
-
-    If the file doesn't exist, it's automatically created.  It's updated
-    on each commit copied, so convert-repo can be interrupted and can
-    be run repeatedly to copy new commits.
-    '''
-
-    srcc = converter(ui, src)
-    if not hasattr(srcc, "getcommit"):
-        raise util.Abort("%s: can't read from this repo type" % src)
-
-    if not dest:
-        dest = src + "-hg"
-        ui.status("assuming destination %s\n" % dest)
-
-    # Try to be smart and initalize things when required
-    if os.path.isdir(dest):
-        if len(os.listdir(dest)) > 0:
-            try:
-                hg.repository(ui, dest)
-                ui.status("destination %s is a Mercurial repository\n" % dest)
-            except hg.RepoError:
-                raise util.Abort(
-                    "destination directory %s is not empty.\n"
-                    "Please specify an empty directory to be initialized\n"
-                    "or an already initialized mercurial repository"
-                    % dest)
-        else:
-            ui.status("initializing destination %s repository\n" % dest)
-            hg.repository(ui, dest, create=True)
-    elif os.path.exists(dest):
-        raise util.Abort("destination %s exists and is not a directory" % dest)
-    else:
-        ui.status("initializing destination %s repository\n" % dest)
-        hg.repository(ui, dest, create=True)
-
-    destc = converter(ui, dest)
-    if not hasattr(destc, "putcommit"):
-        raise util.Abort("%s: can't write to this repo type" % src)
-
-    if not mapfile:
-        try:
-            mapfile = destc.mapfile()
-        except:
-            mapfile = os.path.join(destc, "map")
-
-    c = convert(ui, srcc, destc, mapfile, opts)
-    c.convert()
-
-cmdtable = {
-    "convert":
-        (_convert,
-         [('', 'datesort', None, 'try to sort changesets by date')],
-         'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
-}