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