cvs.py
280 lines
| 10.3 KiB
| text/x-python
|
PythonLexer
Martin Geisler
|
r8250 | # cvs.py: CVS conversion code inspired by hg-cvs-import and git-cvsimport | ||
# | ||||
# Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others | ||||
# | ||||
# This software may be used and distributed according to the terms of the | ||||
Matt Mackall
|
r10263 | # GNU General Public License version 2 or any later version. | ||
Brendan Cully
|
r4536 | |||
Brodie Rao
|
r11987 | import os, re, socket, errno | ||
Patrick Mezard
|
r5539 | from cStringIO import StringIO | ||
Pierre-Yves David
|
r26587 | from mercurial import encoding, util, error | ||
Frank Kingswood
|
r6690 | from mercurial.i18n import _ | ||
Brendan Cully
|
r4536 | |||
Patrick Mezard
|
r5497 | from common import NoRepo, commit, converter_source, checktool | ||
Julian Cowley
|
r17974 | from common import makedatetimestamp | ||
Frank Kingswood
|
r6690 | import cvsps | ||
Brendan Cully
|
r4536 | |||
class convert_cvs(converter_source): | ||||
Durham Goode
|
r25748 | def __init__(self, ui, path, revs=None): | ||
super(convert_cvs, self).__init__(ui, path, revs=revs) | ||||
Brendan Cully
|
r4807 | |||
Brendan Cully
|
r4536 | cvs = os.path.join(path, "CVS") | ||
if not os.path.exists(cvs): | ||||
Martin Geisler
|
r10939 | raise NoRepo(_("%s does not look like a CVS checkout") % path) | ||
Brendan Cully
|
r4536 | |||
Frank Kingswood
|
r6690 | checktool('cvs') | ||
Patrick Mezard
|
r5497 | |||
Patrick Mezard
|
r8048 | self.changeset = None | ||
Brendan Cully
|
r4536 | self.files = {} | ||
self.tags = {} | ||||
self.lastbranch = {} | ||||
self.socket = None | ||||
Alejandro Santos
|
r9031 | self.cvsroot = open(os.path.join(cvs, "Root")).read()[:-1] | ||
self.cvsrepo = open(os.path.join(cvs, "Repository")).read()[:-1] | ||||
Brodie Rao
|
r11987 | self.encoding = encoding.encoding | ||
Frank Kingswood
|
r6690 | |||
Brendan Cully
|
r4536 | self._connect() | ||
Patrick Mezard
|
r8048 | def _parse(self): | ||
if self.changeset is not None: | ||||
Brendan Cully
|
r4536 | return | ||
Patrick Mezard
|
r8048 | self.changeset = {} | ||
Brendan Cully
|
r4536 | |||
Brendan Cully
|
r4760 | maxrev = 0 | ||
Durham Goode
|
r25748 | if self.revs: | ||
if len(self.revs) > 1: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('cvs source does not support specifying ' | ||
Durham Goode
|
r25748 | 'multiple revs')) | ||
Brendan Cully
|
r4760 | # TODO: handle tags | ||
try: | ||||
# patchset number? | ||||
Durham Goode
|
r25748 | maxrev = int(self.revs[0]) | ||
Brendan Cully
|
r4760 | except ValueError: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('revision %s is not a patchset number') | ||
Durham Goode
|
r25748 | % self.revs[0]) | ||
Brendan Cully
|
r4760 | |||
Brendan Cully
|
r4536 | d = os.getcwd() | ||
try: | ||||
os.chdir(self.path) | ||||
id = None | ||||
Frank Kingswood
|
r6690 | |||
Patrick Mezard
|
r9543 | cache = 'update' | ||
if not self.ui.configbool('convert', 'cvsps.cache', True): | ||||
cache = None | ||||
db = cvsps.createlog(self.ui, cache=cache) | ||||
db = cvsps.createchangeset(self.ui, db, | ||||
fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)), | ||||
mergeto=self.ui.config('convert', 'cvsps.mergeto', None), | ||||
mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None)) | ||||
Thomas Arendsen Hein
|
r5920 | |||
Patrick Mezard
|
r9543 | for cs in db: | ||
Matt Mackall
|
r10282 | if maxrev and cs.id > maxrev: | ||
Patrick Mezard
|
r9543 | break | ||
id = str(cs.id) | ||||
cs.author = self.recode(cs.author) | ||||
self.lastbranch[cs.branch] = id | ||||
cs.comment = self.recode(cs.comment) | ||||
Julian Cowley
|
r17974 | if self.ui.configbool('convert', 'localtimezone'): | ||
cs.date = makedatetimestamp(cs.date[0]) | ||||
FUJIWARA Katsunori
|
r16514 | date = util.datestr(cs.date, '%Y-%m-%d %H:%M:%S %1%2') | ||
Patrick Mezard
|
r9543 | self.tags.update(dict.fromkeys(cs.tags, id)) | ||
Thomas Arendsen Hein
|
r5920 | |||
Patrick Mezard
|
r9543 | files = {} | ||
for f in cs.entries: | ||||
Matt Mackall
|
r10282 | files[f.file] = "%s%s" % ('.'.join([str(x) | ||
for x in f.revision]), | ||||
Patrick Mezard
|
r9543 | ['', '(DEAD)'][f.dead]) | ||
Frank Kingswood
|
r6690 | |||
Patrick Mezard
|
r9543 | # add current commit to set | ||
c = commit(author=cs.author, date=date, | ||||
parents=[str(p.id) for p in cs.parents], | ||||
desc=cs.comment, branch=cs.branch or '') | ||||
self.changeset[id] = c | ||||
self.files[id] = files | ||||
Brendan Cully
|
r4536 | |||
self.heads = self.lastbranch.values() | ||||
finally: | ||||
os.chdir(d) | ||||
def _connect(self): | ||||
root = self.cvsroot | ||||
conntype = None | ||||
user, host = None, None | ||||
cmd = ['cvs', 'server'] | ||||
Martin Geisler
|
r6956 | self.ui.status(_("connecting to %s\n") % root) | ||
Brendan Cully
|
r4536 | |||
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" | ||||
Thomas Arendsen Hein
|
r5082 | if not port: | ||
port = 2401 | ||||
Brendan Cully
|
r4536 | else: | ||
Thomas Arendsen Hein
|
r5082 | port = int(port) | ||
format0 = ":pserver:%s@%s:%s" % (user, serv, root) | ||||
format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root) | ||||
Brendan Cully
|
r4536 | |||
if not passw: | ||||
passw = "A" | ||||
Edouard Gomez
|
r7442 | cvspass = os.path.expanduser("~/.cvspass") | ||
Thomas Arendsen Hein
|
r7444 | try: | ||
Edouard Gomez
|
r7442 | pf = open(cvspass) | ||
for line in pf.read().splitlines(): | ||||
part1, part2 = line.split(' ', 1) | ||||
Brodie Rao
|
r16683 | # /1 :pserver:user@example.com:2401/cvsroot/foo | ||
# Ah<Z | ||||
Edouard Gomez
|
r7442 | if part1 == '/1': | ||
part1, part2 = part2.split(' ', 1) | ||||
format = format1 | ||||
Brodie Rao
|
r16683 | # :pserver:user@example.com:/cvsroot/foo Ah<Z | ||
Edouard Gomez
|
r7442 | else: | ||
format = format0 | ||||
if part1 == format: | ||||
passw = part2 | ||||
break | ||||
pf.close() | ||||
Gregory Szorc
|
r25660 | except IOError as inst: | ||
Thomas Arendsen Hein
|
r7444 | if inst.errno != errno.ENOENT: | ||
if not getattr(inst, 'filename', None): | ||||
inst.filename = cvspass | ||||
raise | ||||
Brendan Cully
|
r4536 | |||
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": | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("CVS pserver authentication failed")) | ||
Brendan Cully
|
r4536 | |||
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) | ||||
Patrick Mezard
|
r5304 | # Do not take Windows path "c:\foo\bar" for a connection strings | ||
if os.path.isdir(root) or not m: | ||||
Brendan Cully
|
r4536 | conntype = "local" | ||
else: | ||||
conntype = "rsh" | ||||
user, host, root = m.group(1), m.group(2), m.group(3) | ||||
if conntype != "pserver": | ||||
if conntype == "rsh": | ||||
Kostantinos Koukopoulos
|
r5860 | rsh = os.environ.get("CVS_RSH") or "ssh" | ||
Brendan Cully
|
r4536 | if user: | ||
cmd = [rsh, '-l', user, host] + cmd | ||||
else: | ||||
cmd = [rsh, host] + cmd | ||||
Patrick Mezard
|
r5303 | # popen2 does not support argument lists under Windows | ||
cmd = [util.shellquote(arg) for arg in cmd] | ||||
cmd = util.quotecommand(' '.join(cmd)) | ||||
Martin Geisler
|
r8339 | self.writep, self.readp = util.popen2(cmd) | ||
Brendan Cully
|
r4536 | |||
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"): | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_('unexpected response from CVS server ' | ||
Matt Mackall
|
r10282 | '(expected "Valid-requests", but got %r)') | ||
Greg Ward
|
r9095 | % r) | ||
Brendan Cully
|
r4536 | if "UseUnchanged" in r: | ||
self.writep.write("UseUnchanged\n") | ||||
self.writep.flush() | ||||
r = self.readp.readline() | ||||
def getheads(self): | ||||
Patrick Mezard
|
r8048 | self._parse() | ||
Brendan Cully
|
r4536 | return self.heads | ||
Patrick Mezard
|
r11134 | def getfile(self, name, rev): | ||
Patrick Mezard
|
r5539 | |||
def chunkedread(fp, count): | ||||
Mads Kiilerich
|
r17424 | # file-objects returned by socket.makefile() do not handle | ||
Patrick Mezard
|
r5539 | # large read() requests very well. | ||
chunksize = 65536 | ||||
output = StringIO() | ||||
while count > 0: | ||||
data = fp.read(min(count, chunksize)) | ||||
if not data: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("%d bytes missing from remote file") | ||
Matt Mackall
|
r10282 | % count) | ||
Patrick Mezard
|
r5539 | count -= len(data) | ||
output.write(data) | ||||
return output.getvalue() | ||||
Patrick Mezard
|
r11134 | self._parse() | ||
Brendan Cully
|
r4536 | if rev.endswith("(DEAD)"): | ||
Mads Kiilerich
|
r22296 | return None, None | ||
Brendan Cully
|
r4536 | |||
args = ("-N -P -kk -r %s --" % rev).split() | ||||
Patrick Mezard
|
r5305 | args.append(self.cvsrepo + '/' + name) | ||
Brendan Cully
|
r4536 | 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 = "" | ||||
Mads Kiilerich
|
r10800 | mode = None | ||
Martin Geisler
|
r14494 | while True: | ||
Brendan Cully
|
r4536 | 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]) | ||||
Patrick Mezard
|
r5539 | data = chunkedread(self.readp, count) | ||
Brendan Cully
|
r4536 | elif line.startswith(" "): | ||
data += line[1:] | ||||
elif line.startswith("M "): | ||||
pass | ||||
elif line.startswith("Mbinary "): | ||||
count = int(self.readp.readline()[:-1]) | ||||
Patrick Mezard
|
r5539 | data = chunkedread(self.readp, count) | ||
Brendan Cully
|
r4536 | else: | ||
if line == "ok\n": | ||||
Mads Kiilerich
|
r10800 | if mode is None: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_('malformed response from CVS')) | ||
Brendan Cully
|
r4536 | return (data, "x" in mode and "x" or "") | ||
elif line.startswith("E "): | ||||
Martin Geisler
|
r6956 | self.ui.warn(_("cvs server: %s\n") % line[2:]) | ||
Brendan Cully
|
r4536 | elif line.startswith("Remove"): | ||
Peter Arrenbrecht
|
r7874 | self.readp.readline() | ||
Brendan Cully
|
r4536 | else: | ||
Pierre-Yves David
|
r26587 | raise error.Abort(_("unknown CVS response: %s") % line) | ||
Brendan Cully
|
r4536 | |||
Mads Kiilerich
|
r22300 | def getchanges(self, rev, full): | ||
if full: | ||||
Pierre-Yves David
|
r26587 | raise error.Abort(_("convert from cvs do not support --full")) | ||
Patrick Mezard
|
r8048 | self._parse() | ||
Mads Kiilerich
|
r24395 | return sorted(self.files[rev].iteritems()), {}, set() | ||
Brendan Cully
|
r4536 | |||
def getcommit(self, rev): | ||||
Patrick Mezard
|
r8048 | self._parse() | ||
Brendan Cully
|
r4536 | return self.changeset[rev] | ||
def gettags(self): | ||||
Patrick Mezard
|
r8048 | self._parse() | ||
Brendan Cully
|
r4536 | return self.tags | ||
Alexis S. L. Carvalho
|
r5381 | |||
def getchangedfiles(self, rev, i): | ||||
Patrick Mezard
|
r8048 | self._parse() | ||
Matt Mackall
|
r8209 | return sorted(self.files[rev]) | ||