##// END OF EJS Templates
convert: use bytes for value in extra dict...
Manuel Jacob -
r45652:e699cebc stable
parent child Browse files
Show More
@@ -1,411 +1,411
1 1 # monotone.py - monotone support for the convert extension
2 2 #
3 3 # Copyright 2008, 2009 Mikkel Fahnoe Jorgensen <mikkel@dvide.com> and
4 4 # others
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8 from __future__ import absolute_import
9 9
10 10 import os
11 11 import re
12 12
13 13 from mercurial.i18n import _
14 14 from mercurial.pycompat import open
15 15 from mercurial import (
16 16 error,
17 17 pycompat,
18 18 )
19 19 from mercurial.utils import dateutil
20 20
21 21 from . import common
22 22
23 23
24 24 class monotone_source(common.converter_source, common.commandline):
25 25 def __init__(self, ui, repotype, path=None, revs=None):
26 26 common.converter_source.__init__(self, ui, repotype, path, revs)
27 27 if revs and len(revs) > 1:
28 28 raise error.Abort(
29 29 _(
30 30 b'monotone source does not support specifying '
31 31 b'multiple revs'
32 32 )
33 33 )
34 34 common.commandline.__init__(self, ui, b'mtn')
35 35
36 36 self.ui = ui
37 37 self.path = path
38 38 self.automatestdio = False
39 39 self.revs = revs
40 40
41 41 norepo = common.NoRepo(
42 42 _(b"%s does not look like a monotone repository") % path
43 43 )
44 44 if not os.path.exists(os.path.join(path, b'_MTN')):
45 45 # Could be a monotone repository (SQLite db file)
46 46 try:
47 47 f = open(path, b'rb')
48 48 header = f.read(16)
49 49 f.close()
50 50 except IOError:
51 51 header = b''
52 52 if header != b'SQLite format 3\x00':
53 53 raise norepo
54 54
55 55 # regular expressions for parsing monotone output
56 56 space = br'\s*'
57 57 name = br'\s+"((?:\\"|[^"])*)"\s*'
58 58 value = name
59 59 revision = br'\s+\[(\w+)\]\s*'
60 60 lines = br'(?:.|\n)+'
61 61
62 62 self.dir_re = re.compile(space + b"dir" + name)
63 63 self.file_re = re.compile(
64 64 space + b"file" + name + b"content" + revision
65 65 )
66 66 self.add_file_re = re.compile(
67 67 space + b"add_file" + name + b"content" + revision
68 68 )
69 69 self.patch_re = re.compile(
70 70 space + b"patch" + name + b"from" + revision + b"to" + revision
71 71 )
72 72 self.rename_re = re.compile(space + b"rename" + name + b"to" + name)
73 73 self.delete_re = re.compile(space + b"delete" + name)
74 74 self.tag_re = re.compile(space + b"tag" + name + b"revision" + revision)
75 75 self.cert_re = re.compile(
76 76 lines + space + b"name" + name + b"value" + value
77 77 )
78 78
79 79 attr = space + b"file" + lines + space + b"attr" + space
80 80 self.attr_execute_re = re.compile(
81 81 attr + b'"mtn:execute"' + space + b'"true"'
82 82 )
83 83
84 84 # cached data
85 85 self.manifest_rev = None
86 86 self.manifest = None
87 87 self.files = None
88 88 self.dirs = None
89 89
90 90 common.checktool(b'mtn', abort=False)
91 91
92 92 def mtnrun(self, *args, **kwargs):
93 93 if self.automatestdio:
94 94 return self.mtnrunstdio(*args, **kwargs)
95 95 else:
96 96 return self.mtnrunsingle(*args, **kwargs)
97 97
98 98 def mtnrunsingle(self, *args, **kwargs):
99 99 kwargs['d'] = self.path
100 100 return self.run0(b'automate', *args, **kwargs)
101 101
102 102 def mtnrunstdio(self, *args, **kwargs):
103 103 # Prepare the command in automate stdio format
104 104 kwargs = pycompat.byteskwargs(kwargs)
105 105 command = []
106 106 for k, v in pycompat.iteritems(kwargs):
107 107 command.append(b"%d:%s" % (len(k), k))
108 108 if v:
109 109 command.append(b"%d:%s" % (len(v), v))
110 110 if command:
111 111 command.insert(0, b'o')
112 112 command.append(b'e')
113 113
114 114 command.append(b'l')
115 115 for arg in args:
116 116 command.append(b"%d:%s" % (len(arg), arg))
117 117 command.append(b'e')
118 118 command = b''.join(command)
119 119
120 120 self.ui.debug(b"mtn: sending '%s'\n" % command)
121 121 self.mtnwritefp.write(command)
122 122 self.mtnwritefp.flush()
123 123
124 124 return self.mtnstdioreadcommandoutput(command)
125 125
126 126 def mtnstdioreadpacket(self):
127 127 read = None
128 128 commandnbr = b''
129 129 while read != b':':
130 130 read = self.mtnreadfp.read(1)
131 131 if not read:
132 132 raise error.Abort(_(b'bad mtn packet - no end of commandnbr'))
133 133 commandnbr += read
134 134 commandnbr = commandnbr[:-1]
135 135
136 136 stream = self.mtnreadfp.read(1)
137 137 if stream not in b'mewptl':
138 138 raise error.Abort(
139 139 _(b'bad mtn packet - bad stream type %s') % stream
140 140 )
141 141
142 142 read = self.mtnreadfp.read(1)
143 143 if read != b':':
144 144 raise error.Abort(_(b'bad mtn packet - no divider before size'))
145 145
146 146 read = None
147 147 lengthstr = b''
148 148 while read != b':':
149 149 read = self.mtnreadfp.read(1)
150 150 if not read:
151 151 raise error.Abort(_(b'bad mtn packet - no end of packet size'))
152 152 lengthstr += read
153 153 try:
154 154 length = pycompat.long(lengthstr[:-1])
155 155 except TypeError:
156 156 raise error.Abort(
157 157 _(b'bad mtn packet - bad packet size %s') % lengthstr
158 158 )
159 159
160 160 read = self.mtnreadfp.read(length)
161 161 if len(read) != length:
162 162 raise error.Abort(
163 163 _(
164 164 b"bad mtn packet - unable to read full packet "
165 165 b"read %s of %s"
166 166 )
167 167 % (len(read), length)
168 168 )
169 169
170 170 return (commandnbr, stream, length, read)
171 171
172 172 def mtnstdioreadcommandoutput(self, command):
173 173 retval = []
174 174 while True:
175 175 commandnbr, stream, length, output = self.mtnstdioreadpacket()
176 176 self.ui.debug(
177 177 b'mtn: read packet %s:%s:%d\n' % (commandnbr, stream, length)
178 178 )
179 179
180 180 if stream == b'l':
181 181 # End of command
182 182 if output != b'0':
183 183 raise error.Abort(
184 184 _(b"mtn command '%s' returned %s") % (command, output)
185 185 )
186 186 break
187 187 elif stream in b'ew':
188 188 # Error, warning output
189 189 self.ui.warn(_(b'%s error:\n') % self.command)
190 190 self.ui.warn(output)
191 191 elif stream == b'p':
192 192 # Progress messages
193 193 self.ui.debug(b'mtn: ' + output)
194 194 elif stream == b'm':
195 195 # Main stream - command output
196 196 retval.append(output)
197 197
198 198 return b''.join(retval)
199 199
200 200 def mtnloadmanifest(self, rev):
201 201 if self.manifest_rev == rev:
202 202 return
203 203 self.manifest = self.mtnrun(b"get_manifest_of", rev).split(b"\n\n")
204 204 self.manifest_rev = rev
205 205 self.files = {}
206 206 self.dirs = {}
207 207
208 208 for e in self.manifest:
209 209 m = self.file_re.match(e)
210 210 if m:
211 211 attr = b""
212 212 name = m.group(1)
213 213 node = m.group(2)
214 214 if self.attr_execute_re.match(e):
215 215 attr += b"x"
216 216 self.files[name] = (node, attr)
217 217 m = self.dir_re.match(e)
218 218 if m:
219 219 self.dirs[m.group(1)] = True
220 220
221 221 def mtnisfile(self, name, rev):
222 222 # a non-file could be a directory or a deleted or renamed file
223 223 self.mtnloadmanifest(rev)
224 224 return name in self.files
225 225
226 226 def mtnisdir(self, name, rev):
227 227 self.mtnloadmanifest(rev)
228 228 return name in self.dirs
229 229
230 230 def mtngetcerts(self, rev):
231 231 certs = {
232 232 b"author": b"<missing>",
233 233 b"date": b"<missing>",
234 234 b"changelog": b"<missing>",
235 235 b"branch": b"<missing>",
236 236 }
237 237 certlist = self.mtnrun(b"certs", rev)
238 238 # mtn < 0.45:
239 239 # key "test@selenic.com"
240 240 # mtn >= 0.45:
241 241 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
242 242 certlist = re.split(br'\n\n {6}key ["\[]', certlist)
243 243 for e in certlist:
244 244 m = self.cert_re.match(e)
245 245 if m:
246 246 name, value = m.groups()
247 247 value = value.replace(br'\"', b'"')
248 248 value = value.replace(br'\\', b'\\')
249 249 certs[name] = value
250 250 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
251 251 # and all times are stored in UTC
252 252 certs[b"date"] = certs[b"date"].split(b'.')[0] + b" UTC"
253 253 return certs
254 254
255 255 # implement the converter_source interface:
256 256
257 257 def getheads(self):
258 258 if not self.revs:
259 259 return self.mtnrun(b"leaves").splitlines()
260 260 else:
261 261 return self.revs
262 262
263 263 def getchanges(self, rev, full):
264 264 if full:
265 265 raise error.Abort(
266 266 _(b"convert from monotone does not support --full")
267 267 )
268 268 revision = self.mtnrun(b"get_revision", rev).split(b"\n\n")
269 269 files = {}
270 270 ignoremove = {}
271 271 renameddirs = []
272 272 copies = {}
273 273 for e in revision:
274 274 m = self.add_file_re.match(e)
275 275 if m:
276 276 files[m.group(1)] = rev
277 277 ignoremove[m.group(1)] = rev
278 278 m = self.patch_re.match(e)
279 279 if m:
280 280 files[m.group(1)] = rev
281 281 # Delete/rename is handled later when the convert engine
282 282 # discovers an IOError exception from getfile,
283 283 # but only if we add the "from" file to the list of changes.
284 284 m = self.delete_re.match(e)
285 285 if m:
286 286 files[m.group(1)] = rev
287 287 m = self.rename_re.match(e)
288 288 if m:
289 289 toname = m.group(2)
290 290 fromname = m.group(1)
291 291 if self.mtnisfile(toname, rev):
292 292 ignoremove[toname] = 1
293 293 copies[toname] = fromname
294 294 files[toname] = rev
295 295 files[fromname] = rev
296 296 elif self.mtnisdir(toname, rev):
297 297 renameddirs.append((fromname, toname))
298 298
299 299 # Directory renames can be handled only once we have recorded
300 300 # all new files
301 301 for fromdir, todir in renameddirs:
302 302 renamed = {}
303 303 for tofile in self.files:
304 304 if tofile in ignoremove:
305 305 continue
306 306 if tofile.startswith(todir + b'/'):
307 307 renamed[tofile] = fromdir + tofile[len(todir) :]
308 308 # Avoid chained moves like:
309 309 # d1(/a) => d3/d1(/a)
310 310 # d2 => d3
311 311 ignoremove[tofile] = 1
312 312 for tofile, fromfile in renamed.items():
313 313 self.ui.debug(
314 314 b"copying file in renamed directory from '%s' to '%s'"
315 315 % (fromfile, tofile),
316 316 b'\n',
317 317 )
318 318 files[tofile] = rev
319 319 copies[tofile] = fromfile
320 320 for fromfile in renamed.values():
321 321 files[fromfile] = rev
322 322
323 323 return (files.items(), copies, set())
324 324
325 325 def getfile(self, name, rev):
326 326 if not self.mtnisfile(name, rev):
327 327 return None, None
328 328 try:
329 329 data = self.mtnrun(b"get_file_of", name, r=rev)
330 330 except Exception:
331 331 return None, None
332 332 self.mtnloadmanifest(rev)
333 333 node, attr = self.files.get(name, (None, b""))
334 334 return data, attr
335 335
336 336 def getcommit(self, rev):
337 337 extra = {}
338 338 certs = self.mtngetcerts(rev)
339 339 if certs.get(b'suspend') == certs[b"branch"]:
340 extra[b'close'] = 1
340 extra[b'close'] = b'1'
341 341 dateformat = b"%Y-%m-%dT%H:%M:%S"
342 342 return common.commit(
343 343 author=certs[b"author"],
344 344 date=dateutil.datestr(dateutil.strdate(certs[b"date"], dateformat)),
345 345 desc=certs[b"changelog"],
346 346 rev=rev,
347 347 parents=self.mtnrun(b"parents", rev).splitlines(),
348 348 branch=certs[b"branch"],
349 349 extra=extra,
350 350 )
351 351
352 352 def gettags(self):
353 353 tags = {}
354 354 for e in self.mtnrun(b"tags").split(b"\n\n"):
355 355 m = self.tag_re.match(e)
356 356 if m:
357 357 tags[m.group(1)] = m.group(2)
358 358 return tags
359 359
360 360 def getchangedfiles(self, rev, i):
361 361 # This function is only needed to support --filemap
362 362 # ... and we don't support that
363 363 raise NotImplementedError
364 364
365 365 def before(self):
366 366 # Check if we have a new enough version to use automate stdio
367 367 try:
368 368 versionstr = self.mtnrunsingle(b"interface_version")
369 369 version = float(versionstr)
370 370 except Exception:
371 371 raise error.Abort(
372 372 _(b"unable to determine mtn automate interface version")
373 373 )
374 374
375 375 if version >= 12.0:
376 376 self.automatestdio = True
377 377 self.ui.debug(
378 378 b"mtn automate version %f - using automate stdio\n" % version
379 379 )
380 380
381 381 # launch the long-running automate stdio process
382 382 self.mtnwritefp, self.mtnreadfp = self._run2(
383 383 b'automate', b'stdio', b'-d', self.path
384 384 )
385 385 # read the headers
386 386 read = self.mtnreadfp.readline()
387 387 if read != b'format-version: 2\n':
388 388 raise error.Abort(
389 389 _(b'mtn automate stdio header unexpected: %s') % read
390 390 )
391 391 while read != b'\n':
392 392 read = self.mtnreadfp.readline()
393 393 if not read:
394 394 raise error.Abort(
395 395 _(
396 396 b"failed to reach end of mtn automate "
397 397 b"stdio headers"
398 398 )
399 399 )
400 400 else:
401 401 self.ui.debug(
402 402 b"mtn automate version %s - not using automate stdio "
403 403 b"(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
404 404 )
405 405
406 406 def after(self):
407 407 if self.automatestdio:
408 408 self.mtnwritefp.close()
409 409 self.mtnwritefp = None
410 410 self.mtnreadfp.close()
411 411 self.mtnreadfp = None
General Comments 0
You need to be logged in to leave comments. Login now