eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/convert/cvs.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # cvs.py: CVS conversion code inspired by hg-cvs-import and git-cvsimport
       
     2 #
       
     3 #  Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 import os, re, socket, errno
       
     9 from cStringIO import StringIO
       
    10 from mercurial import encoding, util
       
    11 from mercurial.i18n import _
       
    12 
       
    13 from common import NoRepo, commit, converter_source, checktool
       
    14 import cvsps
       
    15 
       
    16 class convert_cvs(converter_source):
       
    17     def __init__(self, ui, path, rev=None):
       
    18         super(convert_cvs, self).__init__(ui, path, rev=rev)
       
    19 
       
    20         cvs = os.path.join(path, "CVS")
       
    21         if not os.path.exists(cvs):
       
    22             raise NoRepo(_("%s does not look like a CVS checkout") % path)
       
    23 
       
    24         checktool('cvs')
       
    25 
       
    26         self.changeset = None
       
    27         self.files = {}
       
    28         self.tags = {}
       
    29         self.lastbranch = {}
       
    30         self.socket = None
       
    31         self.cvsroot = open(os.path.join(cvs, "Root")).read()[:-1]
       
    32         self.cvsrepo = open(os.path.join(cvs, "Repository")).read()[:-1]
       
    33         self.encoding = encoding.encoding
       
    34 
       
    35         self._connect()
       
    36 
       
    37     def _parse(self):
       
    38         if self.changeset is not None:
       
    39             return
       
    40         self.changeset = {}
       
    41 
       
    42         maxrev = 0
       
    43         if self.rev:
       
    44             # TODO: handle tags
       
    45             try:
       
    46                 # patchset number?
       
    47                 maxrev = int(self.rev)
       
    48             except ValueError:
       
    49                 raise util.Abort(_('revision %s is not a patchset number')
       
    50                                  % self.rev)
       
    51 
       
    52         d = os.getcwd()
       
    53         try:
       
    54             os.chdir(self.path)
       
    55             id = None
       
    56 
       
    57             cache = 'update'
       
    58             if not self.ui.configbool('convert', 'cvsps.cache', True):
       
    59                 cache = None
       
    60             db = cvsps.createlog(self.ui, cache=cache)
       
    61             db = cvsps.createchangeset(self.ui, db,
       
    62                 fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
       
    63                 mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
       
    64                 mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
       
    65 
       
    66             for cs in db:
       
    67                 if maxrev and cs.id > maxrev:
       
    68                     break
       
    69                 id = str(cs.id)
       
    70                 cs.author = self.recode(cs.author)
       
    71                 self.lastbranch[cs.branch] = id
       
    72                 cs.comment = self.recode(cs.comment)
       
    73                 date = util.datestr(cs.date)
       
    74                 self.tags.update(dict.fromkeys(cs.tags, id))
       
    75 
       
    76                 files = {}
       
    77                 for f in cs.entries:
       
    78                     files[f.file] = "%s%s" % ('.'.join([str(x)
       
    79                                                         for x in f.revision]),
       
    80                                               ['', '(DEAD)'][f.dead])
       
    81 
       
    82                 # add current commit to set
       
    83                 c = commit(author=cs.author, date=date,
       
    84                            parents=[str(p.id) for p in cs.parents],
       
    85                            desc=cs.comment, branch=cs.branch or '')
       
    86                 self.changeset[id] = c
       
    87                 self.files[id] = files
       
    88 
       
    89             self.heads = self.lastbranch.values()
       
    90         finally:
       
    91             os.chdir(d)
       
    92 
       
    93     def _connect(self):
       
    94         root = self.cvsroot
       
    95         conntype = None
       
    96         user, host = None, None
       
    97         cmd = ['cvs', 'server']
       
    98 
       
    99         self.ui.status(_("connecting to %s\n") % root)
       
   100 
       
   101         if root.startswith(":pserver:"):
       
   102             root = root[9:]
       
   103             m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
       
   104                          root)
       
   105             if m:
       
   106                 conntype = "pserver"
       
   107                 user, passw, serv, port, root = m.groups()
       
   108                 if not user:
       
   109                     user = "anonymous"
       
   110                 if not port:
       
   111                     port = 2401
       
   112                 else:
       
   113                     port = int(port)
       
   114                 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
       
   115                 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
       
   116 
       
   117                 if not passw:
       
   118                     passw = "A"
       
   119                     cvspass = os.path.expanduser("~/.cvspass")
       
   120                     try:
       
   121                         pf = open(cvspass)
       
   122                         for line in pf.read().splitlines():
       
   123                             part1, part2 = line.split(' ', 1)
       
   124                             if part1 == '/1':
       
   125                                 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
       
   126                                 part1, part2 = part2.split(' ', 1)
       
   127                                 format = format1
       
   128                             else:
       
   129                                 # :pserver:user@example.com:/cvsroot/foo Ah<Z
       
   130                                 format = format0
       
   131                             if part1 == format:
       
   132                                 passw = part2
       
   133                                 break
       
   134                         pf.close()
       
   135                     except IOError, inst:
       
   136                         if inst.errno != errno.ENOENT:
       
   137                             if not getattr(inst, 'filename', None):
       
   138                                 inst.filename = cvspass
       
   139                             raise
       
   140 
       
   141                 sck = socket.socket()
       
   142                 sck.connect((serv, port))
       
   143                 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
       
   144                                     "END AUTH REQUEST", ""]))
       
   145                 if sck.recv(128) != "I LOVE YOU\n":
       
   146                     raise util.Abort(_("CVS pserver authentication failed"))
       
   147 
       
   148                 self.writep = self.readp = sck.makefile('r+')
       
   149 
       
   150         if not conntype and root.startswith(":local:"):
       
   151             conntype = "local"
       
   152             root = root[7:]
       
   153 
       
   154         if not conntype:
       
   155             # :ext:user@host/home/user/path/to/cvsroot
       
   156             if root.startswith(":ext:"):
       
   157                 root = root[5:]
       
   158             m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
       
   159             # Do not take Windows path "c:\foo\bar" for a connection strings
       
   160             if os.path.isdir(root) or not m:
       
   161                 conntype = "local"
       
   162             else:
       
   163                 conntype = "rsh"
       
   164                 user, host, root = m.group(1), m.group(2), m.group(3)
       
   165 
       
   166         if conntype != "pserver":
       
   167             if conntype == "rsh":
       
   168                 rsh = os.environ.get("CVS_RSH") or "ssh"
       
   169                 if user:
       
   170                     cmd = [rsh, '-l', user, host] + cmd
       
   171                 else:
       
   172                     cmd = [rsh, host] + cmd
       
   173 
       
   174             # popen2 does not support argument lists under Windows
       
   175             cmd = [util.shellquote(arg) for arg in cmd]
       
   176             cmd = util.quotecommand(' '.join(cmd))
       
   177             self.writep, self.readp = util.popen2(cmd)
       
   178 
       
   179         self.realroot = root
       
   180 
       
   181         self.writep.write("Root %s\n" % root)
       
   182         self.writep.write("Valid-responses ok error Valid-requests Mode"
       
   183                           " M Mbinary E Checked-in Created Updated"
       
   184                           " Merged Removed\n")
       
   185         self.writep.write("valid-requests\n")
       
   186         self.writep.flush()
       
   187         r = self.readp.readline()
       
   188         if not r.startswith("Valid-requests"):
       
   189             raise util.Abort(_('unexpected response from CVS server '
       
   190                                '(expected "Valid-requests", but got %r)')
       
   191                              % r)
       
   192         if "UseUnchanged" in r:
       
   193             self.writep.write("UseUnchanged\n")
       
   194             self.writep.flush()
       
   195             r = self.readp.readline()
       
   196 
       
   197     def getheads(self):
       
   198         self._parse()
       
   199         return self.heads
       
   200 
       
   201     def getfile(self, name, rev):
       
   202 
       
   203         def chunkedread(fp, count):
       
   204             # file-objects returned by socked.makefile() do not handle
       
   205             # large read() requests very well.
       
   206             chunksize = 65536
       
   207             output = StringIO()
       
   208             while count > 0:
       
   209                 data = fp.read(min(count, chunksize))
       
   210                 if not data:
       
   211                     raise util.Abort(_("%d bytes missing from remote file")
       
   212                                      % count)
       
   213                 count -= len(data)
       
   214                 output.write(data)
       
   215             return output.getvalue()
       
   216 
       
   217         self._parse()
       
   218         if rev.endswith("(DEAD)"):
       
   219             raise IOError
       
   220 
       
   221         args = ("-N -P -kk -r %s --" % rev).split()
       
   222         args.append(self.cvsrepo + '/' + name)
       
   223         for x in args:
       
   224             self.writep.write("Argument %s\n" % x)
       
   225         self.writep.write("Directory .\n%s\nco\n" % self.realroot)
       
   226         self.writep.flush()
       
   227 
       
   228         data = ""
       
   229         mode = None
       
   230         while 1:
       
   231             line = self.readp.readline()
       
   232             if line.startswith("Created ") or line.startswith("Updated "):
       
   233                 self.readp.readline() # path
       
   234                 self.readp.readline() # entries
       
   235                 mode = self.readp.readline()[:-1]
       
   236                 count = int(self.readp.readline()[:-1])
       
   237                 data = chunkedread(self.readp, count)
       
   238             elif line.startswith(" "):
       
   239                 data += line[1:]
       
   240             elif line.startswith("M "):
       
   241                 pass
       
   242             elif line.startswith("Mbinary "):
       
   243                 count = int(self.readp.readline()[:-1])
       
   244                 data = chunkedread(self.readp, count)
       
   245             else:
       
   246                 if line == "ok\n":
       
   247                     if mode is None:
       
   248                         raise util.Abort(_('malformed response from CVS'))
       
   249                     return (data, "x" in mode and "x" or "")
       
   250                 elif line.startswith("E "):
       
   251                     self.ui.warn(_("cvs server: %s\n") % line[2:])
       
   252                 elif line.startswith("Remove"):
       
   253                     self.readp.readline()
       
   254                 else:
       
   255                     raise util.Abort(_("unknown CVS response: %s") % line)
       
   256 
       
   257     def getchanges(self, rev):
       
   258         self._parse()
       
   259         return sorted(self.files[rev].iteritems()), {}
       
   260 
       
   261     def getcommit(self, rev):
       
   262         self._parse()
       
   263         return self.changeset[rev]
       
   264 
       
   265     def gettags(self):
       
   266         self._parse()
       
   267         return self.tags
       
   268 
       
   269     def getchangedfiles(self, rev, i):
       
   270         self._parse()
       
   271         return sorted(self.files[rev])