hgext/convert/cvs.py
changeset 4536 cc9b79216a76
parent 4532 c3a78a49d7f0
child 4698 30e826bd8ed1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/convert/cvs.py	Sun Jun 10 20:08:47 2007 -0700
@@ -0,0 +1,244 @@
+# CVS conversion code inspired by hg-cvs-import and git-cvsimport
+
+import os, locale, re, socket
+from mercurial import util
+
+from common import NoRepo, commit, converter_source
+
+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