##// END OF EJS Templates
typing: add some assertions that a variable isn't None...
Matt Harbison -
r50748:e63ab79b default
parent child Browse files
Show More
@@ -1,240 +1,242 b''
1 # blackbox.py - log repository events to a file for post-mortem debugging
1 # blackbox.py - log repository events to a file for post-mortem debugging
2 #
2 #
3 # Copyright 2010 Nicolas Dumazet
3 # Copyright 2010 Nicolas Dumazet
4 # Copyright 2013 Facebook, Inc.
4 # Copyright 2013 Facebook, Inc.
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 """log repository events to a blackbox for debugging
9 """log repository events to a blackbox for debugging
10
10
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
11 Logs event information to .hg/blackbox.log to help debug and diagnose problems.
12 The events that get logged can be configured via the blackbox.track and
12 The events that get logged can be configured via the blackbox.track and
13 blackbox.ignore config keys.
13 blackbox.ignore config keys.
14
14
15 Examples::
15 Examples::
16
16
17 [blackbox]
17 [blackbox]
18 track = *
18 track = *
19 ignore = pythonhook
19 ignore = pythonhook
20 # dirty is *EXPENSIVE* (slow);
20 # dirty is *EXPENSIVE* (slow);
21 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
21 # each log entry indicates `+` if the repository is dirty, like :hg:`id`.
22 dirty = True
22 dirty = True
23 # record the source of log messages
23 # record the source of log messages
24 logsource = True
24 logsource = True
25
25
26 [blackbox]
26 [blackbox]
27 track = command, commandfinish, commandexception, exthook, pythonhook
27 track = command, commandfinish, commandexception, exthook, pythonhook
28
28
29 [blackbox]
29 [blackbox]
30 track = incoming
30 track = incoming
31
31
32 [blackbox]
32 [blackbox]
33 # limit the size of a log file
33 # limit the size of a log file
34 maxsize = 1.5 MB
34 maxsize = 1.5 MB
35 # rotate up to N log files when the current one gets too big
35 # rotate up to N log files when the current one gets too big
36 maxfiles = 3
36 maxfiles = 3
37
37
38 [blackbox]
38 [blackbox]
39 # Include microseconds in log entries with %f (see Python function
39 # Include microseconds in log entries with %f (see Python function
40 # datetime.datetime.strftime)
40 # datetime.datetime.strftime)
41 date-format = %Y-%m-%d @ %H:%M:%S.%f
41 date-format = %Y-%m-%d @ %H:%M:%S.%f
42
42
43 """
43 """
44
44
45
45
46 import re
46 import re
47
47
48 from mercurial.i18n import _
48 from mercurial.i18n import _
49 from mercurial.node import hex
49 from mercurial.node import hex
50
50
51 from mercurial import (
51 from mercurial import (
52 encoding,
52 encoding,
53 loggingutil,
53 loggingutil,
54 registrar,
54 registrar,
55 )
55 )
56 from mercurial.utils import (
56 from mercurial.utils import (
57 dateutil,
57 dateutil,
58 procutil,
58 procutil,
59 )
59 )
60
60
61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
61 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
62 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
63 # be specifying the version(s) of Mercurial they are tested with, or
63 # be specifying the version(s) of Mercurial they are tested with, or
64 # leave the attribute unspecified.
64 # leave the attribute unspecified.
65 testedwith = b'ships-with-hg-core'
65 testedwith = b'ships-with-hg-core'
66
66
67 cmdtable = {}
67 cmdtable = {}
68 command = registrar.command(cmdtable)
68 command = registrar.command(cmdtable)
69
69
70 configtable = {}
70 configtable = {}
71 configitem = registrar.configitem(configtable)
71 configitem = registrar.configitem(configtable)
72
72
73 configitem(
73 configitem(
74 b'blackbox',
74 b'blackbox',
75 b'dirty',
75 b'dirty',
76 default=False,
76 default=False,
77 )
77 )
78 configitem(
78 configitem(
79 b'blackbox',
79 b'blackbox',
80 b'maxsize',
80 b'maxsize',
81 default=b'1 MB',
81 default=b'1 MB',
82 )
82 )
83 configitem(
83 configitem(
84 b'blackbox',
84 b'blackbox',
85 b'logsource',
85 b'logsource',
86 default=False,
86 default=False,
87 )
87 )
88 configitem(
88 configitem(
89 b'blackbox',
89 b'blackbox',
90 b'maxfiles',
90 b'maxfiles',
91 default=7,
91 default=7,
92 )
92 )
93 configitem(
93 configitem(
94 b'blackbox',
94 b'blackbox',
95 b'track',
95 b'track',
96 default=lambda: [b'*'],
96 default=lambda: [b'*'],
97 )
97 )
98 configitem(
98 configitem(
99 b'blackbox',
99 b'blackbox',
100 b'ignore',
100 b'ignore',
101 default=lambda: [b'chgserver', b'cmdserver', b'extension'],
101 default=lambda: [b'chgserver', b'cmdserver', b'extension'],
102 )
102 )
103 configitem(b'blackbox', b'date-format', default=b'')
103 configitem(b'blackbox', b'date-format', default=b'')
104
104
105 _lastlogger = loggingutil.proxylogger()
105 _lastlogger = loggingutil.proxylogger()
106
106
107
107
108 class blackboxlogger:
108 class blackboxlogger:
109 def __init__(self, ui, repo):
109 def __init__(self, ui, repo):
110 self._repo = repo
110 self._repo = repo
111 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
111 self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
112 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
112 self._ignoredevents = set(ui.configlist(b'blackbox', b'ignore'))
113 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
113 self._maxfiles = ui.configint(b'blackbox', b'maxfiles')
114 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
114 self._maxsize = ui.configbytes(b'blackbox', b'maxsize')
115 self._inlog = False
115 self._inlog = False
116
116
117 def tracked(self, event):
117 def tracked(self, event):
118 return (
118 return (
119 b'*' in self._trackedevents and event not in self._ignoredevents
119 b'*' in self._trackedevents and event not in self._ignoredevents
120 ) or event in self._trackedevents
120 ) or event in self._trackedevents
121
121
122 def log(self, ui, event, msg, opts):
122 def log(self, ui, event, msg, opts):
123 # self._log() -> ctx.dirty() may create new subrepo instance, which
123 # self._log() -> ctx.dirty() may create new subrepo instance, which
124 # ui is derived from baseui. So the recursion guard in ui.log()
124 # ui is derived from baseui. So the recursion guard in ui.log()
125 # doesn't work as it's local to the ui instance.
125 # doesn't work as it's local to the ui instance.
126 if self._inlog:
126 if self._inlog:
127 return
127 return
128 self._inlog = True
128 self._inlog = True
129 try:
129 try:
130 self._log(ui, event, msg, opts)
130 self._log(ui, event, msg, opts)
131 finally:
131 finally:
132 self._inlog = False
132 self._inlog = False
133
133
134 def _log(self, ui, event, msg, opts):
134 def _log(self, ui, event, msg, opts):
135 default = ui.configdate(b'devel', b'default-date')
135 default = ui.configdate(b'devel', b'default-date')
136 dateformat = ui.config(b'blackbox', b'date-format')
136 dateformat = ui.config(b'blackbox', b'date-format')
137 if dateformat:
137 if dateformat:
138 date = dateutil.datestr(default, dateformat)
138 date = dateutil.datestr(default, dateformat)
139 else:
139 else:
140 # We want to display milliseconds (more precision seems
140 # We want to display milliseconds (more precision seems
141 # unnecessary). Since %.3f is not supported, use %f and truncate
141 # unnecessary). Since %.3f is not supported, use %f and truncate
142 # microseconds.
142 # microseconds.
143 date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3]
143 date = dateutil.datestr(default, b'%Y-%m-%d %H:%M:%S.%f')[:-3]
144 user = procutil.getuser()
144 user = procutil.getuser()
145 pid = b'%d' % procutil.getpid()
145 pid = b'%d' % procutil.getpid()
146 changed = b''
146 changed = b''
147 ctx = self._repo[None]
147 ctx = self._repo[None]
148 parents = ctx.parents()
148 parents = ctx.parents()
149 rev = b'+'.join([hex(p.node()) for p in parents])
149 rev = b'+'.join([hex(p.node()) for p in parents])
150 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
150 if ui.configbool(b'blackbox', b'dirty') and ctx.dirty(
151 missing=True, merge=False, branch=False
151 missing=True, merge=False, branch=False
152 ):
152 ):
153 changed = b'+'
153 changed = b'+'
154 if ui.configbool(b'blackbox', b'logsource'):
154 if ui.configbool(b'blackbox', b'logsource'):
155 src = b' [%s]' % event
155 src = b' [%s]' % event
156 else:
156 else:
157 src = b''
157 src = b''
158 try:
158 try:
159 fmt = b'%s %s @%s%s (%s)%s> %s'
159 fmt = b'%s %s @%s%s (%s)%s> %s'
160 args = (date, user, rev, changed, pid, src, msg)
160 args = (date, user, rev, changed, pid, src, msg)
161 with loggingutil.openlogfile(
161 with loggingutil.openlogfile(
162 ui,
162 ui,
163 self._repo.vfs,
163 self._repo.vfs,
164 name=b'blackbox.log',
164 name=b'blackbox.log',
165 maxfiles=self._maxfiles,
165 maxfiles=self._maxfiles,
166 maxsize=self._maxsize,
166 maxsize=self._maxsize,
167 ) as fp:
167 ) as fp:
168 fp.write(fmt % args)
168 fp.write(fmt % args)
169 except (IOError, OSError) as err:
169 except (IOError, OSError) as err:
170 # deactivate this to avoid failed logging again
170 # deactivate this to avoid failed logging again
171 self._trackedevents.clear()
171 self._trackedevents.clear()
172 ui.debug(
172 ui.debug(
173 b'warning: cannot write to blackbox.log: %s\n'
173 b'warning: cannot write to blackbox.log: %s\n'
174 % encoding.strtolocal(err.strerror)
174 % encoding.strtolocal(err.strerror)
175 )
175 )
176 return
176 return
177 _lastlogger.logger = self
177 _lastlogger.logger = self
178
178
179
179
180 def uipopulate(ui):
180 def uipopulate(ui):
181 ui.setlogger(b'blackbox', _lastlogger)
181 ui.setlogger(b'blackbox', _lastlogger)
182
182
183
183
184 def reposetup(ui, repo):
184 def reposetup(ui, repo):
185 # During 'hg pull' a httppeer repo is created to represent the remote repo.
185 # During 'hg pull' a httppeer repo is created to represent the remote repo.
186 # It doesn't have a .hg directory to put a blackbox in, so we don't do
186 # It doesn't have a .hg directory to put a blackbox in, so we don't do
187 # the blackbox setup for it.
187 # the blackbox setup for it.
188 if not repo.local():
188 if not repo.local():
189 return
189 return
190
190
191 # Since blackbox.log is stored in the repo directory, the logger should be
191 # Since blackbox.log is stored in the repo directory, the logger should be
192 # instantiated per repository.
192 # instantiated per repository.
193 logger = blackboxlogger(ui, repo)
193 logger = blackboxlogger(ui, repo)
194 ui.setlogger(b'blackbox', logger)
194 ui.setlogger(b'blackbox', logger)
195
195
196 # Set _lastlogger even if ui.log is not called. This gives blackbox a
196 # Set _lastlogger even if ui.log is not called. This gives blackbox a
197 # fallback place to log
197 # fallback place to log
198 if _lastlogger.logger is None:
198 if _lastlogger.logger is None:
199 _lastlogger.logger = logger
199 _lastlogger.logger = logger
200
200
201 repo._wlockfreeprefix.add(b'blackbox.log')
201 repo._wlockfreeprefix.add(b'blackbox.log')
202
202
203
203
204 @command(
204 @command(
205 b'blackbox',
205 b'blackbox',
206 [
206 [
207 (b'l', b'limit', 10, _(b'the number of events to show')),
207 (b'l', b'limit', 10, _(b'the number of events to show')),
208 ],
208 ],
209 _(b'hg blackbox [OPTION]...'),
209 _(b'hg blackbox [OPTION]...'),
210 helpcategory=command.CATEGORY_MAINTENANCE,
210 helpcategory=command.CATEGORY_MAINTENANCE,
211 helpbasic=True,
211 helpbasic=True,
212 )
212 )
213 def blackbox(ui, repo, *revs, **opts):
213 def blackbox(ui, repo, *revs, **opts):
214 """view the recent repository events"""
214 """view the recent repository events"""
215
215
216 if not repo.vfs.exists(b'blackbox.log'):
216 if not repo.vfs.exists(b'blackbox.log'):
217 return
217 return
218
218
219 limit = opts.get('limit')
219 limit = opts.get('limit')
220 assert limit is not None # help pytype
221
220 fp = repo.vfs(b'blackbox.log', b'r')
222 fp = repo.vfs(b'blackbox.log', b'r')
221 lines = fp.read().split(b'\n')
223 lines = fp.read().split(b'\n')
222
224
223 count = 0
225 count = 0
224 output = []
226 output = []
225 for line in reversed(lines):
227 for line in reversed(lines):
226 if count >= limit:
228 if count >= limit:
227 break
229 break
228
230
229 # count the commands by matching lines like:
231 # count the commands by matching lines like:
230 # 2013/01/23 19:13:36 root>
232 # 2013/01/23 19:13:36 root>
231 # 2013/01/23 19:13:36 root (1234)>
233 # 2013/01/23 19:13:36 root (1234)>
232 # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)>
234 # 2013/01/23 19:13:36 root @0000000000000000000000000000000000000000 (1234)>
233 # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)>
235 # 2013-01-23 19:13:36.000 root @0000000000000000000000000000000000000000 (1234)>
234 if re.match(
236 if re.match(
235 br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line
237 br'^\d{4}[-/]\d{2}[-/]\d{2} \d{2}:\d{2}:\d{2}(.\d*)? .*> .*', line
236 ):
238 ):
237 count += 1
239 count += 1
238 output.append(line)
240 output.append(line)
239
241
240 ui.status(b'\n'.join(reversed(output)))
242 ui.status(b'\n'.join(reversed(output)))
@@ -1,410 +1,411 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
9 import os
10 import re
10 import re
11
11
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from mercurial.pycompat import open
13 from mercurial.pycompat import open
14 from mercurial import (
14 from mercurial import (
15 error,
15 error,
16 pycompat,
16 pycompat,
17 )
17 )
18 from mercurial.utils import dateutil
18 from mercurial.utils import dateutil
19
19
20 from . import common
20 from . import common
21
21
22
22
23 class monotone_source(common.converter_source, common.commandline):
23 class monotone_source(common.converter_source, common.commandline):
24 def __init__(self, ui, repotype, path=None, revs=None):
24 def __init__(self, ui, repotype, path=None, revs=None):
25 common.converter_source.__init__(self, ui, repotype, path, revs)
25 common.converter_source.__init__(self, ui, repotype, path, revs)
26 if revs and len(revs) > 1:
26 if revs and len(revs) > 1:
27 raise error.Abort(
27 raise error.Abort(
28 _(
28 _(
29 b'monotone source does not support specifying '
29 b'monotone source does not support specifying '
30 b'multiple revs'
30 b'multiple revs'
31 )
31 )
32 )
32 )
33 common.commandline.__init__(self, ui, b'mtn')
33 common.commandline.__init__(self, ui, b'mtn')
34
34
35 self.ui = ui
35 self.ui = ui
36 self.path = path
36 self.path = path
37 self.automatestdio = False
37 self.automatestdio = False
38 self.revs = revs
38 self.revs = revs
39
39
40 norepo = common.NoRepo(
40 norepo = common.NoRepo(
41 _(b"%s does not look like a monotone repository") % path
41 _(b"%s does not look like a monotone repository") % path
42 )
42 )
43 if not os.path.exists(os.path.join(path, b'_MTN')):
43 if not os.path.exists(os.path.join(path, b'_MTN')):
44 # Could be a monotone repository (SQLite db file)
44 # Could be a monotone repository (SQLite db file)
45 try:
45 try:
46 f = open(path, b'rb')
46 f = open(path, b'rb')
47 header = f.read(16)
47 header = f.read(16)
48 f.close()
48 f.close()
49 except IOError:
49 except IOError:
50 header = b''
50 header = b''
51 if header != b'SQLite format 3\x00':
51 if header != b'SQLite format 3\x00':
52 raise norepo
52 raise norepo
53
53
54 # regular expressions for parsing monotone output
54 # regular expressions for parsing monotone output
55 space = br'\s*'
55 space = br'\s*'
56 name = br'\s+"((?:\\"|[^"])*)"\s*'
56 name = br'\s+"((?:\\"|[^"])*)"\s*'
57 value = name
57 value = name
58 revision = br'\s+\[(\w+)\]\s*'
58 revision = br'\s+\[(\w+)\]\s*'
59 lines = br'(?:.|\n)+'
59 lines = br'(?:.|\n)+'
60
60
61 self.dir_re = re.compile(space + b"dir" + name)
61 self.dir_re = re.compile(space + b"dir" + name)
62 self.file_re = re.compile(
62 self.file_re = re.compile(
63 space + b"file" + name + b"content" + revision
63 space + b"file" + name + b"content" + revision
64 )
64 )
65 self.add_file_re = re.compile(
65 self.add_file_re = re.compile(
66 space + b"add_file" + name + b"content" + revision
66 space + b"add_file" + name + b"content" + revision
67 )
67 )
68 self.patch_re = re.compile(
68 self.patch_re = re.compile(
69 space + b"patch" + name + b"from" + revision + b"to" + revision
69 space + b"patch" + name + b"from" + revision + b"to" + revision
70 )
70 )
71 self.rename_re = re.compile(space + b"rename" + name + b"to" + name)
71 self.rename_re = re.compile(space + b"rename" + name + b"to" + name)
72 self.delete_re = re.compile(space + b"delete" + name)
72 self.delete_re = re.compile(space + b"delete" + name)
73 self.tag_re = re.compile(space + b"tag" + name + b"revision" + revision)
73 self.tag_re = re.compile(space + b"tag" + name + b"revision" + revision)
74 self.cert_re = re.compile(
74 self.cert_re = re.compile(
75 lines + space + b"name" + name + b"value" + value
75 lines + space + b"name" + name + b"value" + value
76 )
76 )
77
77
78 attr = space + b"file" + lines + space + b"attr" + space
78 attr = space + b"file" + lines + space + b"attr" + space
79 self.attr_execute_re = re.compile(
79 self.attr_execute_re = re.compile(
80 attr + b'"mtn:execute"' + space + b'"true"'
80 attr + b'"mtn:execute"' + space + b'"true"'
81 )
81 )
82
82
83 # cached data
83 # cached data
84 self.manifest_rev = None
84 self.manifest_rev = None
85 self.manifest = None
85 self.manifest = None
86 self.files = None
86 self.files = None
87 self.dirs = None
87 self.dirs = None
88
88
89 common.checktool(b'mtn', abort=False)
89 common.checktool(b'mtn', abort=False)
90
90
91 def mtnrun(self, *args, **kwargs):
91 def mtnrun(self, *args, **kwargs):
92 if self.automatestdio:
92 if self.automatestdio:
93 return self.mtnrunstdio(*args, **kwargs)
93 return self.mtnrunstdio(*args, **kwargs)
94 else:
94 else:
95 return self.mtnrunsingle(*args, **kwargs)
95 return self.mtnrunsingle(*args, **kwargs)
96
96
97 def mtnrunsingle(self, *args, **kwargs):
97 def mtnrunsingle(self, *args, **kwargs):
98 kwargs['d'] = self.path
98 kwargs['d'] = self.path
99 return self.run0(b'automate', *args, **kwargs)
99 return self.run0(b'automate', *args, **kwargs)
100
100
101 def mtnrunstdio(self, *args, **kwargs):
101 def mtnrunstdio(self, *args, **kwargs):
102 # Prepare the command in automate stdio format
102 # Prepare the command in automate stdio format
103 kwargs = pycompat.byteskwargs(kwargs)
103 kwargs = pycompat.byteskwargs(kwargs)
104 command = []
104 command = []
105 for k, v in kwargs.items():
105 for k, v in kwargs.items():
106 command.append(b"%d:%s" % (len(k), k))
106 command.append(b"%d:%s" % (len(k), k))
107 if v:
107 if v:
108 command.append(b"%d:%s" % (len(v), v))
108 command.append(b"%d:%s" % (len(v), v))
109 if command:
109 if command:
110 command.insert(0, b'o')
110 command.insert(0, b'o')
111 command.append(b'e')
111 command.append(b'e')
112
112
113 command.append(b'l')
113 command.append(b'l')
114 for arg in args:
114 for arg in args:
115 command.append(b"%d:%s" % (len(arg), arg))
115 command.append(b"%d:%s" % (len(arg), arg))
116 command.append(b'e')
116 command.append(b'e')
117 command = b''.join(command)
117 command = b''.join(command)
118
118
119 self.ui.debug(b"mtn: sending '%s'\n" % command)
119 self.ui.debug(b"mtn: sending '%s'\n" % command)
120 self.mtnwritefp.write(command)
120 self.mtnwritefp.write(command)
121 self.mtnwritefp.flush()
121 self.mtnwritefp.flush()
122
122
123 return self.mtnstdioreadcommandoutput(command)
123 return self.mtnstdioreadcommandoutput(command)
124
124
125 def mtnstdioreadpacket(self):
125 def mtnstdioreadpacket(self):
126 read = None
126 read = None
127 commandnbr = b''
127 commandnbr = b''
128 while read != b':':
128 while read != b':':
129 read = self.mtnreadfp.read(1)
129 read = self.mtnreadfp.read(1)
130 if not read:
130 if not read:
131 raise error.Abort(_(b'bad mtn packet - no end of commandnbr'))
131 raise error.Abort(_(b'bad mtn packet - no end of commandnbr'))
132 commandnbr += read
132 commandnbr += read
133 commandnbr = commandnbr[:-1]
133 commandnbr = commandnbr[:-1]
134
134
135 stream = self.mtnreadfp.read(1)
135 stream = self.mtnreadfp.read(1)
136 if stream not in b'mewptl':
136 if stream not in b'mewptl':
137 raise error.Abort(
137 raise error.Abort(
138 _(b'bad mtn packet - bad stream type %s') % stream
138 _(b'bad mtn packet - bad stream type %s') % stream
139 )
139 )
140
140
141 read = self.mtnreadfp.read(1)
141 read = self.mtnreadfp.read(1)
142 if read != b':':
142 if read != b':':
143 raise error.Abort(_(b'bad mtn packet - no divider before size'))
143 raise error.Abort(_(b'bad mtn packet - no divider before size'))
144
144
145 read = None
145 read = None
146 lengthstr = b''
146 lengthstr = b''
147 while read != b':':
147 while read != b':':
148 read = self.mtnreadfp.read(1)
148 read = self.mtnreadfp.read(1)
149 if not read:
149 if not read:
150 raise error.Abort(_(b'bad mtn packet - no end of packet size'))
150 raise error.Abort(_(b'bad mtn packet - no end of packet size'))
151 lengthstr += read
151 lengthstr += read
152 try:
152 try:
153 length = int(lengthstr[:-1])
153 length = int(lengthstr[:-1])
154 except TypeError:
154 except TypeError:
155 raise error.Abort(
155 raise error.Abort(
156 _(b'bad mtn packet - bad packet size %s') % lengthstr
156 _(b'bad mtn packet - bad packet size %s') % lengthstr
157 )
157 )
158
158
159 read = self.mtnreadfp.read(length)
159 read = self.mtnreadfp.read(length)
160 if len(read) != length:
160 if len(read) != length:
161 raise error.Abort(
161 raise error.Abort(
162 _(
162 _(
163 b"bad mtn packet - unable to read full packet "
163 b"bad mtn packet - unable to read full packet "
164 b"read %s of %s"
164 b"read %s of %s"
165 )
165 )
166 % (len(read), length)
166 % (len(read), length)
167 )
167 )
168
168
169 return (commandnbr, stream, length, read)
169 return (commandnbr, stream, length, read)
170
170
171 def mtnstdioreadcommandoutput(self, command):
171 def mtnstdioreadcommandoutput(self, command):
172 retval = []
172 retval = []
173 while True:
173 while True:
174 commandnbr, stream, length, output = self.mtnstdioreadpacket()
174 commandnbr, stream, length, output = self.mtnstdioreadpacket()
175 self.ui.debug(
175 self.ui.debug(
176 b'mtn: read packet %s:%s:%d\n' % (commandnbr, stream, length)
176 b'mtn: read packet %s:%s:%d\n' % (commandnbr, stream, length)
177 )
177 )
178
178
179 if stream == b'l':
179 if stream == b'l':
180 # End of command
180 # End of command
181 if output != b'0':
181 if output != b'0':
182 raise error.Abort(
182 raise error.Abort(
183 _(b"mtn command '%s' returned %s") % (command, output)
183 _(b"mtn command '%s' returned %s") % (command, output)
184 )
184 )
185 break
185 break
186 elif stream in b'ew':
186 elif stream in b'ew':
187 # Error, warning output
187 # Error, warning output
188 self.ui.warn(_(b'%s error:\n') % self.command)
188 self.ui.warn(_(b'%s error:\n') % self.command)
189 self.ui.warn(output)
189 self.ui.warn(output)
190 elif stream == b'p':
190 elif stream == b'p':
191 # Progress messages
191 # Progress messages
192 self.ui.debug(b'mtn: ' + output)
192 self.ui.debug(b'mtn: ' + output)
193 elif stream == b'm':
193 elif stream == b'm':
194 # Main stream - command output
194 # Main stream - command output
195 retval.append(output)
195 retval.append(output)
196
196
197 return b''.join(retval)
197 return b''.join(retval)
198
198
199 def mtnloadmanifest(self, rev):
199 def mtnloadmanifest(self, rev):
200 if self.manifest_rev == rev:
200 if self.manifest_rev == rev:
201 return
201 return
202 self.manifest = self.mtnrun(b"get_manifest_of", rev).split(b"\n\n")
202 self.manifest = self.mtnrun(b"get_manifest_of", rev).split(b"\n\n")
203 self.manifest_rev = rev
203 self.manifest_rev = rev
204 self.files = {}
204 self.files = {}
205 self.dirs = {}
205 self.dirs = {}
206
206
207 for e in self.manifest:
207 for e in self.manifest:
208 m = self.file_re.match(e)
208 m = self.file_re.match(e)
209 if m:
209 if m:
210 attr = b""
210 attr = b""
211 name = m.group(1)
211 name = m.group(1)
212 node = m.group(2)
212 node = m.group(2)
213 if self.attr_execute_re.match(e):
213 if self.attr_execute_re.match(e):
214 attr += b"x"
214 attr += b"x"
215 self.files[name] = (node, attr)
215 self.files[name] = (node, attr)
216 m = self.dir_re.match(e)
216 m = self.dir_re.match(e)
217 if m:
217 if m:
218 self.dirs[m.group(1)] = True
218 self.dirs[m.group(1)] = True
219
219
220 def mtnisfile(self, name, rev):
220 def mtnisfile(self, name, rev):
221 # a non-file could be a directory or a deleted or renamed file
221 # a non-file could be a directory or a deleted or renamed file
222 self.mtnloadmanifest(rev)
222 self.mtnloadmanifest(rev)
223 return name in self.files
223 return name in self.files
224
224
225 def mtnisdir(self, name, rev):
225 def mtnisdir(self, name, rev):
226 self.mtnloadmanifest(rev)
226 self.mtnloadmanifest(rev)
227 return name in self.dirs
227 return name in self.dirs
228
228
229 def mtngetcerts(self, rev):
229 def mtngetcerts(self, rev):
230 certs = {
230 certs = {
231 b"author": b"<missing>",
231 b"author": b"<missing>",
232 b"date": b"<missing>",
232 b"date": b"<missing>",
233 b"changelog": b"<missing>",
233 b"changelog": b"<missing>",
234 b"branch": b"<missing>",
234 b"branch": b"<missing>",
235 }
235 }
236 certlist = self.mtnrun(b"certs", rev)
236 certlist = self.mtnrun(b"certs", rev)
237 # mtn < 0.45:
237 # mtn < 0.45:
238 # key "test@selenic.com"
238 # key "test@selenic.com"
239 # mtn >= 0.45:
239 # mtn >= 0.45:
240 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
240 # key [ff58a7ffb771907c4ff68995eada1c4da068d328]
241 certlist = re.split(br'\n\n {6}key ["\[]', certlist)
241 certlist = re.split(br'\n\n {6}key ["\[]', certlist)
242 for e in certlist:
242 for e in certlist:
243 m = self.cert_re.match(e)
243 m = self.cert_re.match(e)
244 if m:
244 if m:
245 name, value = m.groups()
245 name, value = m.groups()
246 assert value is not None # help pytype
246 value = value.replace(br'\"', b'"')
247 value = value.replace(br'\"', b'"')
247 value = value.replace(br'\\', b'\\')
248 value = value.replace(br'\\', b'\\')
248 certs[name] = value
249 certs[name] = value
249 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
250 # Monotone may have subsecond dates: 2005-02-05T09:39:12.364306
250 # and all times are stored in UTC
251 # and all times are stored in UTC
251 certs[b"date"] = certs[b"date"].split(b'.')[0] + b" UTC"
252 certs[b"date"] = certs[b"date"].split(b'.')[0] + b" UTC"
252 return certs
253 return certs
253
254
254 # implement the converter_source interface:
255 # implement the converter_source interface:
255
256
256 def getheads(self):
257 def getheads(self):
257 if not self.revs:
258 if not self.revs:
258 return self.mtnrun(b"leaves").splitlines()
259 return self.mtnrun(b"leaves").splitlines()
259 else:
260 else:
260 return self.revs
261 return self.revs
261
262
262 def getchanges(self, rev, full):
263 def getchanges(self, rev, full):
263 if full:
264 if full:
264 raise error.Abort(
265 raise error.Abort(
265 _(b"convert from monotone does not support --full")
266 _(b"convert from monotone does not support --full")
266 )
267 )
267 revision = self.mtnrun(b"get_revision", rev).split(b"\n\n")
268 revision = self.mtnrun(b"get_revision", rev).split(b"\n\n")
268 files = {}
269 files = {}
269 ignoremove = {}
270 ignoremove = {}
270 renameddirs = []
271 renameddirs = []
271 copies = {}
272 copies = {}
272 for e in revision:
273 for e in revision:
273 m = self.add_file_re.match(e)
274 m = self.add_file_re.match(e)
274 if m:
275 if m:
275 files[m.group(1)] = rev
276 files[m.group(1)] = rev
276 ignoremove[m.group(1)] = rev
277 ignoremove[m.group(1)] = rev
277 m = self.patch_re.match(e)
278 m = self.patch_re.match(e)
278 if m:
279 if m:
279 files[m.group(1)] = rev
280 files[m.group(1)] = rev
280 # Delete/rename is handled later when the convert engine
281 # Delete/rename is handled later when the convert engine
281 # discovers an IOError exception from getfile,
282 # discovers an IOError exception from getfile,
282 # but only if we add the "from" file to the list of changes.
283 # but only if we add the "from" file to the list of changes.
283 m = self.delete_re.match(e)
284 m = self.delete_re.match(e)
284 if m:
285 if m:
285 files[m.group(1)] = rev
286 files[m.group(1)] = rev
286 m = self.rename_re.match(e)
287 m = self.rename_re.match(e)
287 if m:
288 if m:
288 toname = m.group(2)
289 toname = m.group(2)
289 fromname = m.group(1)
290 fromname = m.group(1)
290 if self.mtnisfile(toname, rev):
291 if self.mtnisfile(toname, rev):
291 ignoremove[toname] = 1
292 ignoremove[toname] = 1
292 copies[toname] = fromname
293 copies[toname] = fromname
293 files[toname] = rev
294 files[toname] = rev
294 files[fromname] = rev
295 files[fromname] = rev
295 elif self.mtnisdir(toname, rev):
296 elif self.mtnisdir(toname, rev):
296 renameddirs.append((fromname, toname))
297 renameddirs.append((fromname, toname))
297
298
298 # Directory renames can be handled only once we have recorded
299 # Directory renames can be handled only once we have recorded
299 # all new files
300 # all new files
300 for fromdir, todir in renameddirs:
301 for fromdir, todir in renameddirs:
301 renamed = {}
302 renamed = {}
302 for tofile in self.files:
303 for tofile in self.files:
303 if tofile in ignoremove:
304 if tofile in ignoremove:
304 continue
305 continue
305 if tofile.startswith(todir + b'/'):
306 if tofile.startswith(todir + b'/'):
306 renamed[tofile] = fromdir + tofile[len(todir) :]
307 renamed[tofile] = fromdir + tofile[len(todir) :]
307 # Avoid chained moves like:
308 # Avoid chained moves like:
308 # d1(/a) => d3/d1(/a)
309 # d1(/a) => d3/d1(/a)
309 # d2 => d3
310 # d2 => d3
310 ignoremove[tofile] = 1
311 ignoremove[tofile] = 1
311 for tofile, fromfile in renamed.items():
312 for tofile, fromfile in renamed.items():
312 self.ui.debug(
313 self.ui.debug(
313 b"copying file in renamed directory from '%s' to '%s'"
314 b"copying file in renamed directory from '%s' to '%s'"
314 % (fromfile, tofile),
315 % (fromfile, tofile),
315 b'\n',
316 b'\n',
316 )
317 )
317 files[tofile] = rev
318 files[tofile] = rev
318 copies[tofile] = fromfile
319 copies[tofile] = fromfile
319 for fromfile in renamed.values():
320 for fromfile in renamed.values():
320 files[fromfile] = rev
321 files[fromfile] = rev
321
322
322 return (files.items(), copies, set())
323 return (files.items(), copies, set())
323
324
324 def getfile(self, name, rev):
325 def getfile(self, name, rev):
325 if not self.mtnisfile(name, rev):
326 if not self.mtnisfile(name, rev):
326 return None, None
327 return None, None
327 try:
328 try:
328 data = self.mtnrun(b"get_file_of", name, r=rev)
329 data = self.mtnrun(b"get_file_of", name, r=rev)
329 except Exception:
330 except Exception:
330 return None, None
331 return None, None
331 self.mtnloadmanifest(rev)
332 self.mtnloadmanifest(rev)
332 node, attr = self.files.get(name, (None, b""))
333 node, attr = self.files.get(name, (None, b""))
333 return data, attr
334 return data, attr
334
335
335 def getcommit(self, rev):
336 def getcommit(self, rev):
336 extra = {}
337 extra = {}
337 certs = self.mtngetcerts(rev)
338 certs = self.mtngetcerts(rev)
338 if certs.get(b'suspend') == certs[b"branch"]:
339 if certs.get(b'suspend') == certs[b"branch"]:
339 extra[b'close'] = b'1'
340 extra[b'close'] = b'1'
340 dateformat = b"%Y-%m-%dT%H:%M:%S"
341 dateformat = b"%Y-%m-%dT%H:%M:%S"
341 return common.commit(
342 return common.commit(
342 author=certs[b"author"],
343 author=certs[b"author"],
343 date=dateutil.datestr(dateutil.strdate(certs[b"date"], dateformat)),
344 date=dateutil.datestr(dateutil.strdate(certs[b"date"], dateformat)),
344 desc=certs[b"changelog"],
345 desc=certs[b"changelog"],
345 rev=rev,
346 rev=rev,
346 parents=self.mtnrun(b"parents", rev).splitlines(),
347 parents=self.mtnrun(b"parents", rev).splitlines(),
347 branch=certs[b"branch"],
348 branch=certs[b"branch"],
348 extra=extra,
349 extra=extra,
349 )
350 )
350
351
351 def gettags(self):
352 def gettags(self):
352 tags = {}
353 tags = {}
353 for e in self.mtnrun(b"tags").split(b"\n\n"):
354 for e in self.mtnrun(b"tags").split(b"\n\n"):
354 m = self.tag_re.match(e)
355 m = self.tag_re.match(e)
355 if m:
356 if m:
356 tags[m.group(1)] = m.group(2)
357 tags[m.group(1)] = m.group(2)
357 return tags
358 return tags
358
359
359 def getchangedfiles(self, rev, i):
360 def getchangedfiles(self, rev, i):
360 # This function is only needed to support --filemap
361 # This function is only needed to support --filemap
361 # ... and we don't support that
362 # ... and we don't support that
362 raise NotImplementedError
363 raise NotImplementedError
363
364
364 def before(self):
365 def before(self):
365 # Check if we have a new enough version to use automate stdio
366 # Check if we have a new enough version to use automate stdio
366 try:
367 try:
367 versionstr = self.mtnrunsingle(b"interface_version")
368 versionstr = self.mtnrunsingle(b"interface_version")
368 version = float(versionstr)
369 version = float(versionstr)
369 except Exception:
370 except Exception:
370 raise error.Abort(
371 raise error.Abort(
371 _(b"unable to determine mtn automate interface version")
372 _(b"unable to determine mtn automate interface version")
372 )
373 )
373
374
374 if version >= 12.0:
375 if version >= 12.0:
375 self.automatestdio = True
376 self.automatestdio = True
376 self.ui.debug(
377 self.ui.debug(
377 b"mtn automate version %f - using automate stdio\n" % version
378 b"mtn automate version %f - using automate stdio\n" % version
378 )
379 )
379
380
380 # launch the long-running automate stdio process
381 # launch the long-running automate stdio process
381 self.mtnwritefp, self.mtnreadfp = self._run2(
382 self.mtnwritefp, self.mtnreadfp = self._run2(
382 b'automate', b'stdio', b'-d', self.path
383 b'automate', b'stdio', b'-d', self.path
383 )
384 )
384 # read the headers
385 # read the headers
385 read = self.mtnreadfp.readline()
386 read = self.mtnreadfp.readline()
386 if read != b'format-version: 2\n':
387 if read != b'format-version: 2\n':
387 raise error.Abort(
388 raise error.Abort(
388 _(b'mtn automate stdio header unexpected: %s') % read
389 _(b'mtn automate stdio header unexpected: %s') % read
389 )
390 )
390 while read != b'\n':
391 while read != b'\n':
391 read = self.mtnreadfp.readline()
392 read = self.mtnreadfp.readline()
392 if not read:
393 if not read:
393 raise error.Abort(
394 raise error.Abort(
394 _(
395 _(
395 b"failed to reach end of mtn automate "
396 b"failed to reach end of mtn automate "
396 b"stdio headers"
397 b"stdio headers"
397 )
398 )
398 )
399 )
399 else:
400 else:
400 self.ui.debug(
401 self.ui.debug(
401 b"mtn automate version %s - not using automate stdio "
402 b"mtn automate version %s - not using automate stdio "
402 b"(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
403 b"(automate >= 12.0 - mtn >= 0.46 is needed)\n" % version
403 )
404 )
404
405
405 def after(self):
406 def after(self):
406 if self.automatestdio:
407 if self.automatestdio:
407 self.mtnwritefp.close()
408 self.mtnwritefp.close()
408 self.mtnwritefp = None
409 self.mtnwritefp = None
409 self.mtnreadfp.close()
410 self.mtnreadfp.close()
410 self.mtnreadfp = None
411 self.mtnreadfp = None
General Comments 0
You need to be logged in to leave comments. Login now