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