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