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