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