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