##// END OF EJS Templates
Bug:1201 hg convert on CVS working copy produces Traceback...
Martin OConnor -
r6813:e1d8e79d default
parent child Browse files
Show More
@@ -1,315 +1,315 b''
1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
1 # CVS conversion code inspired by hg-cvs-import and git-cvsimport
2
2
3 import os, locale, re, socket
3 import os, locale, re, socket
4 from cStringIO import StringIO
4 from cStringIO import StringIO
5 from mercurial import util
5 from mercurial import util
6
6
7 from common import NoRepo, commit, converter_source, checktool
7 from common import NoRepo, commit, converter_source, checktool
8
8
9 class convert_cvs(converter_source):
9 class convert_cvs(converter_source):
10 def __init__(self, ui, path, rev=None):
10 def __init__(self, ui, path, rev=None):
11 super(convert_cvs, self).__init__(ui, path, rev=rev)
11 super(convert_cvs, self).__init__(ui, path, rev=rev)
12
12
13 cvs = os.path.join(path, "CVS")
13 cvs = os.path.join(path, "CVS")
14 if not os.path.exists(cvs):
14 if not os.path.exists(cvs):
15 raise NoRepo("%s does not look like a CVS checkout" % path)
15 raise NoRepo("%s does not look like a CVS checkout" % path)
16
16
17 self.cmd = ui.config('convert', 'cvsps', 'cvsps -A -u --cvs-direct -q')
17 self.cmd = ui.config('convert', 'cvsps', 'cvsps -A -u --cvs-direct -q')
18 cvspsexe = self.cmd.split(None, 1)[0]
18 cvspsexe = self.cmd.split(None, 1)[0]
19 for tool in (cvspsexe, 'cvs'):
19 for tool in (cvspsexe, 'cvs'):
20 checktool(tool)
20 checktool(tool)
21
21
22 self.changeset = {}
22 self.changeset = {}
23 self.files = {}
23 self.files = {}
24 self.tags = {}
24 self.tags = {}
25 self.lastbranch = {}
25 self.lastbranch = {}
26 self.parent = {}
26 self.parent = {}
27 self.socket = None
27 self.socket = None
28 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
28 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
29 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
29 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
30 self.encoding = locale.getpreferredencoding()
30 self.encoding = locale.getpreferredencoding()
31 self._parse()
31 self._parse()
32 self._connect()
32 self._connect()
33
33
34 def _parse(self):
34 def _parse(self):
35 if self.changeset:
35 if self.changeset:
36 return
36 return
37
37
38 maxrev = 0
38 maxrev = 0
39 cmd = self.cmd
39 cmd = self.cmd
40 if self.rev:
40 if self.rev:
41 # TODO: handle tags
41 # TODO: handle tags
42 try:
42 try:
43 # patchset number?
43 # patchset number?
44 maxrev = int(self.rev)
44 maxrev = int(self.rev)
45 except ValueError:
45 except ValueError:
46 try:
46 try:
47 # date
47 # date
48 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
48 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
49 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
49 cmd = '%s -d "1970/01/01 00:00:01" -d "%s"' % (cmd, self.rev)
50 except util.Abort:
50 except util.Abort:
51 raise util.Abort('revision %s is not a patchset number or date' % self.rev)
51 raise util.Abort('revision %s is not a patchset number or date' % self.rev)
52
52
53 d = os.getcwd()
53 d = os.getcwd()
54 try:
54 try:
55 os.chdir(self.path)
55 os.chdir(self.path)
56 id = None
56 id = None
57 state = 0
57 state = 0
58 filerevids = {}
58 filerevids = {}
59 for l in util.popen(cmd):
59 for l in util.popen(cmd):
60 if state == 0: # header
60 if state == 0: # header
61 if l.startswith("PatchSet"):
61 if l.startswith("PatchSet"):
62 id = l[9:-2]
62 id = l[9:-2]
63 if maxrev and int(id) > maxrev:
63 if maxrev and int(id) > maxrev:
64 # ignore everything
64 # ignore everything
65 state = 3
65 state = 3
66 elif l.startswith("Date"):
66 elif l.startswith("Date"):
67 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
67 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
68 date = util.datestr(date)
68 date = util.datestr(date)
69 elif l.startswith("Branch"):
69 elif l.startswith("Branch"):
70 branch = l[8:-1]
70 branch = l[8:-1]
71 self.parent[id] = self.lastbranch.get(branch, 'bad')
71 self.parent[id] = self.lastbranch.get(branch, 'bad')
72 self.lastbranch[branch] = id
72 self.lastbranch[branch] = id
73 elif l.startswith("Ancestor branch"):
73 elif l.startswith("Ancestor branch"):
74 ancestor = l[17:-1]
74 ancestor = l[17:-1]
75 # figure out the parent later
75 # figure out the parent later
76 self.parent[id] = self.lastbranch[ancestor]
76 self.parent[id] = self.lastbranch[ancestor]
77 elif l.startswith("Author"):
77 elif l.startswith("Author"):
78 author = self.recode(l[8:-1])
78 author = self.recode(l[8:-1])
79 elif l.startswith("Tag:") or l.startswith("Tags:"):
79 elif l.startswith("Tag:") or l.startswith("Tags:"):
80 t = l[l.index(':')+1:]
80 t = l[l.index(':')+1:]
81 t = [ut.strip() for ut in t.split(',')]
81 t = [ut.strip() for ut in t.split(',')]
82 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
82 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
83 self.tags.update(dict.fromkeys(t, id))
83 self.tags.update(dict.fromkeys(t, id))
84 elif l.startswith("Log:"):
84 elif l.startswith("Log:"):
85 # switch to gathering log
85 # switch to gathering log
86 state = 1
86 state = 1
87 log = ""
87 log = ""
88 elif state == 1: # log
88 elif state == 1: # log
89 if l == "Members: \n":
89 if l == "Members: \n":
90 # switch to gathering members
90 # switch to gathering members
91 files = {}
91 files = {}
92 oldrevs = []
92 oldrevs = []
93 log = self.recode(log[:-1])
93 log = self.recode(log[:-1])
94 state = 2
94 state = 2
95 else:
95 else:
96 # gather log
96 # gather log
97 log += l
97 log += l
98 elif state == 2: # members
98 elif state == 2: # members
99 if l == "\n": # start of next entry
99 if l == "\n": # start of next entry
100 state = 0
100 state = 0
101 p = [self.parent[id]]
101 p = [self.parent[id]]
102 if id == "1":
102 if id == "1":
103 p = []
103 p = []
104 if branch == "HEAD":
104 if branch == "HEAD":
105 branch = ""
105 branch = ""
106 if branch:
106 if branch:
107 latest = None
107 latest = None
108 # the last changeset that contains a base
108 # the last changeset that contains a base
109 # file is our parent
109 # file is our parent
110 for r in oldrevs:
110 for r in oldrevs:
111 latest = max(filerevids.get(r, None), latest)
111 latest = max(filerevids.get(r, None), latest)
112 if latest:
112 if latest:
113 p = [latest]
113 p = [latest]
114
114
115 # add current commit to set
115 # add current commit to set
116 c = commit(author=author, date=date, parents=p,
116 c = commit(author=author, date=date, parents=p,
117 desc=log, branch=branch)
117 desc=log, branch=branch)
118 self.changeset[id] = c
118 self.changeset[id] = c
119 self.files[id] = files
119 self.files[id] = files
120 else:
120 else:
121 colon = l.rfind(':')
121 colon = l.rfind(':')
122 file = l[1:colon]
122 file = l[1:colon]
123 rev = l[colon+1:-2]
123 rev = l[colon+1:-2]
124 oldrev, rev = rev.split("->")
124 oldrev, rev = rev.split("->")
125 files[file] = rev
125 files[file] = rev
126
126
127 # save some information for identifying branch points
127 # save some information for identifying branch points
128 oldrevs.append("%s:%s" % (oldrev, file))
128 oldrevs.append("%s:%s" % (oldrev, file))
129 filerevids["%s:%s" % (rev, file)] = id
129 filerevids["%s:%s" % (rev, file)] = id
130 elif state == 3:
130 elif state == 3:
131 # swallow all input
131 # swallow all input
132 continue
132 continue
133
133
134 self.heads = self.lastbranch.values()
134 self.heads = self.lastbranch.values()
135 finally:
135 finally:
136 os.chdir(d)
136 os.chdir(d)
137
137
138 def _connect(self):
138 def _connect(self):
139 root = self.cvsroot
139 root = self.cvsroot
140 conntype = None
140 conntype = None
141 user, host = None, None
141 user, host = None, None
142 cmd = ['cvs', 'server']
142 cmd = ['cvs', 'server']
143
143
144 self.ui.status("connecting to %s\n" % root)
144 self.ui.status("connecting to %s\n" % root)
145
145
146 if root.startswith(":pserver:"):
146 if root.startswith(":pserver:"):
147 root = root[9:]
147 root = root[9:]
148 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
148 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
149 root)
149 root)
150 if m:
150 if m:
151 conntype = "pserver"
151 conntype = "pserver"
152 user, passw, serv, port, root = m.groups()
152 user, passw, serv, port, root = m.groups()
153 if not user:
153 if not user:
154 user = "anonymous"
154 user = "anonymous"
155 if not port:
155 if not port:
156 port = 2401
156 port = 2401
157 else:
157 else:
158 port = int(port)
158 port = int(port)
159 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
159 format0 = ":pserver:%s@%s:%s" % (user, serv, root)
160 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
160 format1 = ":pserver:%s@%s:%d%s" % (user, serv, port, root)
161
161
162 if not passw:
162 if not passw:
163 passw = "A"
163 passw = "A"
164 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
164 pf = open(os.path.expanduser("~/.cvspass"))
165 for line in pf.read().splitlines():
165 for line in pf.read().splitlines():
166 part1, part2 = line.split(' ', 1)
166 part1, part2 = line.split(' ', 1)
167 if part1 == '/1':
167 if part1 == '/1':
168 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
168 # /1 :pserver:user@example.com:2401/cvsroot/foo Ah<Z
169 part1, part2 = part2.split(' ', 1)
169 part1, part2 = part2.split(' ', 1)
170 format = format1
170 format = format1
171 else:
171 else:
172 # :pserver:user@example.com:/cvsroot/foo Ah<Z
172 # :pserver:user@example.com:/cvsroot/foo Ah<Z
173 format = format0
173 format = format0
174 if part1 == format:
174 if part1 == format:
175 passw = part2
175 passw = part2
176 break
176 break
177 pf.close()
177 pf.close()
178
178
179 sck = socket.socket()
179 sck = socket.socket()
180 sck.connect((serv, port))
180 sck.connect((serv, port))
181 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
181 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
182 "END AUTH REQUEST", ""]))
182 "END AUTH REQUEST", ""]))
183 if sck.recv(128) != "I LOVE YOU\n":
183 if sck.recv(128) != "I LOVE YOU\n":
184 raise util.Abort("CVS pserver authentication failed")
184 raise util.Abort("CVS pserver authentication failed")
185
185
186 self.writep = self.readp = sck.makefile('r+')
186 self.writep = self.readp = sck.makefile('r+')
187
187
188 if not conntype and root.startswith(":local:"):
188 if not conntype and root.startswith(":local:"):
189 conntype = "local"
189 conntype = "local"
190 root = root[7:]
190 root = root[7:]
191
191
192 if not conntype:
192 if not conntype:
193 # :ext:user@host/home/user/path/to/cvsroot
193 # :ext:user@host/home/user/path/to/cvsroot
194 if root.startswith(":ext:"):
194 if root.startswith(":ext:"):
195 root = root[5:]
195 root = root[5:]
196 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
196 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
197 # Do not take Windows path "c:\foo\bar" for a connection strings
197 # Do not take Windows path "c:\foo\bar" for a connection strings
198 if os.path.isdir(root) or not m:
198 if os.path.isdir(root) or not m:
199 conntype = "local"
199 conntype = "local"
200 else:
200 else:
201 conntype = "rsh"
201 conntype = "rsh"
202 user, host, root = m.group(1), m.group(2), m.group(3)
202 user, host, root = m.group(1), m.group(2), m.group(3)
203
203
204 if conntype != "pserver":
204 if conntype != "pserver":
205 if conntype == "rsh":
205 if conntype == "rsh":
206 rsh = os.environ.get("CVS_RSH") or "ssh"
206 rsh = os.environ.get("CVS_RSH") or "ssh"
207 if user:
207 if user:
208 cmd = [rsh, '-l', user, host] + cmd
208 cmd = [rsh, '-l', user, host] + cmd
209 else:
209 else:
210 cmd = [rsh, host] + cmd
210 cmd = [rsh, host] + cmd
211
211
212 # popen2 does not support argument lists under Windows
212 # popen2 does not support argument lists under Windows
213 cmd = [util.shellquote(arg) for arg in cmd]
213 cmd = [util.shellquote(arg) for arg in cmd]
214 cmd = util.quotecommand(' '.join(cmd))
214 cmd = util.quotecommand(' '.join(cmd))
215 self.writep, self.readp = os.popen2(cmd, 'b')
215 self.writep, self.readp = os.popen2(cmd, 'b')
216
216
217 self.realroot = root
217 self.realroot = root
218
218
219 self.writep.write("Root %s\n" % root)
219 self.writep.write("Root %s\n" % root)
220 self.writep.write("Valid-responses ok error Valid-requests Mode"
220 self.writep.write("Valid-responses ok error Valid-requests Mode"
221 " M Mbinary E Checked-in Created Updated"
221 " M Mbinary E Checked-in Created Updated"
222 " Merged Removed\n")
222 " Merged Removed\n")
223 self.writep.write("valid-requests\n")
223 self.writep.write("valid-requests\n")
224 self.writep.flush()
224 self.writep.flush()
225 r = self.readp.readline()
225 r = self.readp.readline()
226 if not r.startswith("Valid-requests"):
226 if not r.startswith("Valid-requests"):
227 raise util.Abort("server sucks")
227 raise util.Abort("server sucks")
228 if "UseUnchanged" in r:
228 if "UseUnchanged" in r:
229 self.writep.write("UseUnchanged\n")
229 self.writep.write("UseUnchanged\n")
230 self.writep.flush()
230 self.writep.flush()
231 r = self.readp.readline()
231 r = self.readp.readline()
232
232
233 def getheads(self):
233 def getheads(self):
234 return self.heads
234 return self.heads
235
235
236 def _getfile(self, name, rev):
236 def _getfile(self, name, rev):
237
237
238 def chunkedread(fp, count):
238 def chunkedread(fp, count):
239 # file-objects returned by socked.makefile() do not handle
239 # file-objects returned by socked.makefile() do not handle
240 # large read() requests very well.
240 # large read() requests very well.
241 chunksize = 65536
241 chunksize = 65536
242 output = StringIO()
242 output = StringIO()
243 while count > 0:
243 while count > 0:
244 data = fp.read(min(count, chunksize))
244 data = fp.read(min(count, chunksize))
245 if not data:
245 if not data:
246 raise util.Abort("%d bytes missing from remote file" % count)
246 raise util.Abort("%d bytes missing from remote file" % count)
247 count -= len(data)
247 count -= len(data)
248 output.write(data)
248 output.write(data)
249 return output.getvalue()
249 return output.getvalue()
250
250
251 if rev.endswith("(DEAD)"):
251 if rev.endswith("(DEAD)"):
252 raise IOError
252 raise IOError
253
253
254 args = ("-N -P -kk -r %s --" % rev).split()
254 args = ("-N -P -kk -r %s --" % rev).split()
255 args.append(self.cvsrepo + '/' + name)
255 args.append(self.cvsrepo + '/' + name)
256 for x in args:
256 for x in args:
257 self.writep.write("Argument %s\n" % x)
257 self.writep.write("Argument %s\n" % x)
258 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
258 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
259 self.writep.flush()
259 self.writep.flush()
260
260
261 data = ""
261 data = ""
262 while 1:
262 while 1:
263 line = self.readp.readline()
263 line = self.readp.readline()
264 if line.startswith("Created ") or line.startswith("Updated "):
264 if line.startswith("Created ") or line.startswith("Updated "):
265 self.readp.readline() # path
265 self.readp.readline() # path
266 self.readp.readline() # entries
266 self.readp.readline() # entries
267 mode = self.readp.readline()[:-1]
267 mode = self.readp.readline()[:-1]
268 count = int(self.readp.readline()[:-1])
268 count = int(self.readp.readline()[:-1])
269 data = chunkedread(self.readp, count)
269 data = chunkedread(self.readp, count)
270 elif line.startswith(" "):
270 elif line.startswith(" "):
271 data += line[1:]
271 data += line[1:]
272 elif line.startswith("M "):
272 elif line.startswith("M "):
273 pass
273 pass
274 elif line.startswith("Mbinary "):
274 elif line.startswith("Mbinary "):
275 count = int(self.readp.readline()[:-1])
275 count = int(self.readp.readline()[:-1])
276 data = chunkedread(self.readp, count)
276 data = chunkedread(self.readp, count)
277 else:
277 else:
278 if line == "ok\n":
278 if line == "ok\n":
279 return (data, "x" in mode and "x" or "")
279 return (data, "x" in mode and "x" or "")
280 elif line.startswith("E "):
280 elif line.startswith("E "):
281 self.ui.warn("cvs server: %s\n" % line[2:])
281 self.ui.warn("cvs server: %s\n" % line[2:])
282 elif line.startswith("Remove"):
282 elif line.startswith("Remove"):
283 l = self.readp.readline()
283 l = self.readp.readline()
284 l = self.readp.readline()
284 l = self.readp.readline()
285 if l != "ok\n":
285 if l != "ok\n":
286 raise util.Abort("unknown CVS response: %s" % l)
286 raise util.Abort("unknown CVS response: %s" % l)
287 else:
287 else:
288 raise util.Abort("unknown CVS response: %s" % line)
288 raise util.Abort("unknown CVS response: %s" % line)
289
289
290 def getfile(self, file, rev):
290 def getfile(self, file, rev):
291 data, mode = self._getfile(file, rev)
291 data, mode = self._getfile(file, rev)
292 self.modecache[(file, rev)] = mode
292 self.modecache[(file, rev)] = mode
293 return data
293 return data
294
294
295 def getmode(self, file, rev):
295 def getmode(self, file, rev):
296 return self.modecache[(file, rev)]
296 return self.modecache[(file, rev)]
297
297
298 def getchanges(self, rev):
298 def getchanges(self, rev):
299 self.modecache = {}
299 self.modecache = {}
300 files = self.files[rev]
300 files = self.files[rev]
301 cl = files.items()
301 cl = files.items()
302 cl.sort()
302 cl.sort()
303 return (cl, {})
303 return (cl, {})
304
304
305 def getcommit(self, rev):
305 def getcommit(self, rev):
306 return self.changeset[rev]
306 return self.changeset[rev]
307
307
308 def gettags(self):
308 def gettags(self):
309 return self.tags
309 return self.tags
310
310
311 def getchangedfiles(self, rev, i):
311 def getchangedfiles(self, rev, i):
312 files = self.files[rev].keys()
312 files = self.files[rev].keys()
313 files.sort()
313 files.sort()
314 return files
314 return files
315
315
General Comments 0
You need to be logged in to leave comments. Login now