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