##// END OF EJS Templates
convert: move *** empty log message *** into commit class
Brendan Cully -
r4762:47091c8d default
parent child Browse files
Show More
@@ -1,108 +1,110
1 # common code for the convert extension
1 # common code for the convert extension
2
2
3 class NoRepo(Exception): pass
3 class NoRepo(Exception): pass
4
4
5 class commit(object):
5 class commit(object):
6 def __init__(self, **parts):
6 def __init__(self, **parts):
7 for x in "author date desc parents".split():
7 for x in "author date desc parents".split():
8 if not x in parts:
8 if not x in parts:
9 raise util.Abort("commit missing field %s" % x)
9 raise util.Abort("commit missing field %s" % x)
10 self.__dict__.update(parts)
10 self.__dict__.update(parts)
11 if not self.desc or self.desc.isspace():
12 self.desc = '*** empty log message ***'
11
13
12 class converter_source(object):
14 class converter_source(object):
13 """Conversion source interface"""
15 """Conversion source interface"""
14
16
15 def __init__(self, ui, path, rev=None):
17 def __init__(self, ui, path, rev=None):
16 """Initialize conversion source (or raise NoRepo("message")
18 """Initialize conversion source (or raise NoRepo("message")
17 exception if path is not a valid repository)"""
19 exception if path is not a valid repository)"""
18 raise NotImplementedError()
20 raise NotImplementedError()
19
21
20 def getheads(self):
22 def getheads(self):
21 """Return a list of this repository's heads"""
23 """Return a list of this repository's heads"""
22 raise NotImplementedError()
24 raise NotImplementedError()
23
25
24 def getfile(self, name, rev):
26 def getfile(self, name, rev):
25 """Return file contents as a string"""
27 """Return file contents as a string"""
26 raise NotImplementedError()
28 raise NotImplementedError()
27
29
28 def getmode(self, name, rev):
30 def getmode(self, name, rev):
29 """Return file mode, eg. '', 'x', or 'l'"""
31 """Return file mode, eg. '', 'x', or 'l'"""
30 raise NotImplementedError()
32 raise NotImplementedError()
31
33
32 def getchanges(self, version):
34 def getchanges(self, version):
33 """Return sorted list of (filename, id) tuples for all files changed in rev.
35 """Return sorted list of (filename, id) tuples for all files changed in rev.
34
36
35 id just tells us which revision to return in getfile(), e.g. in
37 id just tells us which revision to return in getfile(), e.g. in
36 git it's an object hash."""
38 git it's an object hash."""
37 raise NotImplementedError()
39 raise NotImplementedError()
38
40
39 def getcommit(self, version):
41 def getcommit(self, version):
40 """Return the commit object for version"""
42 """Return the commit object for version"""
41 raise NotImplementedError()
43 raise NotImplementedError()
42
44
43 def gettags(self):
45 def gettags(self):
44 """Return the tags as a dictionary of name: revision"""
46 """Return the tags as a dictionary of name: revision"""
45 raise NotImplementedError()
47 raise NotImplementedError()
46
48
47 def recode(self, s, encoding=None):
49 def recode(self, s, encoding=None):
48 if not encoding:
50 if not encoding:
49 encoding = hasattr(self, 'encoding') and self.encoding or 'utf-8'
51 encoding = hasattr(self, 'encoding') and self.encoding or 'utf-8'
50
52
51 try:
53 try:
52 return s.decode(encoding).encode("utf-8")
54 return s.decode(encoding).encode("utf-8")
53 except:
55 except:
54 try:
56 try:
55 return s.decode("latin-1").encode("utf-8")
57 return s.decode("latin-1").encode("utf-8")
56 except:
58 except:
57 return s.decode(encoding, "replace").encode("utf-8")
59 return s.decode(encoding, "replace").encode("utf-8")
58
60
59 class converter_sink(object):
61 class converter_sink(object):
60 """Conversion sink (target) interface"""
62 """Conversion sink (target) interface"""
61
63
62 def __init__(self, ui, path):
64 def __init__(self, ui, path):
63 """Initialize conversion sink (or raise NoRepo("message")
65 """Initialize conversion sink (or raise NoRepo("message")
64 exception if path is not a valid repository)"""
66 exception if path is not a valid repository)"""
65 raise NotImplementedError()
67 raise NotImplementedError()
66
68
67 def getheads(self):
69 def getheads(self):
68 """Return a list of this repository's heads"""
70 """Return a list of this repository's heads"""
69 raise NotImplementedError()
71 raise NotImplementedError()
70
72
71 def mapfile(self):
73 def mapfile(self):
72 """Path to a file that will contain lines
74 """Path to a file that will contain lines
73 source_rev_id sink_rev_id
75 source_rev_id sink_rev_id
74 mapping equivalent revision identifiers for each system."""
76 mapping equivalent revision identifiers for each system."""
75 raise NotImplementedError()
77 raise NotImplementedError()
76
78
77 def authorfile(self):
79 def authorfile(self):
78 """Path to a file that will contain lines
80 """Path to a file that will contain lines
79 srcauthor=dstauthor
81 srcauthor=dstauthor
80 mapping equivalent authors identifiers for each system."""
82 mapping equivalent authors identifiers for each system."""
81 return None
83 return None
82
84
83 def putfile(self, f, e, data):
85 def putfile(self, f, e, data):
84 """Put file for next putcommit().
86 """Put file for next putcommit().
85 f: path to file
87 f: path to file
86 e: '', 'x', or 'l' (regular file, executable, or symlink)
88 e: '', 'x', or 'l' (regular file, executable, or symlink)
87 data: file contents"""
89 data: file contents"""
88 raise NotImplementedError()
90 raise NotImplementedError()
89
91
90 def delfile(self, f):
92 def delfile(self, f):
91 """Delete file for next putcommit().
93 """Delete file for next putcommit().
92 f: path to file"""
94 f: path to file"""
93 raise NotImplementedError()
95 raise NotImplementedError()
94
96
95 def putcommit(self, files, parents, commit):
97 def putcommit(self, files, parents, commit):
96 """Create a revision with all changed files listed in 'files'
98 """Create a revision with all changed files listed in 'files'
97 and having listed parents. 'commit' is a commit object containing
99 and having listed parents. 'commit' is a commit object containing
98 at a minimum the author, date, and message for this changeset.
100 at a minimum the author, date, and message for this changeset.
99 Called after putfile() and delfile() calls. Note that the sink
101 Called after putfile() and delfile() calls. Note that the sink
100 repository is not told to update itself to a particular revision
102 repository is not told to update itself to a particular revision
101 (or even what that revision would be) before it receives the
103 (or even what that revision would be) before it receives the
102 file data."""
104 file data."""
103 raise NotImplementedError()
105 raise NotImplementedError()
104
106
105 def puttags(self, tags):
107 def puttags(self, tags):
106 """Put tags into sink.
108 """Put tags into sink.
107 tags: {tagname: sink_rev_id, ...}"""
109 tags: {tagname: sink_rev_id, ...}"""
108 raise NotImplementedError()
110 raise NotImplementedError()
@@ -1,262 +1,260
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 self.path = path
10 self.path = path
11 self.ui = ui
11 self.ui = ui
12 self.rev = rev
12 self.rev = rev
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("couldn't open CVS repo %s" % path)
15 raise NoRepo("couldn't open CVS repo %s" % path)
16
16
17 self.changeset = {}
17 self.changeset = {}
18 self.files = {}
18 self.files = {}
19 self.tags = {}
19 self.tags = {}
20 self.lastbranch = {}
20 self.lastbranch = {}
21 self.parent = {}
21 self.parent = {}
22 self.socket = None
22 self.socket = None
23 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
23 self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1]
24 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
24 self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1]
25 self.encoding = locale.getpreferredencoding()
25 self.encoding = locale.getpreferredencoding()
26 self._parse()
26 self._parse()
27 self._connect()
27 self._connect()
28
28
29 def _parse(self):
29 def _parse(self):
30 if self.changeset:
30 if self.changeset:
31 return
31 return
32
32
33 maxrev = 0
33 maxrev = 0
34 cmd = 'cvsps -A -u --cvs-direct -q'
34 cmd = 'cvsps -A -u --cvs-direct -q'
35 if self.rev:
35 if self.rev:
36 # TODO: handle tags
36 # TODO: handle tags
37 try:
37 try:
38 # patchset number?
38 # patchset number?
39 maxrev = int(self.rev)
39 maxrev = int(self.rev)
40 except ValueError:
40 except ValueError:
41 try:
41 try:
42 # date
42 # date
43 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
43 util.parsedate(self.rev, ['%Y/%m/%d %H:%M:%S'])
44 cmd = "%s -d '1970/01/01 00:00:01' -d '%s'" % (cmd, self.rev)
44 cmd = "%s -d '1970/01/01 00:00:01' -d '%s'" % (cmd, self.rev)
45 except util.Abort:
45 except util.Abort:
46 raise util.Abort('revision %s is not a patchset number or date' % self.rev)
46 raise util.Abort('revision %s is not a patchset number or date' % self.rev)
47
47
48 d = os.getcwd()
48 d = os.getcwd()
49 try:
49 try:
50 os.chdir(self.path)
50 os.chdir(self.path)
51 id = None
51 id = None
52 state = 0
52 state = 0
53 for l in os.popen(cmd):
53 for l in os.popen(cmd):
54 if state == 0: # header
54 if state == 0: # header
55 if l.startswith("PatchSet"):
55 if l.startswith("PatchSet"):
56 id = l[9:-2]
56 id = l[9:-2]
57 if maxrev and int(id) > maxrev:
57 if maxrev and int(id) > maxrev:
58 state = 3
58 state = 3
59 elif l.startswith("Date"):
59 elif l.startswith("Date"):
60 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
60 date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"])
61 date = util.datestr(date)
61 date = util.datestr(date)
62 elif l.startswith("Branch"):
62 elif l.startswith("Branch"):
63 branch = l[8:-1]
63 branch = l[8:-1]
64 self.parent[id] = self.lastbranch.get(branch, 'bad')
64 self.parent[id] = self.lastbranch.get(branch, 'bad')
65 self.lastbranch[branch] = id
65 self.lastbranch[branch] = id
66 elif l.startswith("Ancestor branch"):
66 elif l.startswith("Ancestor branch"):
67 ancestor = l[17:-1]
67 ancestor = l[17:-1]
68 self.parent[id] = self.lastbranch[ancestor]
68 self.parent[id] = self.lastbranch[ancestor]
69 elif l.startswith("Author"):
69 elif l.startswith("Author"):
70 author = self.recode(l[8:-1])
70 author = self.recode(l[8:-1])
71 elif l.startswith("Tag:") or l.startswith("Tags:"):
71 elif l.startswith("Tag:") or l.startswith("Tags:"):
72 t = l[l.index(':')+1:]
72 t = l[l.index(':')+1:]
73 t = [ut.strip() for ut in t.split(',')]
73 t = [ut.strip() for ut in t.split(',')]
74 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
74 if (len(t) > 1) or (t[0] and (t[0] != "(none)")):
75 self.tags.update(dict.fromkeys(t, id))
75 self.tags.update(dict.fromkeys(t, id))
76 elif l.startswith("Log:"):
76 elif l.startswith("Log:"):
77 state = 1
77 state = 1
78 log = ""
78 log = ""
79 elif state == 1: # log
79 elif state == 1: # log
80 if l == "Members: \n":
80 if l == "Members: \n":
81 files = {}
81 files = {}
82 log = self.recode(log[:-1])
82 log = self.recode(log[:-1])
83 if log.isspace():
84 log = "*** empty log message ***\n"
85 state = 2
83 state = 2
86 else:
84 else:
87 log += l
85 log += l
88 elif state == 2:
86 elif state == 2:
89 if l == "\n": #
87 if l == "\n": #
90 state = 0
88 state = 0
91 p = [self.parent[id]]
89 p = [self.parent[id]]
92 if id == "1":
90 if id == "1":
93 p = []
91 p = []
94 if branch == "HEAD":
92 if branch == "HEAD":
95 branch = ""
93 branch = ""
96 c = commit(author=author, date=date, parents=p,
94 c = commit(author=author, date=date, parents=p,
97 desc=log, branch=branch)
95 desc=log, branch=branch)
98 self.changeset[id] = c
96 self.changeset[id] = c
99 self.files[id] = files
97 self.files[id] = files
100 else:
98 else:
101 colon = l.rfind(':')
99 colon = l.rfind(':')
102 file = l[1:colon]
100 file = l[1:colon]
103 rev = l[colon+1:-2]
101 rev = l[colon+1:-2]
104 rev = rev.split("->")[1]
102 rev = rev.split("->")[1]
105 files[file] = rev
103 files[file] = rev
106 elif state == 3:
104 elif state == 3:
107 continue
105 continue
108
106
109 self.heads = self.lastbranch.values()
107 self.heads = self.lastbranch.values()
110 finally:
108 finally:
111 os.chdir(d)
109 os.chdir(d)
112
110
113 def _connect(self):
111 def _connect(self):
114 root = self.cvsroot
112 root = self.cvsroot
115 conntype = None
113 conntype = None
116 user, host = None, None
114 user, host = None, None
117 cmd = ['cvs', 'server']
115 cmd = ['cvs', 'server']
118
116
119 self.ui.status("connecting to %s\n" % root)
117 self.ui.status("connecting to %s\n" % root)
120
118
121 if root.startswith(":pserver:"):
119 if root.startswith(":pserver:"):
122 root = root[9:]
120 root = root[9:]
123 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
121 m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)',
124 root)
122 root)
125 if m:
123 if m:
126 conntype = "pserver"
124 conntype = "pserver"
127 user, passw, serv, port, root = m.groups()
125 user, passw, serv, port, root = m.groups()
128 if not user:
126 if not user:
129 user = "anonymous"
127 user = "anonymous"
130 rr = ":pserver:" + user + "@" + serv + ":" + root
128 rr = ":pserver:" + user + "@" + serv + ":" + root
131 if port:
129 if port:
132 rr2, port = "-", int(port)
130 rr2, port = "-", int(port)
133 else:
131 else:
134 rr2, port = rr, 2401
132 rr2, port = rr, 2401
135 rr += str(port)
133 rr += str(port)
136
134
137 if not passw:
135 if not passw:
138 passw = "A"
136 passw = "A"
139 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
137 pf = open(os.path.join(os.environ["HOME"], ".cvspass"))
140 for l in pf:
138 for l in pf:
141 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
139 # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z
142 m = re.match(r'(/\d+\s+/)?(.*)', l)
140 m = re.match(r'(/\d+\s+/)?(.*)', l)
143 l = m.group(2)
141 l = m.group(2)
144 w, p = l.split(' ', 1)
142 w, p = l.split(' ', 1)
145 if w in [rr, rr2]:
143 if w in [rr, rr2]:
146 passw = p
144 passw = p
147 break
145 break
148 pf.close()
146 pf.close()
149
147
150 sck = socket.socket()
148 sck = socket.socket()
151 sck.connect((serv, port))
149 sck.connect((serv, port))
152 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
150 sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw,
153 "END AUTH REQUEST", ""]))
151 "END AUTH REQUEST", ""]))
154 if sck.recv(128) != "I LOVE YOU\n":
152 if sck.recv(128) != "I LOVE YOU\n":
155 raise NoRepo("CVS pserver authentication failed")
153 raise NoRepo("CVS pserver authentication failed")
156
154
157 self.writep = self.readp = sck.makefile('r+')
155 self.writep = self.readp = sck.makefile('r+')
158
156
159 if not conntype and root.startswith(":local:"):
157 if not conntype and root.startswith(":local:"):
160 conntype = "local"
158 conntype = "local"
161 root = root[7:]
159 root = root[7:]
162
160
163 if not conntype:
161 if not conntype:
164 # :ext:user@host/home/user/path/to/cvsroot
162 # :ext:user@host/home/user/path/to/cvsroot
165 if root.startswith(":ext:"):
163 if root.startswith(":ext:"):
166 root = root[5:]
164 root = root[5:]
167 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
165 m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root)
168 if not m:
166 if not m:
169 conntype = "local"
167 conntype = "local"
170 else:
168 else:
171 conntype = "rsh"
169 conntype = "rsh"
172 user, host, root = m.group(1), m.group(2), m.group(3)
170 user, host, root = m.group(1), m.group(2), m.group(3)
173
171
174 if conntype != "pserver":
172 if conntype != "pserver":
175 if conntype == "rsh":
173 if conntype == "rsh":
176 rsh = os.environ.get("CVS_RSH" or "rsh")
174 rsh = os.environ.get("CVS_RSH" or "rsh")
177 if user:
175 if user:
178 cmd = [rsh, '-l', user, host] + cmd
176 cmd = [rsh, '-l', user, host] + cmd
179 else:
177 else:
180 cmd = [rsh, host] + cmd
178 cmd = [rsh, host] + cmd
181
179
182 self.writep, self.readp = os.popen2(cmd)
180 self.writep, self.readp = os.popen2(cmd)
183
181
184 self.realroot = root
182 self.realroot = root
185
183
186 self.writep.write("Root %s\n" % root)
184 self.writep.write("Root %s\n" % root)
187 self.writep.write("Valid-responses ok error Valid-requests Mode"
185 self.writep.write("Valid-responses ok error Valid-requests Mode"
188 " M Mbinary E Checked-in Created Updated"
186 " M Mbinary E Checked-in Created Updated"
189 " Merged Removed\n")
187 " Merged Removed\n")
190 self.writep.write("valid-requests\n")
188 self.writep.write("valid-requests\n")
191 self.writep.flush()
189 self.writep.flush()
192 r = self.readp.readline()
190 r = self.readp.readline()
193 if not r.startswith("Valid-requests"):
191 if not r.startswith("Valid-requests"):
194 raise util.Abort("server sucks")
192 raise util.Abort("server sucks")
195 if "UseUnchanged" in r:
193 if "UseUnchanged" in r:
196 self.writep.write("UseUnchanged\n")
194 self.writep.write("UseUnchanged\n")
197 self.writep.flush()
195 self.writep.flush()
198 r = self.readp.readline()
196 r = self.readp.readline()
199
197
200 def getheads(self):
198 def getheads(self):
201 return self.heads
199 return self.heads
202
200
203 def _getfile(self, name, rev):
201 def _getfile(self, name, rev):
204 if rev.endswith("(DEAD)"):
202 if rev.endswith("(DEAD)"):
205 raise IOError
203 raise IOError
206
204
207 args = ("-N -P -kk -r %s --" % rev).split()
205 args = ("-N -P -kk -r %s --" % rev).split()
208 args.append(os.path.join(self.cvsrepo, name))
206 args.append(os.path.join(self.cvsrepo, name))
209 for x in args:
207 for x in args:
210 self.writep.write("Argument %s\n" % x)
208 self.writep.write("Argument %s\n" % x)
211 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
209 self.writep.write("Directory .\n%s\nco\n" % self.realroot)
212 self.writep.flush()
210 self.writep.flush()
213
211
214 data = ""
212 data = ""
215 while 1:
213 while 1:
216 line = self.readp.readline()
214 line = self.readp.readline()
217 if line.startswith("Created ") or line.startswith("Updated "):
215 if line.startswith("Created ") or line.startswith("Updated "):
218 self.readp.readline() # path
216 self.readp.readline() # path
219 self.readp.readline() # entries
217 self.readp.readline() # entries
220 mode = self.readp.readline()[:-1]
218 mode = self.readp.readline()[:-1]
221 count = int(self.readp.readline()[:-1])
219 count = int(self.readp.readline()[:-1])
222 data = self.readp.read(count)
220 data = self.readp.read(count)
223 elif line.startswith(" "):
221 elif line.startswith(" "):
224 data += line[1:]
222 data += line[1:]
225 elif line.startswith("M "):
223 elif line.startswith("M "):
226 pass
224 pass
227 elif line.startswith("Mbinary "):
225 elif line.startswith("Mbinary "):
228 count = int(self.readp.readline()[:-1])
226 count = int(self.readp.readline()[:-1])
229 data = self.readp.read(count)
227 data = self.readp.read(count)
230 else:
228 else:
231 if line == "ok\n":
229 if line == "ok\n":
232 return (data, "x" in mode and "x" or "")
230 return (data, "x" in mode and "x" or "")
233 elif line.startswith("E "):
231 elif line.startswith("E "):
234 self.ui.warn("cvs server: %s\n" % line[2:])
232 self.ui.warn("cvs server: %s\n" % line[2:])
235 elif line.startswith("Remove"):
233 elif line.startswith("Remove"):
236 l = self.readp.readline()
234 l = self.readp.readline()
237 l = self.readp.readline()
235 l = self.readp.readline()
238 if l != "ok\n":
236 if l != "ok\n":
239 raise util.Abort("unknown CVS response: %s" % l)
237 raise util.Abort("unknown CVS response: %s" % l)
240 else:
238 else:
241 raise util.Abort("unknown CVS response: %s" % line)
239 raise util.Abort("unknown CVS response: %s" % line)
242
240
243 def getfile(self, file, rev):
241 def getfile(self, file, rev):
244 data, mode = self._getfile(file, rev)
242 data, mode = self._getfile(file, rev)
245 self.modecache[(file, rev)] = mode
243 self.modecache[(file, rev)] = mode
246 return data
244 return data
247
245
248 def getmode(self, file, rev):
246 def getmode(self, file, rev):
249 return self.modecache[(file, rev)]
247 return self.modecache[(file, rev)]
250
248
251 def getchanges(self, rev):
249 def getchanges(self, rev):
252 self.modecache = {}
250 self.modecache = {}
253 files = self.files[rev]
251 files = self.files[rev]
254 cl = files.items()
252 cl = files.items()
255 cl.sort()
253 cl.sort()
256 return cl
254 return cl
257
255
258 def getcommit(self, rev):
256 def getcommit(self, rev):
259 return self.changeset[rev]
257 return self.changeset[rev]
260
258
261 def gettags(self):
259 def gettags(self):
262 return self.tags
260 return self.tags
General Comments 0
You need to be logged in to leave comments. Login now