##// END OF EJS Templates
formatter: add option to redirect output to file object...
Yuya Nishihara -
r32573:012e0da5 default
parent child Browse files
Show More
@@ -1,430 +1,434 b''
1 # formatter.py - generic output formatting for mercurial
1 # formatter.py - generic output formatting for mercurial
2 #
2 #
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
3 # Copyright 2012 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 """Generic output formatting for Mercurial
8 """Generic output formatting for Mercurial
9
9
10 The formatter provides API to show data in various ways. The following
10 The formatter provides API to show data in various ways. The following
11 functions should be used in place of ui.write():
11 functions should be used in place of ui.write():
12
12
13 - fm.write() for unconditional output
13 - fm.write() for unconditional output
14 - fm.condwrite() to show some extra data conditionally in plain output
14 - fm.condwrite() to show some extra data conditionally in plain output
15 - fm.context() to provide changectx to template output
15 - fm.context() to provide changectx to template output
16 - fm.data() to provide extra data to JSON or template output
16 - fm.data() to provide extra data to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
17 - fm.plain() to show raw text that isn't provided to JSON or template output
18
18
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
19 To show structured data (e.g. date tuples, dicts, lists), apply fm.format*()
20 beforehand so the data is converted to the appropriate data type. Use
20 beforehand so the data is converted to the appropriate data type. Use
21 fm.isplain() if you need to convert or format data conditionally which isn't
21 fm.isplain() if you need to convert or format data conditionally which isn't
22 supported by the formatter API.
22 supported by the formatter API.
23
23
24 To build nested structure (i.e. a list of dicts), use fm.nested().
24 To build nested structure (i.e. a list of dicts), use fm.nested().
25
25
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
26 See also https://www.mercurial-scm.org/wiki/GenericTemplatingPlan
27
27
28 fm.condwrite() vs 'if cond:':
28 fm.condwrite() vs 'if cond:':
29
29
30 In most cases, use fm.condwrite() so users can selectively show the data
30 In most cases, use fm.condwrite() so users can selectively show the data
31 in template output. If it's costly to build data, use plain 'if cond:' with
31 in template output. If it's costly to build data, use plain 'if cond:' with
32 fm.write().
32 fm.write().
33
33
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
34 fm.nested() vs fm.formatdict() (or fm.formatlist()):
35
35
36 fm.nested() should be used to form a tree structure (a list of dicts of
36 fm.nested() should be used to form a tree structure (a list of dicts of
37 lists of dicts...) which can be accessed through template keywords, e.g.
37 lists of dicts...) which can be accessed through template keywords, e.g.
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
38 "{foo % "{bar % {...}} {baz % {...}}"}". On the other hand, fm.formatdict()
39 exports a dict-type object to template, which can be accessed by e.g.
39 exports a dict-type object to template, which can be accessed by e.g.
40 "{get(foo, key)}" function.
40 "{get(foo, key)}" function.
41
41
42 Doctest helper:
42 Doctest helper:
43
43
44 >>> def show(fn, verbose=False, **opts):
44 >>> def show(fn, verbose=False, **opts):
45 ... import sys
45 ... import sys
46 ... from . import ui as uimod
46 ... from . import ui as uimod
47 ... ui = uimod.ui()
47 ... ui = uimod.ui()
48 ... ui.fout = sys.stdout # redirect to doctest
48 ... ui.fout = sys.stdout # redirect to doctest
49 ... ui.verbose = verbose
49 ... ui.verbose = verbose
50 ... return fn(ui, ui.formatter(fn.__name__, opts))
50 ... return fn(ui, ui.formatter(fn.__name__, opts))
51
51
52 Basic example:
52 Basic example:
53
53
54 >>> def files(ui, fm):
54 >>> def files(ui, fm):
55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
55 ... files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
56 ... for f in files:
56 ... for f in files:
57 ... fm.startitem()
57 ... fm.startitem()
58 ... fm.write('path', '%s', f[0])
58 ... fm.write('path', '%s', f[0])
59 ... fm.condwrite(ui.verbose, 'date', ' %s',
59 ... fm.condwrite(ui.verbose, 'date', ' %s',
60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
60 ... fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
61 ... fm.data(size=f[1])
61 ... fm.data(size=f[1])
62 ... fm.plain('\\n')
62 ... fm.plain('\\n')
63 ... fm.end()
63 ... fm.end()
64 >>> show(files)
64 >>> show(files)
65 foo
65 foo
66 bar
66 bar
67 >>> show(files, verbose=True)
67 >>> show(files, verbose=True)
68 foo 1970-01-01 00:00:00
68 foo 1970-01-01 00:00:00
69 bar 1970-01-01 00:00:01
69 bar 1970-01-01 00:00:01
70 >>> show(files, template='json')
70 >>> show(files, template='json')
71 [
71 [
72 {
72 {
73 "date": [0, 0],
73 "date": [0, 0],
74 "path": "foo",
74 "path": "foo",
75 "size": 123
75 "size": 123
76 },
76 },
77 {
77 {
78 "date": [1, 0],
78 "date": [1, 0],
79 "path": "bar",
79 "path": "bar",
80 "size": 456
80 "size": 456
81 }
81 }
82 ]
82 ]
83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
83 >>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
84 path: foo
84 path: foo
85 date: 1970-01-01T00:00:00+00:00
85 date: 1970-01-01T00:00:00+00:00
86 path: bar
86 path: bar
87 date: 1970-01-01T00:00:01+00:00
87 date: 1970-01-01T00:00:01+00:00
88
88
89 Nested example:
89 Nested example:
90
90
91 >>> def subrepos(ui, fm):
91 >>> def subrepos(ui, fm):
92 ... fm.startitem()
92 ... fm.startitem()
93 ... fm.write('repo', '[%s]\\n', 'baz')
93 ... fm.write('repo', '[%s]\\n', 'baz')
94 ... files(ui, fm.nested('files'))
94 ... files(ui, fm.nested('files'))
95 ... fm.end()
95 ... fm.end()
96 >>> show(subrepos)
96 >>> show(subrepos)
97 [baz]
97 [baz]
98 foo
98 foo
99 bar
99 bar
100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
100 >>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
101 baz: foo, bar
101 baz: foo, bar
102 """
102 """
103
103
104 from __future__ import absolute_import
104 from __future__ import absolute_import
105
105
106 import itertools
106 import itertools
107 import os
107 import os
108
108
109 from .i18n import _
109 from .i18n import _
110 from .node import (
110 from .node import (
111 hex,
111 hex,
112 short,
112 short,
113 )
113 )
114
114
115 from . import (
115 from . import (
116 error,
116 error,
117 pycompat,
117 pycompat,
118 templatefilters,
118 templatefilters,
119 templatekw,
119 templatekw,
120 templater,
120 templater,
121 util,
121 util,
122 )
122 )
123
123
124 pickle = util.pickle
124 pickle = util.pickle
125
125
126 class _nullconverter(object):
126 class _nullconverter(object):
127 '''convert non-primitive data types to be processed by formatter'''
127 '''convert non-primitive data types to be processed by formatter'''
128 @staticmethod
128 @staticmethod
129 def formatdate(date, fmt):
129 def formatdate(date, fmt):
130 '''convert date tuple to appropriate format'''
130 '''convert date tuple to appropriate format'''
131 return date
131 return date
132 @staticmethod
132 @staticmethod
133 def formatdict(data, key, value, fmt, sep):
133 def formatdict(data, key, value, fmt, sep):
134 '''convert dict or key-value pairs to appropriate dict format'''
134 '''convert dict or key-value pairs to appropriate dict format'''
135 # use plain dict instead of util.sortdict so that data can be
135 # use plain dict instead of util.sortdict so that data can be
136 # serialized as a builtin dict in pickle output
136 # serialized as a builtin dict in pickle output
137 return dict(data)
137 return dict(data)
138 @staticmethod
138 @staticmethod
139 def formatlist(data, name, fmt, sep):
139 def formatlist(data, name, fmt, sep):
140 '''convert iterable to appropriate list format'''
140 '''convert iterable to appropriate list format'''
141 return list(data)
141 return list(data)
142
142
143 class baseformatter(object):
143 class baseformatter(object):
144 def __init__(self, ui, topic, opts, converter):
144 def __init__(self, ui, topic, opts, converter):
145 self._ui = ui
145 self._ui = ui
146 self._topic = topic
146 self._topic = topic
147 self._style = opts.get("style")
147 self._style = opts.get("style")
148 self._template = opts.get("template")
148 self._template = opts.get("template")
149 self._converter = converter
149 self._converter = converter
150 self._item = None
150 self._item = None
151 # function to convert node to string suitable for this output
151 # function to convert node to string suitable for this output
152 self.hexfunc = hex
152 self.hexfunc = hex
153 def __enter__(self):
153 def __enter__(self):
154 return self
154 return self
155 def __exit__(self, exctype, excvalue, traceback):
155 def __exit__(self, exctype, excvalue, traceback):
156 if exctype is None:
156 if exctype is None:
157 self.end()
157 self.end()
158 def _showitem(self):
158 def _showitem(self):
159 '''show a formatted item once all data is collected'''
159 '''show a formatted item once all data is collected'''
160 pass
160 pass
161 def startitem(self):
161 def startitem(self):
162 '''begin an item in the format list'''
162 '''begin an item in the format list'''
163 if self._item is not None:
163 if self._item is not None:
164 self._showitem()
164 self._showitem()
165 self._item = {}
165 self._item = {}
166 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
166 def formatdate(self, date, fmt='%a %b %d %H:%M:%S %Y %1%2'):
167 '''convert date tuple to appropriate format'''
167 '''convert date tuple to appropriate format'''
168 return self._converter.formatdate(date, fmt)
168 return self._converter.formatdate(date, fmt)
169 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
169 def formatdict(self, data, key='key', value='value', fmt='%s=%s', sep=' '):
170 '''convert dict or key-value pairs to appropriate dict format'''
170 '''convert dict or key-value pairs to appropriate dict format'''
171 return self._converter.formatdict(data, key, value, fmt, sep)
171 return self._converter.formatdict(data, key, value, fmt, sep)
172 def formatlist(self, data, name, fmt='%s', sep=' '):
172 def formatlist(self, data, name, fmt='%s', sep=' '):
173 '''convert iterable to appropriate list format'''
173 '''convert iterable to appropriate list format'''
174 # name is mandatory argument for now, but it could be optional if
174 # name is mandatory argument for now, but it could be optional if
175 # we have default template keyword, e.g. {item}
175 # we have default template keyword, e.g. {item}
176 return self._converter.formatlist(data, name, fmt, sep)
176 return self._converter.formatlist(data, name, fmt, sep)
177 def context(self, **ctxs):
177 def context(self, **ctxs):
178 '''insert context objects to be used to render template keywords'''
178 '''insert context objects to be used to render template keywords'''
179 pass
179 pass
180 def data(self, **data):
180 def data(self, **data):
181 '''insert data into item that's not shown in default output'''
181 '''insert data into item that's not shown in default output'''
182 data = pycompat.byteskwargs(data)
182 data = pycompat.byteskwargs(data)
183 self._item.update(data)
183 self._item.update(data)
184 def write(self, fields, deftext, *fielddata, **opts):
184 def write(self, fields, deftext, *fielddata, **opts):
185 '''do default text output while assigning data to item'''
185 '''do default text output while assigning data to item'''
186 fieldkeys = fields.split()
186 fieldkeys = fields.split()
187 assert len(fieldkeys) == len(fielddata)
187 assert len(fieldkeys) == len(fielddata)
188 self._item.update(zip(fieldkeys, fielddata))
188 self._item.update(zip(fieldkeys, fielddata))
189 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
189 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
190 '''do conditional write (primarily for plain formatter)'''
190 '''do conditional write (primarily for plain formatter)'''
191 fieldkeys = fields.split()
191 fieldkeys = fields.split()
192 assert len(fieldkeys) == len(fielddata)
192 assert len(fieldkeys) == len(fielddata)
193 self._item.update(zip(fieldkeys, fielddata))
193 self._item.update(zip(fieldkeys, fielddata))
194 def plain(self, text, **opts):
194 def plain(self, text, **opts):
195 '''show raw text for non-templated mode'''
195 '''show raw text for non-templated mode'''
196 pass
196 pass
197 def isplain(self):
197 def isplain(self):
198 '''check for plain formatter usage'''
198 '''check for plain formatter usage'''
199 return False
199 return False
200 def nested(self, field):
200 def nested(self, field):
201 '''sub formatter to store nested data in the specified field'''
201 '''sub formatter to store nested data in the specified field'''
202 self._item[field] = data = []
202 self._item[field] = data = []
203 return _nestedformatter(self._ui, self._converter, data)
203 return _nestedformatter(self._ui, self._converter, data)
204 def end(self):
204 def end(self):
205 '''end output for the formatter'''
205 '''end output for the formatter'''
206 if self._item is not None:
206 if self._item is not None:
207 self._showitem()
207 self._showitem()
208
208
209 class _nestedformatter(baseformatter):
209 class _nestedformatter(baseformatter):
210 '''build sub items and store them in the parent formatter'''
210 '''build sub items and store them in the parent formatter'''
211 def __init__(self, ui, converter, data):
211 def __init__(self, ui, converter, data):
212 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
212 baseformatter.__init__(self, ui, topic='', opts={}, converter=converter)
213 self._data = data
213 self._data = data
214 def _showitem(self):
214 def _showitem(self):
215 self._data.append(self._item)
215 self._data.append(self._item)
216
216
217 def _iteritems(data):
217 def _iteritems(data):
218 '''iterate key-value pairs in stable order'''
218 '''iterate key-value pairs in stable order'''
219 if isinstance(data, dict):
219 if isinstance(data, dict):
220 return sorted(data.iteritems())
220 return sorted(data.iteritems())
221 return data
221 return data
222
222
223 class _plainconverter(object):
223 class _plainconverter(object):
224 '''convert non-primitive data types to text'''
224 '''convert non-primitive data types to text'''
225 @staticmethod
225 @staticmethod
226 def formatdate(date, fmt):
226 def formatdate(date, fmt):
227 '''stringify date tuple in the given format'''
227 '''stringify date tuple in the given format'''
228 return util.datestr(date, fmt)
228 return util.datestr(date, fmt)
229 @staticmethod
229 @staticmethod
230 def formatdict(data, key, value, fmt, sep):
230 def formatdict(data, key, value, fmt, sep):
231 '''stringify key-value pairs separated by sep'''
231 '''stringify key-value pairs separated by sep'''
232 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
232 return sep.join(fmt % (k, v) for k, v in _iteritems(data))
233 @staticmethod
233 @staticmethod
234 def formatlist(data, name, fmt, sep):
234 def formatlist(data, name, fmt, sep):
235 '''stringify iterable separated by sep'''
235 '''stringify iterable separated by sep'''
236 return sep.join(fmt % e for e in data)
236 return sep.join(fmt % e for e in data)
237
237
238 class plainformatter(baseformatter):
238 class plainformatter(baseformatter):
239 '''the default text output scheme'''
239 '''the default text output scheme'''
240 def __init__(self, ui, topic, opts):
240 def __init__(self, ui, out, topic, opts):
241 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
241 baseformatter.__init__(self, ui, topic, opts, _plainconverter)
242 if ui.debugflag:
242 if ui.debugflag:
243 self.hexfunc = hex
243 self.hexfunc = hex
244 else:
244 else:
245 self.hexfunc = short
245 self.hexfunc = short
246 if ui is out:
247 self._write = ui.write
248 else:
249 self._write = lambda s, **opts: out.write(s)
246 def startitem(self):
250 def startitem(self):
247 pass
251 pass
248 def data(self, **data):
252 def data(self, **data):
249 pass
253 pass
250 def write(self, fields, deftext, *fielddata, **opts):
254 def write(self, fields, deftext, *fielddata, **opts):
251 self._ui.write(deftext % fielddata, **opts)
255 self._write(deftext % fielddata, **opts)
252 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
256 def condwrite(self, cond, fields, deftext, *fielddata, **opts):
253 '''do conditional write'''
257 '''do conditional write'''
254 if cond:
258 if cond:
255 self._ui.write(deftext % fielddata, **opts)
259 self._write(deftext % fielddata, **opts)
256 def plain(self, text, **opts):
260 def plain(self, text, **opts):
257 self._ui.write(text, **opts)
261 self._write(text, **opts)
258 def isplain(self):
262 def isplain(self):
259 return True
263 return True
260 def nested(self, field):
264 def nested(self, field):
261 # nested data will be directly written to ui
265 # nested data will be directly written to ui
262 return self
266 return self
263 def end(self):
267 def end(self):
264 pass
268 pass
265
269
266 class debugformatter(baseformatter):
270 class debugformatter(baseformatter):
267 def __init__(self, ui, out, topic, opts):
271 def __init__(self, ui, out, topic, opts):
268 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
272 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
269 self._out = out
273 self._out = out
270 self._out.write("%s = [\n" % self._topic)
274 self._out.write("%s = [\n" % self._topic)
271 def _showitem(self):
275 def _showitem(self):
272 self._out.write(" " + repr(self._item) + ",\n")
276 self._out.write(" " + repr(self._item) + ",\n")
273 def end(self):
277 def end(self):
274 baseformatter.end(self)
278 baseformatter.end(self)
275 self._out.write("]\n")
279 self._out.write("]\n")
276
280
277 class pickleformatter(baseformatter):
281 class pickleformatter(baseformatter):
278 def __init__(self, ui, out, topic, opts):
282 def __init__(self, ui, out, topic, opts):
279 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
283 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
280 self._out = out
284 self._out = out
281 self._data = []
285 self._data = []
282 def _showitem(self):
286 def _showitem(self):
283 self._data.append(self._item)
287 self._data.append(self._item)
284 def end(self):
288 def end(self):
285 baseformatter.end(self)
289 baseformatter.end(self)
286 self._out.write(pickle.dumps(self._data))
290 self._out.write(pickle.dumps(self._data))
287
291
288 class jsonformatter(baseformatter):
292 class jsonformatter(baseformatter):
289 def __init__(self, ui, out, topic, opts):
293 def __init__(self, ui, out, topic, opts):
290 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
294 baseformatter.__init__(self, ui, topic, opts, _nullconverter)
291 self._out = out
295 self._out = out
292 self._out.write("[")
296 self._out.write("[")
293 self._first = True
297 self._first = True
294 def _showitem(self):
298 def _showitem(self):
295 if self._first:
299 if self._first:
296 self._first = False
300 self._first = False
297 else:
301 else:
298 self._out.write(",")
302 self._out.write(",")
299
303
300 self._out.write("\n {\n")
304 self._out.write("\n {\n")
301 first = True
305 first = True
302 for k, v in sorted(self._item.items()):
306 for k, v in sorted(self._item.items()):
303 if first:
307 if first:
304 first = False
308 first = False
305 else:
309 else:
306 self._out.write(",\n")
310 self._out.write(",\n")
307 u = templatefilters.json(v, paranoid=False)
311 u = templatefilters.json(v, paranoid=False)
308 self._out.write(' "%s": %s' % (k, u))
312 self._out.write(' "%s": %s' % (k, u))
309 self._out.write("\n }")
313 self._out.write("\n }")
310 def end(self):
314 def end(self):
311 baseformatter.end(self)
315 baseformatter.end(self)
312 self._out.write("\n]\n")
316 self._out.write("\n]\n")
313
317
314 class _templateconverter(object):
318 class _templateconverter(object):
315 '''convert non-primitive data types to be processed by templater'''
319 '''convert non-primitive data types to be processed by templater'''
316 @staticmethod
320 @staticmethod
317 def formatdate(date, fmt):
321 def formatdate(date, fmt):
318 '''return date tuple'''
322 '''return date tuple'''
319 return date
323 return date
320 @staticmethod
324 @staticmethod
321 def formatdict(data, key, value, fmt, sep):
325 def formatdict(data, key, value, fmt, sep):
322 '''build object that can be evaluated as either plain string or dict'''
326 '''build object that can be evaluated as either plain string or dict'''
323 data = util.sortdict(_iteritems(data))
327 data = util.sortdict(_iteritems(data))
324 def f():
328 def f():
325 yield _plainconverter.formatdict(data, key, value, fmt, sep)
329 yield _plainconverter.formatdict(data, key, value, fmt, sep)
326 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
330 return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
327 gen=f())
331 gen=f())
328 @staticmethod
332 @staticmethod
329 def formatlist(data, name, fmt, sep):
333 def formatlist(data, name, fmt, sep):
330 '''build object that can be evaluated as either plain string or list'''
334 '''build object that can be evaluated as either plain string or list'''
331 data = list(data)
335 data = list(data)
332 def f():
336 def f():
333 yield _plainconverter.formatlist(data, name, fmt, sep)
337 yield _plainconverter.formatlist(data, name, fmt, sep)
334 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
338 return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
335
339
336 class templateformatter(baseformatter):
340 class templateformatter(baseformatter):
337 def __init__(self, ui, out, topic, opts):
341 def __init__(self, ui, out, topic, opts):
338 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
342 baseformatter.__init__(self, ui, topic, opts, _templateconverter)
339 self._out = out
343 self._out = out
340 self._topic = topic
344 self._topic = topic
341 self._t = gettemplater(ui, topic, opts.get('template', ''),
345 self._t = gettemplater(ui, topic, opts.get('template', ''),
342 cache=templatekw.defaulttempl)
346 cache=templatekw.defaulttempl)
343 self._counter = itertools.count()
347 self._counter = itertools.count()
344 self._cache = {} # for templatekw/funcs to store reusable data
348 self._cache = {} # for templatekw/funcs to store reusable data
345 def context(self, **ctxs):
349 def context(self, **ctxs):
346 '''insert context objects to be used to render template keywords'''
350 '''insert context objects to be used to render template keywords'''
347 assert all(k == 'ctx' for k in ctxs)
351 assert all(k == 'ctx' for k in ctxs)
348 self._item.update(ctxs)
352 self._item.update(ctxs)
349 def _showitem(self):
353 def _showitem(self):
350 # TODO: add support for filectx. probably each template keyword or
354 # TODO: add support for filectx. probably each template keyword or
351 # function will have to declare dependent resources. e.g.
355 # function will have to declare dependent resources. e.g.
352 # @templatekeyword(..., requires=('ctx',))
356 # @templatekeyword(..., requires=('ctx',))
353 props = {}
357 props = {}
354 if 'ctx' in self._item:
358 if 'ctx' in self._item:
355 props.update(templatekw.keywords)
359 props.update(templatekw.keywords)
356 props['index'] = next(self._counter)
360 props['index'] = next(self._counter)
357 # explicitly-defined fields precede templatekw
361 # explicitly-defined fields precede templatekw
358 props.update(self._item)
362 props.update(self._item)
359 if 'ctx' in self._item:
363 if 'ctx' in self._item:
360 # but template resources must be always available
364 # but template resources must be always available
361 props['templ'] = self._t
365 props['templ'] = self._t
362 props['repo'] = props['ctx'].repo()
366 props['repo'] = props['ctx'].repo()
363 props['revcache'] = {}
367 props['revcache'] = {}
364 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
368 g = self._t(self._topic, ui=self._ui, cache=self._cache, **props)
365 self._out.write(templater.stringify(g))
369 self._out.write(templater.stringify(g))
366
370
367 def lookuptemplate(ui, topic, tmpl):
371 def lookuptemplate(ui, topic, tmpl):
368 # looks like a literal template?
372 # looks like a literal template?
369 if '{' in tmpl:
373 if '{' in tmpl:
370 return tmpl, None
374 return tmpl, None
371
375
372 # perhaps a stock style?
376 # perhaps a stock style?
373 if not os.path.split(tmpl)[0]:
377 if not os.path.split(tmpl)[0]:
374 mapname = (templater.templatepath('map-cmdline.' + tmpl)
378 mapname = (templater.templatepath('map-cmdline.' + tmpl)
375 or templater.templatepath(tmpl))
379 or templater.templatepath(tmpl))
376 if mapname and os.path.isfile(mapname):
380 if mapname and os.path.isfile(mapname):
377 return None, mapname
381 return None, mapname
378
382
379 # perhaps it's a reference to [templates]
383 # perhaps it's a reference to [templates]
380 t = ui.config('templates', tmpl)
384 t = ui.config('templates', tmpl)
381 if t:
385 if t:
382 return templater.unquotestring(t), None
386 return templater.unquotestring(t), None
383
387
384 if tmpl == 'list':
388 if tmpl == 'list':
385 ui.write(_("available styles: %s\n") % templater.stylelist())
389 ui.write(_("available styles: %s\n") % templater.stylelist())
386 raise error.Abort(_("specify a template"))
390 raise error.Abort(_("specify a template"))
387
391
388 # perhaps it's a path to a map or a template
392 # perhaps it's a path to a map or a template
389 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
393 if ('/' in tmpl or '\\' in tmpl) and os.path.isfile(tmpl):
390 # is it a mapfile for a style?
394 # is it a mapfile for a style?
391 if os.path.basename(tmpl).startswith("map-"):
395 if os.path.basename(tmpl).startswith("map-"):
392 return None, os.path.realpath(tmpl)
396 return None, os.path.realpath(tmpl)
393 tmpl = open(tmpl).read()
397 tmpl = open(tmpl).read()
394 return tmpl, None
398 return tmpl, None
395
399
396 # constant string?
400 # constant string?
397 return tmpl, None
401 return tmpl, None
398
402
399 def gettemplater(ui, topic, spec, cache=None):
403 def gettemplater(ui, topic, spec, cache=None):
400 tmpl, mapfile = lookuptemplate(ui, topic, spec)
404 tmpl, mapfile = lookuptemplate(ui, topic, spec)
401 assert not (tmpl and mapfile)
405 assert not (tmpl and mapfile)
402 if mapfile:
406 if mapfile:
403 return templater.templater.frommapfile(mapfile, cache=cache)
407 return templater.templater.frommapfile(mapfile, cache=cache)
404 return maketemplater(ui, topic, tmpl, cache=cache)
408 return maketemplater(ui, topic, tmpl, cache=cache)
405
409
406 def maketemplater(ui, topic, tmpl, cache=None):
410 def maketemplater(ui, topic, tmpl, cache=None):
407 """Create a templater from a string template 'tmpl'"""
411 """Create a templater from a string template 'tmpl'"""
408 aliases = ui.configitems('templatealias')
412 aliases = ui.configitems('templatealias')
409 t = templater.templater(cache=cache, aliases=aliases)
413 t = templater.templater(cache=cache, aliases=aliases)
410 if tmpl:
414 if tmpl:
411 t.cache[topic] = tmpl
415 t.cache[topic] = tmpl
412 return t
416 return t
413
417
414 def formatter(ui, topic, opts):
418 def formatter(ui, out, topic, opts):
415 template = opts.get("template", "")
419 template = opts.get("template", "")
416 if template == "json":
420 if template == "json":
417 return jsonformatter(ui, ui, topic, opts)
421 return jsonformatter(ui, out, topic, opts)
418 elif template == "pickle":
422 elif template == "pickle":
419 return pickleformatter(ui, ui, topic, opts)
423 return pickleformatter(ui, out, topic, opts)
420 elif template == "debug":
424 elif template == "debug":
421 return debugformatter(ui, ui, topic, opts)
425 return debugformatter(ui, out, topic, opts)
422 elif template != "":
426 elif template != "":
423 return templateformatter(ui, ui, topic, opts)
427 return templateformatter(ui, out, topic, opts)
424 # developer config: ui.formatdebug
428 # developer config: ui.formatdebug
425 elif ui.configbool('ui', 'formatdebug'):
429 elif ui.configbool('ui', 'formatdebug'):
426 return debugformatter(ui, ui, topic, opts)
430 return debugformatter(ui, out, topic, opts)
427 # deprecated config: ui.formatjson
431 # deprecated config: ui.formatjson
428 elif ui.configbool('ui', 'formatjson'):
432 elif ui.configbool('ui', 'formatjson'):
429 return jsonformatter(ui, ui, topic, opts)
433 return jsonformatter(ui, out, topic, opts)
430 return plainformatter(ui, topic, opts)
434 return plainformatter(ui, out, topic, opts)
@@ -1,1699 +1,1699 b''
1 # ui.py - user interface bits for mercurial
1 # ui.py - user interface bits for mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import collections
10 import collections
11 import contextlib
11 import contextlib
12 import errno
12 import errno
13 import getpass
13 import getpass
14 import inspect
14 import inspect
15 import os
15 import os
16 import re
16 import re
17 import signal
17 import signal
18 import socket
18 import socket
19 import subprocess
19 import subprocess
20 import sys
20 import sys
21 import tempfile
21 import tempfile
22 import traceback
22 import traceback
23
23
24 from .i18n import _
24 from .i18n import _
25 from .node import hex
25 from .node import hex
26
26
27 from . import (
27 from . import (
28 color,
28 color,
29 config,
29 config,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 progress,
33 progress,
34 pycompat,
34 pycompat,
35 rcutil,
35 rcutil,
36 scmutil,
36 scmutil,
37 util,
37 util,
38 )
38 )
39
39
40 urlreq = util.urlreq
40 urlreq = util.urlreq
41
41
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
42 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
43 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
44 if not c.isalnum())
44 if not c.isalnum())
45
45
46 samplehgrcs = {
46 samplehgrcs = {
47 'user':
47 'user':
48 """# example user config (see 'hg help config' for more info)
48 """# example user config (see 'hg help config' for more info)
49 [ui]
49 [ui]
50 # name and email, e.g.
50 # name and email, e.g.
51 # username = Jane Doe <jdoe@example.com>
51 # username = Jane Doe <jdoe@example.com>
52 username =
52 username =
53
53
54 # uncomment to disable color in command output
54 # uncomment to disable color in command output
55 # (see 'hg help color' for details)
55 # (see 'hg help color' for details)
56 # color = never
56 # color = never
57
57
58 # uncomment to disable command output pagination
58 # uncomment to disable command output pagination
59 # (see 'hg help pager' for details)
59 # (see 'hg help pager' for details)
60 # paginate = never
60 # paginate = never
61
61
62 [extensions]
62 [extensions]
63 # uncomment these lines to enable some popular extensions
63 # uncomment these lines to enable some popular extensions
64 # (see 'hg help extensions' for more info)
64 # (see 'hg help extensions' for more info)
65 #
65 #
66 # churn =
66 # churn =
67 """,
67 """,
68
68
69 'cloned':
69 'cloned':
70 """# example repository config (see 'hg help config' for more info)
70 """# example repository config (see 'hg help config' for more info)
71 [paths]
71 [paths]
72 default = %s
72 default = %s
73
73
74 # path aliases to other clones of this repo in URLs or filesystem paths
74 # path aliases to other clones of this repo in URLs or filesystem paths
75 # (see 'hg help config.paths' for more info)
75 # (see 'hg help config.paths' for more info)
76 #
76 #
77 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
77 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
78 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
78 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
79 # my-clone = /home/jdoe/jdoes-clone
79 # my-clone = /home/jdoe/jdoes-clone
80
80
81 [ui]
81 [ui]
82 # name and email (local to this repository, optional), e.g.
82 # name and email (local to this repository, optional), e.g.
83 # username = Jane Doe <jdoe@example.com>
83 # username = Jane Doe <jdoe@example.com>
84 """,
84 """,
85
85
86 'local':
86 'local':
87 """# example repository config (see 'hg help config' for more info)
87 """# example repository config (see 'hg help config' for more info)
88 [paths]
88 [paths]
89 # path aliases to other clones of this repo in URLs or filesystem paths
89 # path aliases to other clones of this repo in URLs or filesystem paths
90 # (see 'hg help config.paths' for more info)
90 # (see 'hg help config.paths' for more info)
91 #
91 #
92 # default = http://example.com/hg/example-repo
92 # default = http://example.com/hg/example-repo
93 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
93 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
94 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
94 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
95 # my-clone = /home/jdoe/jdoes-clone
95 # my-clone = /home/jdoe/jdoes-clone
96
96
97 [ui]
97 [ui]
98 # name and email (local to this repository, optional), e.g.
98 # name and email (local to this repository, optional), e.g.
99 # username = Jane Doe <jdoe@example.com>
99 # username = Jane Doe <jdoe@example.com>
100 """,
100 """,
101
101
102 'global':
102 'global':
103 """# example system-wide hg config (see 'hg help config' for more info)
103 """# example system-wide hg config (see 'hg help config' for more info)
104
104
105 [ui]
105 [ui]
106 # uncomment to disable color in command output
106 # uncomment to disable color in command output
107 # (see 'hg help color' for details)
107 # (see 'hg help color' for details)
108 # color = never
108 # color = never
109
109
110 # uncomment to disable command output pagination
110 # uncomment to disable command output pagination
111 # (see 'hg help pager' for details)
111 # (see 'hg help pager' for details)
112 # paginate = never
112 # paginate = never
113
113
114 [extensions]
114 [extensions]
115 # uncomment these lines to enable some popular extensions
115 # uncomment these lines to enable some popular extensions
116 # (see 'hg help extensions' for more info)
116 # (see 'hg help extensions' for more info)
117 #
117 #
118 # blackbox =
118 # blackbox =
119 # churn =
119 # churn =
120 """,
120 """,
121 }
121 }
122
122
123
123
124 class httppasswordmgrdbproxy(object):
124 class httppasswordmgrdbproxy(object):
125 """Delays loading urllib2 until it's needed."""
125 """Delays loading urllib2 until it's needed."""
126 def __init__(self):
126 def __init__(self):
127 self._mgr = None
127 self._mgr = None
128
128
129 def _get_mgr(self):
129 def _get_mgr(self):
130 if self._mgr is None:
130 if self._mgr is None:
131 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
131 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
132 return self._mgr
132 return self._mgr
133
133
134 def add_password(self, *args, **kwargs):
134 def add_password(self, *args, **kwargs):
135 return self._get_mgr().add_password(*args, **kwargs)
135 return self._get_mgr().add_password(*args, **kwargs)
136
136
137 def find_user_password(self, *args, **kwargs):
137 def find_user_password(self, *args, **kwargs):
138 return self._get_mgr().find_user_password(*args, **kwargs)
138 return self._get_mgr().find_user_password(*args, **kwargs)
139
139
140 def _catchterm(*args):
140 def _catchterm(*args):
141 raise error.SignalInterrupt
141 raise error.SignalInterrupt
142
142
143 class ui(object):
143 class ui(object):
144 def __init__(self, src=None):
144 def __init__(self, src=None):
145 """Create a fresh new ui object if no src given
145 """Create a fresh new ui object if no src given
146
146
147 Use uimod.ui.load() to create a ui which knows global and user configs.
147 Use uimod.ui.load() to create a ui which knows global and user configs.
148 In most cases, you should use ui.copy() to create a copy of an existing
148 In most cases, you should use ui.copy() to create a copy of an existing
149 ui object.
149 ui object.
150 """
150 """
151 # _buffers: used for temporary capture of output
151 # _buffers: used for temporary capture of output
152 self._buffers = []
152 self._buffers = []
153 # _exithandlers: callbacks run at the end of a request
153 # _exithandlers: callbacks run at the end of a request
154 self._exithandlers = []
154 self._exithandlers = []
155 # 3-tuple describing how each buffer in the stack behaves.
155 # 3-tuple describing how each buffer in the stack behaves.
156 # Values are (capture stderr, capture subprocesses, apply labels).
156 # Values are (capture stderr, capture subprocesses, apply labels).
157 self._bufferstates = []
157 self._bufferstates = []
158 # When a buffer is active, defines whether we are expanding labels.
158 # When a buffer is active, defines whether we are expanding labels.
159 # This exists to prevent an extra list lookup.
159 # This exists to prevent an extra list lookup.
160 self._bufferapplylabels = None
160 self._bufferapplylabels = None
161 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
161 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
162 self._reportuntrusted = True
162 self._reportuntrusted = True
163 self._ocfg = config.config() # overlay
163 self._ocfg = config.config() # overlay
164 self._tcfg = config.config() # trusted
164 self._tcfg = config.config() # trusted
165 self._ucfg = config.config() # untrusted
165 self._ucfg = config.config() # untrusted
166 self._trustusers = set()
166 self._trustusers = set()
167 self._trustgroups = set()
167 self._trustgroups = set()
168 self.callhooks = True
168 self.callhooks = True
169 # Insecure server connections requested.
169 # Insecure server connections requested.
170 self.insecureconnections = False
170 self.insecureconnections = False
171 # Blocked time
171 # Blocked time
172 self.logblockedtimes = False
172 self.logblockedtimes = False
173 # color mode: see mercurial/color.py for possible value
173 # color mode: see mercurial/color.py for possible value
174 self._colormode = None
174 self._colormode = None
175 self._terminfoparams = {}
175 self._terminfoparams = {}
176 self._styles = {}
176 self._styles = {}
177
177
178 if src:
178 if src:
179 self._exithandlers = src._exithandlers
179 self._exithandlers = src._exithandlers
180 self.fout = src.fout
180 self.fout = src.fout
181 self.ferr = src.ferr
181 self.ferr = src.ferr
182 self.fin = src.fin
182 self.fin = src.fin
183 self.pageractive = src.pageractive
183 self.pageractive = src.pageractive
184 self._disablepager = src._disablepager
184 self._disablepager = src._disablepager
185
185
186 self._tcfg = src._tcfg.copy()
186 self._tcfg = src._tcfg.copy()
187 self._ucfg = src._ucfg.copy()
187 self._ucfg = src._ucfg.copy()
188 self._ocfg = src._ocfg.copy()
188 self._ocfg = src._ocfg.copy()
189 self._trustusers = src._trustusers.copy()
189 self._trustusers = src._trustusers.copy()
190 self._trustgroups = src._trustgroups.copy()
190 self._trustgroups = src._trustgroups.copy()
191 self.environ = src.environ
191 self.environ = src.environ
192 self.callhooks = src.callhooks
192 self.callhooks = src.callhooks
193 self.insecureconnections = src.insecureconnections
193 self.insecureconnections = src.insecureconnections
194 self._colormode = src._colormode
194 self._colormode = src._colormode
195 self._terminfoparams = src._terminfoparams.copy()
195 self._terminfoparams = src._terminfoparams.copy()
196 self._styles = src._styles.copy()
196 self._styles = src._styles.copy()
197
197
198 self.fixconfig()
198 self.fixconfig()
199
199
200 self.httppasswordmgrdb = src.httppasswordmgrdb
200 self.httppasswordmgrdb = src.httppasswordmgrdb
201 self._blockedtimes = src._blockedtimes
201 self._blockedtimes = src._blockedtimes
202 else:
202 else:
203 self.fout = util.stdout
203 self.fout = util.stdout
204 self.ferr = util.stderr
204 self.ferr = util.stderr
205 self.fin = util.stdin
205 self.fin = util.stdin
206 self.pageractive = False
206 self.pageractive = False
207 self._disablepager = False
207 self._disablepager = False
208
208
209 # shared read-only environment
209 # shared read-only environment
210 self.environ = encoding.environ
210 self.environ = encoding.environ
211
211
212 self.httppasswordmgrdb = httppasswordmgrdbproxy()
212 self.httppasswordmgrdb = httppasswordmgrdbproxy()
213 self._blockedtimes = collections.defaultdict(int)
213 self._blockedtimes = collections.defaultdict(int)
214
214
215 allowed = self.configlist('experimental', 'exportableenviron')
215 allowed = self.configlist('experimental', 'exportableenviron')
216 if '*' in allowed:
216 if '*' in allowed:
217 self._exportableenviron = self.environ
217 self._exportableenviron = self.environ
218 else:
218 else:
219 self._exportableenviron = {}
219 self._exportableenviron = {}
220 for k in allowed:
220 for k in allowed:
221 if k in self.environ:
221 if k in self.environ:
222 self._exportableenviron[k] = self.environ[k]
222 self._exportableenviron[k] = self.environ[k]
223
223
224 @classmethod
224 @classmethod
225 def load(cls):
225 def load(cls):
226 """Create a ui and load global and user configs"""
226 """Create a ui and load global and user configs"""
227 u = cls()
227 u = cls()
228 # we always trust global config files and environment variables
228 # we always trust global config files and environment variables
229 for t, f in rcutil.rccomponents():
229 for t, f in rcutil.rccomponents():
230 if t == 'path':
230 if t == 'path':
231 u.readconfig(f, trust=True)
231 u.readconfig(f, trust=True)
232 elif t == 'items':
232 elif t == 'items':
233 sections = set()
233 sections = set()
234 for section, name, value, source in f:
234 for section, name, value, source in f:
235 # do not set u._ocfg
235 # do not set u._ocfg
236 # XXX clean this up once immutable config object is a thing
236 # XXX clean this up once immutable config object is a thing
237 u._tcfg.set(section, name, value, source)
237 u._tcfg.set(section, name, value, source)
238 u._ucfg.set(section, name, value, source)
238 u._ucfg.set(section, name, value, source)
239 sections.add(section)
239 sections.add(section)
240 for section in sections:
240 for section in sections:
241 u.fixconfig(section=section)
241 u.fixconfig(section=section)
242 else:
242 else:
243 raise error.ProgrammingError('unknown rctype: %s' % t)
243 raise error.ProgrammingError('unknown rctype: %s' % t)
244 return u
244 return u
245
245
246 def copy(self):
246 def copy(self):
247 return self.__class__(self)
247 return self.__class__(self)
248
248
249 def resetstate(self):
249 def resetstate(self):
250 """Clear internal state that shouldn't persist across commands"""
250 """Clear internal state that shouldn't persist across commands"""
251 if self._progbar:
251 if self._progbar:
252 self._progbar.resetstate() # reset last-print time of progress bar
252 self._progbar.resetstate() # reset last-print time of progress bar
253 self.httppasswordmgrdb = httppasswordmgrdbproxy()
253 self.httppasswordmgrdb = httppasswordmgrdbproxy()
254
254
255 @contextlib.contextmanager
255 @contextlib.contextmanager
256 def timeblockedsection(self, key):
256 def timeblockedsection(self, key):
257 # this is open-coded below - search for timeblockedsection to find them
257 # this is open-coded below - search for timeblockedsection to find them
258 starttime = util.timer()
258 starttime = util.timer()
259 try:
259 try:
260 yield
260 yield
261 finally:
261 finally:
262 self._blockedtimes[key + '_blocked'] += \
262 self._blockedtimes[key + '_blocked'] += \
263 (util.timer() - starttime) * 1000
263 (util.timer() - starttime) * 1000
264
264
265 def formatter(self, topic, opts):
265 def formatter(self, topic, opts):
266 return formatter.formatter(self, topic, opts)
266 return formatter.formatter(self, self, topic, opts)
267
267
268 def _trusted(self, fp, f):
268 def _trusted(self, fp, f):
269 st = util.fstat(fp)
269 st = util.fstat(fp)
270 if util.isowner(st):
270 if util.isowner(st):
271 return True
271 return True
272
272
273 tusers, tgroups = self._trustusers, self._trustgroups
273 tusers, tgroups = self._trustusers, self._trustgroups
274 if '*' in tusers or '*' in tgroups:
274 if '*' in tusers or '*' in tgroups:
275 return True
275 return True
276
276
277 user = util.username(st.st_uid)
277 user = util.username(st.st_uid)
278 group = util.groupname(st.st_gid)
278 group = util.groupname(st.st_gid)
279 if user in tusers or group in tgroups or user == util.username():
279 if user in tusers or group in tgroups or user == util.username():
280 return True
280 return True
281
281
282 if self._reportuntrusted:
282 if self._reportuntrusted:
283 self.warn(_('not trusting file %s from untrusted '
283 self.warn(_('not trusting file %s from untrusted '
284 'user %s, group %s\n') % (f, user, group))
284 'user %s, group %s\n') % (f, user, group))
285 return False
285 return False
286
286
287 def readconfig(self, filename, root=None, trust=False,
287 def readconfig(self, filename, root=None, trust=False,
288 sections=None, remap=None):
288 sections=None, remap=None):
289 try:
289 try:
290 fp = open(filename, u'rb')
290 fp = open(filename, u'rb')
291 except IOError:
291 except IOError:
292 if not sections: # ignore unless we were looking for something
292 if not sections: # ignore unless we were looking for something
293 return
293 return
294 raise
294 raise
295
295
296 cfg = config.config()
296 cfg = config.config()
297 trusted = sections or trust or self._trusted(fp, filename)
297 trusted = sections or trust or self._trusted(fp, filename)
298
298
299 try:
299 try:
300 cfg.read(filename, fp, sections=sections, remap=remap)
300 cfg.read(filename, fp, sections=sections, remap=remap)
301 fp.close()
301 fp.close()
302 except error.ConfigError as inst:
302 except error.ConfigError as inst:
303 if trusted:
303 if trusted:
304 raise
304 raise
305 self.warn(_("ignored: %s\n") % str(inst))
305 self.warn(_("ignored: %s\n") % str(inst))
306
306
307 if self.plain():
307 if self.plain():
308 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
308 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
309 'logtemplate', 'statuscopies', 'style',
309 'logtemplate', 'statuscopies', 'style',
310 'traceback', 'verbose'):
310 'traceback', 'verbose'):
311 if k in cfg['ui']:
311 if k in cfg['ui']:
312 del cfg['ui'][k]
312 del cfg['ui'][k]
313 for k, v in cfg.items('defaults'):
313 for k, v in cfg.items('defaults'):
314 del cfg['defaults'][k]
314 del cfg['defaults'][k]
315 for k, v in cfg.items('commands'):
315 for k, v in cfg.items('commands'):
316 del cfg['commands'][k]
316 del cfg['commands'][k]
317 # Don't remove aliases from the configuration if in the exceptionlist
317 # Don't remove aliases from the configuration if in the exceptionlist
318 if self.plain('alias'):
318 if self.plain('alias'):
319 for k, v in cfg.items('alias'):
319 for k, v in cfg.items('alias'):
320 del cfg['alias'][k]
320 del cfg['alias'][k]
321 if self.plain('revsetalias'):
321 if self.plain('revsetalias'):
322 for k, v in cfg.items('revsetalias'):
322 for k, v in cfg.items('revsetalias'):
323 del cfg['revsetalias'][k]
323 del cfg['revsetalias'][k]
324 if self.plain('templatealias'):
324 if self.plain('templatealias'):
325 for k, v in cfg.items('templatealias'):
325 for k, v in cfg.items('templatealias'):
326 del cfg['templatealias'][k]
326 del cfg['templatealias'][k]
327
327
328 if trusted:
328 if trusted:
329 self._tcfg.update(cfg)
329 self._tcfg.update(cfg)
330 self._tcfg.update(self._ocfg)
330 self._tcfg.update(self._ocfg)
331 self._ucfg.update(cfg)
331 self._ucfg.update(cfg)
332 self._ucfg.update(self._ocfg)
332 self._ucfg.update(self._ocfg)
333
333
334 if root is None:
334 if root is None:
335 root = os.path.expanduser('~')
335 root = os.path.expanduser('~')
336 self.fixconfig(root=root)
336 self.fixconfig(root=root)
337
337
338 def fixconfig(self, root=None, section=None):
338 def fixconfig(self, root=None, section=None):
339 if section in (None, 'paths'):
339 if section in (None, 'paths'):
340 # expand vars and ~
340 # expand vars and ~
341 # translate paths relative to root (or home) into absolute paths
341 # translate paths relative to root (or home) into absolute paths
342 root = root or pycompat.getcwd()
342 root = root or pycompat.getcwd()
343 for c in self._tcfg, self._ucfg, self._ocfg:
343 for c in self._tcfg, self._ucfg, self._ocfg:
344 for n, p in c.items('paths'):
344 for n, p in c.items('paths'):
345 # Ignore sub-options.
345 # Ignore sub-options.
346 if ':' in n:
346 if ':' in n:
347 continue
347 continue
348 if not p:
348 if not p:
349 continue
349 continue
350 if '%%' in p:
350 if '%%' in p:
351 s = self.configsource('paths', n) or 'none'
351 s = self.configsource('paths', n) or 'none'
352 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
352 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
353 % (n, p, s))
353 % (n, p, s))
354 p = p.replace('%%', '%')
354 p = p.replace('%%', '%')
355 p = util.expandpath(p)
355 p = util.expandpath(p)
356 if not util.hasscheme(p) and not os.path.isabs(p):
356 if not util.hasscheme(p) and not os.path.isabs(p):
357 p = os.path.normpath(os.path.join(root, p))
357 p = os.path.normpath(os.path.join(root, p))
358 c.set("paths", n, p)
358 c.set("paths", n, p)
359
359
360 if section in (None, 'ui'):
360 if section in (None, 'ui'):
361 # update ui options
361 # update ui options
362 self.debugflag = self.configbool('ui', 'debug')
362 self.debugflag = self.configbool('ui', 'debug')
363 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
363 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
364 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
364 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
365 if self.verbose and self.quiet:
365 if self.verbose and self.quiet:
366 self.quiet = self.verbose = False
366 self.quiet = self.verbose = False
367 self._reportuntrusted = self.debugflag or self.configbool("ui",
367 self._reportuntrusted = self.debugflag or self.configbool("ui",
368 "report_untrusted", True)
368 "report_untrusted", True)
369 self.tracebackflag = self.configbool('ui', 'traceback', False)
369 self.tracebackflag = self.configbool('ui', 'traceback', False)
370 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
370 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
371
371
372 if section in (None, 'trusted'):
372 if section in (None, 'trusted'):
373 # update trust information
373 # update trust information
374 self._trustusers.update(self.configlist('trusted', 'users'))
374 self._trustusers.update(self.configlist('trusted', 'users'))
375 self._trustgroups.update(self.configlist('trusted', 'groups'))
375 self._trustgroups.update(self.configlist('trusted', 'groups'))
376
376
377 def backupconfig(self, section, item):
377 def backupconfig(self, section, item):
378 return (self._ocfg.backup(section, item),
378 return (self._ocfg.backup(section, item),
379 self._tcfg.backup(section, item),
379 self._tcfg.backup(section, item),
380 self._ucfg.backup(section, item),)
380 self._ucfg.backup(section, item),)
381 def restoreconfig(self, data):
381 def restoreconfig(self, data):
382 self._ocfg.restore(data[0])
382 self._ocfg.restore(data[0])
383 self._tcfg.restore(data[1])
383 self._tcfg.restore(data[1])
384 self._ucfg.restore(data[2])
384 self._ucfg.restore(data[2])
385
385
386 def setconfig(self, section, name, value, source=''):
386 def setconfig(self, section, name, value, source=''):
387 for cfg in (self._ocfg, self._tcfg, self._ucfg):
387 for cfg in (self._ocfg, self._tcfg, self._ucfg):
388 cfg.set(section, name, value, source)
388 cfg.set(section, name, value, source)
389 self.fixconfig(section=section)
389 self.fixconfig(section=section)
390
390
391 def _data(self, untrusted):
391 def _data(self, untrusted):
392 return untrusted and self._ucfg or self._tcfg
392 return untrusted and self._ucfg or self._tcfg
393
393
394 def configsource(self, section, name, untrusted=False):
394 def configsource(self, section, name, untrusted=False):
395 return self._data(untrusted).source(section, name)
395 return self._data(untrusted).source(section, name)
396
396
397 def config(self, section, name, default=None, untrusted=False):
397 def config(self, section, name, default=None, untrusted=False):
398 if isinstance(name, list):
398 if isinstance(name, list):
399 alternates = name
399 alternates = name
400 else:
400 else:
401 alternates = [name]
401 alternates = [name]
402
402
403 for n in alternates:
403 for n in alternates:
404 value = self._data(untrusted).get(section, n, None)
404 value = self._data(untrusted).get(section, n, None)
405 if value is not None:
405 if value is not None:
406 name = n
406 name = n
407 break
407 break
408 else:
408 else:
409 value = default
409 value = default
410
410
411 if self.debugflag and not untrusted and self._reportuntrusted:
411 if self.debugflag and not untrusted and self._reportuntrusted:
412 for n in alternates:
412 for n in alternates:
413 uvalue = self._ucfg.get(section, n)
413 uvalue = self._ucfg.get(section, n)
414 if uvalue is not None and uvalue != value:
414 if uvalue is not None and uvalue != value:
415 self.debug("ignoring untrusted configuration option "
415 self.debug("ignoring untrusted configuration option "
416 "%s.%s = %s\n" % (section, n, uvalue))
416 "%s.%s = %s\n" % (section, n, uvalue))
417 return value
417 return value
418
418
419 def configsuboptions(self, section, name, default=None, untrusted=False):
419 def configsuboptions(self, section, name, default=None, untrusted=False):
420 """Get a config option and all sub-options.
420 """Get a config option and all sub-options.
421
421
422 Some config options have sub-options that are declared with the
422 Some config options have sub-options that are declared with the
423 format "key:opt = value". This method is used to return the main
423 format "key:opt = value". This method is used to return the main
424 option and all its declared sub-options.
424 option and all its declared sub-options.
425
425
426 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
426 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
427 is a dict of defined sub-options where keys and values are strings.
427 is a dict of defined sub-options where keys and values are strings.
428 """
428 """
429 data = self._data(untrusted)
429 data = self._data(untrusted)
430 main = data.get(section, name, default)
430 main = data.get(section, name, default)
431 if self.debugflag and not untrusted and self._reportuntrusted:
431 if self.debugflag and not untrusted and self._reportuntrusted:
432 uvalue = self._ucfg.get(section, name)
432 uvalue = self._ucfg.get(section, name)
433 if uvalue is not None and uvalue != main:
433 if uvalue is not None and uvalue != main:
434 self.debug('ignoring untrusted configuration option '
434 self.debug('ignoring untrusted configuration option '
435 '%s.%s = %s\n' % (section, name, uvalue))
435 '%s.%s = %s\n' % (section, name, uvalue))
436
436
437 sub = {}
437 sub = {}
438 prefix = '%s:' % name
438 prefix = '%s:' % name
439 for k, v in data.items(section):
439 for k, v in data.items(section):
440 if k.startswith(prefix):
440 if k.startswith(prefix):
441 sub[k[len(prefix):]] = v
441 sub[k[len(prefix):]] = v
442
442
443 if self.debugflag and not untrusted and self._reportuntrusted:
443 if self.debugflag and not untrusted and self._reportuntrusted:
444 for k, v in sub.items():
444 for k, v in sub.items():
445 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
445 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
446 if uvalue is not None and uvalue != v:
446 if uvalue is not None and uvalue != v:
447 self.debug('ignoring untrusted configuration option '
447 self.debug('ignoring untrusted configuration option '
448 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
448 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
449
449
450 return main, sub
450 return main, sub
451
451
452 def configpath(self, section, name, default=None, untrusted=False):
452 def configpath(self, section, name, default=None, untrusted=False):
453 'get a path config item, expanded relative to repo root or config file'
453 'get a path config item, expanded relative to repo root or config file'
454 v = self.config(section, name, default, untrusted)
454 v = self.config(section, name, default, untrusted)
455 if v is None:
455 if v is None:
456 return None
456 return None
457 if not os.path.isabs(v) or "://" not in v:
457 if not os.path.isabs(v) or "://" not in v:
458 src = self.configsource(section, name, untrusted)
458 src = self.configsource(section, name, untrusted)
459 if ':' in src:
459 if ':' in src:
460 base = os.path.dirname(src.rsplit(':')[0])
460 base = os.path.dirname(src.rsplit(':')[0])
461 v = os.path.join(base, os.path.expanduser(v))
461 v = os.path.join(base, os.path.expanduser(v))
462 return v
462 return v
463
463
464 def configbool(self, section, name, default=False, untrusted=False):
464 def configbool(self, section, name, default=False, untrusted=False):
465 """parse a configuration element as a boolean
465 """parse a configuration element as a boolean
466
466
467 >>> u = ui(); s = 'foo'
467 >>> u = ui(); s = 'foo'
468 >>> u.setconfig(s, 'true', 'yes')
468 >>> u.setconfig(s, 'true', 'yes')
469 >>> u.configbool(s, 'true')
469 >>> u.configbool(s, 'true')
470 True
470 True
471 >>> u.setconfig(s, 'false', 'no')
471 >>> u.setconfig(s, 'false', 'no')
472 >>> u.configbool(s, 'false')
472 >>> u.configbool(s, 'false')
473 False
473 False
474 >>> u.configbool(s, 'unknown')
474 >>> u.configbool(s, 'unknown')
475 False
475 False
476 >>> u.configbool(s, 'unknown', True)
476 >>> u.configbool(s, 'unknown', True)
477 True
477 True
478 >>> u.setconfig(s, 'invalid', 'somevalue')
478 >>> u.setconfig(s, 'invalid', 'somevalue')
479 >>> u.configbool(s, 'invalid')
479 >>> u.configbool(s, 'invalid')
480 Traceback (most recent call last):
480 Traceback (most recent call last):
481 ...
481 ...
482 ConfigError: foo.invalid is not a boolean ('somevalue')
482 ConfigError: foo.invalid is not a boolean ('somevalue')
483 """
483 """
484
484
485 v = self.config(section, name, None, untrusted)
485 v = self.config(section, name, None, untrusted)
486 if v is None:
486 if v is None:
487 return default
487 return default
488 if isinstance(v, bool):
488 if isinstance(v, bool):
489 return v
489 return v
490 b = util.parsebool(v)
490 b = util.parsebool(v)
491 if b is None:
491 if b is None:
492 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
492 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
493 % (section, name, v))
493 % (section, name, v))
494 return b
494 return b
495
495
496 def configwith(self, convert, section, name, default=None,
496 def configwith(self, convert, section, name, default=None,
497 desc=None, untrusted=False):
497 desc=None, untrusted=False):
498 """parse a configuration element with a conversion function
498 """parse a configuration element with a conversion function
499
499
500 >>> u = ui(); s = 'foo'
500 >>> u = ui(); s = 'foo'
501 >>> u.setconfig(s, 'float1', '42')
501 >>> u.setconfig(s, 'float1', '42')
502 >>> u.configwith(float, s, 'float1')
502 >>> u.configwith(float, s, 'float1')
503 42.0
503 42.0
504 >>> u.setconfig(s, 'float2', '-4.25')
504 >>> u.setconfig(s, 'float2', '-4.25')
505 >>> u.configwith(float, s, 'float2')
505 >>> u.configwith(float, s, 'float2')
506 -4.25
506 -4.25
507 >>> u.configwith(float, s, 'unknown', 7)
507 >>> u.configwith(float, s, 'unknown', 7)
508 7
508 7
509 >>> u.setconfig(s, 'invalid', 'somevalue')
509 >>> u.setconfig(s, 'invalid', 'somevalue')
510 >>> u.configwith(float, s, 'invalid')
510 >>> u.configwith(float, s, 'invalid')
511 Traceback (most recent call last):
511 Traceback (most recent call last):
512 ...
512 ...
513 ConfigError: foo.invalid is not a valid float ('somevalue')
513 ConfigError: foo.invalid is not a valid float ('somevalue')
514 >>> u.configwith(float, s, 'invalid', desc='womble')
514 >>> u.configwith(float, s, 'invalid', desc='womble')
515 Traceback (most recent call last):
515 Traceback (most recent call last):
516 ...
516 ...
517 ConfigError: foo.invalid is not a valid womble ('somevalue')
517 ConfigError: foo.invalid is not a valid womble ('somevalue')
518 """
518 """
519
519
520 v = self.config(section, name, None, untrusted)
520 v = self.config(section, name, None, untrusted)
521 if v is None:
521 if v is None:
522 return default
522 return default
523 try:
523 try:
524 return convert(v)
524 return convert(v)
525 except (ValueError, error.ParseError):
525 except (ValueError, error.ParseError):
526 if desc is None:
526 if desc is None:
527 desc = convert.__name__
527 desc = convert.__name__
528 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
528 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
529 % (section, name, desc, v))
529 % (section, name, desc, v))
530
530
531 def configint(self, section, name, default=None, untrusted=False):
531 def configint(self, section, name, default=None, untrusted=False):
532 """parse a configuration element as an integer
532 """parse a configuration element as an integer
533
533
534 >>> u = ui(); s = 'foo'
534 >>> u = ui(); s = 'foo'
535 >>> u.setconfig(s, 'int1', '42')
535 >>> u.setconfig(s, 'int1', '42')
536 >>> u.configint(s, 'int1')
536 >>> u.configint(s, 'int1')
537 42
537 42
538 >>> u.setconfig(s, 'int2', '-42')
538 >>> u.setconfig(s, 'int2', '-42')
539 >>> u.configint(s, 'int2')
539 >>> u.configint(s, 'int2')
540 -42
540 -42
541 >>> u.configint(s, 'unknown', 7)
541 >>> u.configint(s, 'unknown', 7)
542 7
542 7
543 >>> u.setconfig(s, 'invalid', 'somevalue')
543 >>> u.setconfig(s, 'invalid', 'somevalue')
544 >>> u.configint(s, 'invalid')
544 >>> u.configint(s, 'invalid')
545 Traceback (most recent call last):
545 Traceback (most recent call last):
546 ...
546 ...
547 ConfigError: foo.invalid is not a valid integer ('somevalue')
547 ConfigError: foo.invalid is not a valid integer ('somevalue')
548 """
548 """
549
549
550 return self.configwith(int, section, name, default, 'integer',
550 return self.configwith(int, section, name, default, 'integer',
551 untrusted)
551 untrusted)
552
552
553 def configbytes(self, section, name, default=0, untrusted=False):
553 def configbytes(self, section, name, default=0, untrusted=False):
554 """parse a configuration element as a quantity in bytes
554 """parse a configuration element as a quantity in bytes
555
555
556 Units can be specified as b (bytes), k or kb (kilobytes), m or
556 Units can be specified as b (bytes), k or kb (kilobytes), m or
557 mb (megabytes), g or gb (gigabytes).
557 mb (megabytes), g or gb (gigabytes).
558
558
559 >>> u = ui(); s = 'foo'
559 >>> u = ui(); s = 'foo'
560 >>> u.setconfig(s, 'val1', '42')
560 >>> u.setconfig(s, 'val1', '42')
561 >>> u.configbytes(s, 'val1')
561 >>> u.configbytes(s, 'val1')
562 42
562 42
563 >>> u.setconfig(s, 'val2', '42.5 kb')
563 >>> u.setconfig(s, 'val2', '42.5 kb')
564 >>> u.configbytes(s, 'val2')
564 >>> u.configbytes(s, 'val2')
565 43520
565 43520
566 >>> u.configbytes(s, 'unknown', '7 MB')
566 >>> u.configbytes(s, 'unknown', '7 MB')
567 7340032
567 7340032
568 >>> u.setconfig(s, 'invalid', 'somevalue')
568 >>> u.setconfig(s, 'invalid', 'somevalue')
569 >>> u.configbytes(s, 'invalid')
569 >>> u.configbytes(s, 'invalid')
570 Traceback (most recent call last):
570 Traceback (most recent call last):
571 ...
571 ...
572 ConfigError: foo.invalid is not a byte quantity ('somevalue')
572 ConfigError: foo.invalid is not a byte quantity ('somevalue')
573 """
573 """
574
574
575 value = self.config(section, name, None, untrusted)
575 value = self.config(section, name, None, untrusted)
576 if value is None:
576 if value is None:
577 if not isinstance(default, str):
577 if not isinstance(default, str):
578 return default
578 return default
579 value = default
579 value = default
580 try:
580 try:
581 return util.sizetoint(value)
581 return util.sizetoint(value)
582 except error.ParseError:
582 except error.ParseError:
583 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
583 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
584 % (section, name, value))
584 % (section, name, value))
585
585
586 def configlist(self, section, name, default=None, untrusted=False):
586 def configlist(self, section, name, default=None, untrusted=False):
587 """parse a configuration element as a list of comma/space separated
587 """parse a configuration element as a list of comma/space separated
588 strings
588 strings
589
589
590 >>> u = ui(); s = 'foo'
590 >>> u = ui(); s = 'foo'
591 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
591 >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
592 >>> u.configlist(s, 'list1')
592 >>> u.configlist(s, 'list1')
593 ['this', 'is', 'a small', 'test']
593 ['this', 'is', 'a small', 'test']
594 """
594 """
595 # default is not always a list
595 # default is not always a list
596 if isinstance(default, bytes):
596 if isinstance(default, bytes):
597 default = config.parselist(default)
597 default = config.parselist(default)
598 return self.configwith(config.parselist, section, name, default or [],
598 return self.configwith(config.parselist, section, name, default or [],
599 'list', untrusted)
599 'list', untrusted)
600
600
601 def configdate(self, section, name, default=None, untrusted=False):
601 def configdate(self, section, name, default=None, untrusted=False):
602 """parse a configuration element as a tuple of ints
602 """parse a configuration element as a tuple of ints
603
603
604 >>> u = ui(); s = 'foo'
604 >>> u = ui(); s = 'foo'
605 >>> u.setconfig(s, 'date', '0 0')
605 >>> u.setconfig(s, 'date', '0 0')
606 >>> u.configdate(s, 'date')
606 >>> u.configdate(s, 'date')
607 (0, 0)
607 (0, 0)
608 """
608 """
609 if self.config(section, name, default, untrusted):
609 if self.config(section, name, default, untrusted):
610 return self.configwith(util.parsedate, section, name, default,
610 return self.configwith(util.parsedate, section, name, default,
611 'date', untrusted)
611 'date', untrusted)
612 return default
612 return default
613
613
614 def hasconfig(self, section, name, untrusted=False):
614 def hasconfig(self, section, name, untrusted=False):
615 return self._data(untrusted).hasitem(section, name)
615 return self._data(untrusted).hasitem(section, name)
616
616
617 def has_section(self, section, untrusted=False):
617 def has_section(self, section, untrusted=False):
618 '''tell whether section exists in config.'''
618 '''tell whether section exists in config.'''
619 return section in self._data(untrusted)
619 return section in self._data(untrusted)
620
620
621 def configitems(self, section, untrusted=False, ignoresub=False):
621 def configitems(self, section, untrusted=False, ignoresub=False):
622 items = self._data(untrusted).items(section)
622 items = self._data(untrusted).items(section)
623 if ignoresub:
623 if ignoresub:
624 newitems = {}
624 newitems = {}
625 for k, v in items:
625 for k, v in items:
626 if ':' not in k:
626 if ':' not in k:
627 newitems[k] = v
627 newitems[k] = v
628 items = newitems.items()
628 items = newitems.items()
629 if self.debugflag and not untrusted and self._reportuntrusted:
629 if self.debugflag and not untrusted and self._reportuntrusted:
630 for k, v in self._ucfg.items(section):
630 for k, v in self._ucfg.items(section):
631 if self._tcfg.get(section, k) != v:
631 if self._tcfg.get(section, k) != v:
632 self.debug("ignoring untrusted configuration option "
632 self.debug("ignoring untrusted configuration option "
633 "%s.%s = %s\n" % (section, k, v))
633 "%s.%s = %s\n" % (section, k, v))
634 return items
634 return items
635
635
636 def walkconfig(self, untrusted=False):
636 def walkconfig(self, untrusted=False):
637 cfg = self._data(untrusted)
637 cfg = self._data(untrusted)
638 for section in cfg.sections():
638 for section in cfg.sections():
639 for name, value in self.configitems(section, untrusted):
639 for name, value in self.configitems(section, untrusted):
640 yield section, name, value
640 yield section, name, value
641
641
642 def plain(self, feature=None):
642 def plain(self, feature=None):
643 '''is plain mode active?
643 '''is plain mode active?
644
644
645 Plain mode means that all configuration variables which affect
645 Plain mode means that all configuration variables which affect
646 the behavior and output of Mercurial should be
646 the behavior and output of Mercurial should be
647 ignored. Additionally, the output should be stable,
647 ignored. Additionally, the output should be stable,
648 reproducible and suitable for use in scripts or applications.
648 reproducible and suitable for use in scripts or applications.
649
649
650 The only way to trigger plain mode is by setting either the
650 The only way to trigger plain mode is by setting either the
651 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
651 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
652
652
653 The return value can either be
653 The return value can either be
654 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
654 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
655 - True otherwise
655 - True otherwise
656 '''
656 '''
657 if ('HGPLAIN' not in encoding.environ and
657 if ('HGPLAIN' not in encoding.environ and
658 'HGPLAINEXCEPT' not in encoding.environ):
658 'HGPLAINEXCEPT' not in encoding.environ):
659 return False
659 return False
660 exceptions = encoding.environ.get('HGPLAINEXCEPT',
660 exceptions = encoding.environ.get('HGPLAINEXCEPT',
661 '').strip().split(',')
661 '').strip().split(',')
662 if feature and exceptions:
662 if feature and exceptions:
663 return feature not in exceptions
663 return feature not in exceptions
664 return True
664 return True
665
665
666 def username(self):
666 def username(self):
667 """Return default username to be used in commits.
667 """Return default username to be used in commits.
668
668
669 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
669 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
670 and stop searching if one of these is set.
670 and stop searching if one of these is set.
671 If not found and ui.askusername is True, ask the user, else use
671 If not found and ui.askusername is True, ask the user, else use
672 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
672 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
673 """
673 """
674 user = encoding.environ.get("HGUSER")
674 user = encoding.environ.get("HGUSER")
675 if user is None:
675 if user is None:
676 user = self.config("ui", ["username", "user"])
676 user = self.config("ui", ["username", "user"])
677 if user is not None:
677 if user is not None:
678 user = os.path.expandvars(user)
678 user = os.path.expandvars(user)
679 if user is None:
679 if user is None:
680 user = encoding.environ.get("EMAIL")
680 user = encoding.environ.get("EMAIL")
681 if user is None and self.configbool("ui", "askusername"):
681 if user is None and self.configbool("ui", "askusername"):
682 user = self.prompt(_("enter a commit username:"), default=None)
682 user = self.prompt(_("enter a commit username:"), default=None)
683 if user is None and not self.interactive():
683 if user is None and not self.interactive():
684 try:
684 try:
685 user = '%s@%s' % (util.getuser(), socket.getfqdn())
685 user = '%s@%s' % (util.getuser(), socket.getfqdn())
686 self.warn(_("no username found, using '%s' instead\n") % user)
686 self.warn(_("no username found, using '%s' instead\n") % user)
687 except KeyError:
687 except KeyError:
688 pass
688 pass
689 if not user:
689 if not user:
690 raise error.Abort(_('no username supplied'),
690 raise error.Abort(_('no username supplied'),
691 hint=_("use 'hg config --edit' "
691 hint=_("use 'hg config --edit' "
692 'to set your username'))
692 'to set your username'))
693 if "\n" in user:
693 if "\n" in user:
694 raise error.Abort(_("username %s contains a newline\n")
694 raise error.Abort(_("username %s contains a newline\n")
695 % repr(user))
695 % repr(user))
696 return user
696 return user
697
697
698 def shortuser(self, user):
698 def shortuser(self, user):
699 """Return a short representation of a user name or email address."""
699 """Return a short representation of a user name or email address."""
700 if not self.verbose:
700 if not self.verbose:
701 user = util.shortuser(user)
701 user = util.shortuser(user)
702 return user
702 return user
703
703
704 def expandpath(self, loc, default=None):
704 def expandpath(self, loc, default=None):
705 """Return repository location relative to cwd or from [paths]"""
705 """Return repository location relative to cwd or from [paths]"""
706 try:
706 try:
707 p = self.paths.getpath(loc)
707 p = self.paths.getpath(loc)
708 if p:
708 if p:
709 return p.rawloc
709 return p.rawloc
710 except error.RepoError:
710 except error.RepoError:
711 pass
711 pass
712
712
713 if default:
713 if default:
714 try:
714 try:
715 p = self.paths.getpath(default)
715 p = self.paths.getpath(default)
716 if p:
716 if p:
717 return p.rawloc
717 return p.rawloc
718 except error.RepoError:
718 except error.RepoError:
719 pass
719 pass
720
720
721 return loc
721 return loc
722
722
723 @util.propertycache
723 @util.propertycache
724 def paths(self):
724 def paths(self):
725 return paths(self)
725 return paths(self)
726
726
727 def pushbuffer(self, error=False, subproc=False, labeled=False):
727 def pushbuffer(self, error=False, subproc=False, labeled=False):
728 """install a buffer to capture standard output of the ui object
728 """install a buffer to capture standard output of the ui object
729
729
730 If error is True, the error output will be captured too.
730 If error is True, the error output will be captured too.
731
731
732 If subproc is True, output from subprocesses (typically hooks) will be
732 If subproc is True, output from subprocesses (typically hooks) will be
733 captured too.
733 captured too.
734
734
735 If labeled is True, any labels associated with buffered
735 If labeled is True, any labels associated with buffered
736 output will be handled. By default, this has no effect
736 output will be handled. By default, this has no effect
737 on the output returned, but extensions and GUI tools may
737 on the output returned, but extensions and GUI tools may
738 handle this argument and returned styled output. If output
738 handle this argument and returned styled output. If output
739 is being buffered so it can be captured and parsed or
739 is being buffered so it can be captured and parsed or
740 processed, labeled should not be set to True.
740 processed, labeled should not be set to True.
741 """
741 """
742 self._buffers.append([])
742 self._buffers.append([])
743 self._bufferstates.append((error, subproc, labeled))
743 self._bufferstates.append((error, subproc, labeled))
744 self._bufferapplylabels = labeled
744 self._bufferapplylabels = labeled
745
745
746 def popbuffer(self):
746 def popbuffer(self):
747 '''pop the last buffer and return the buffered output'''
747 '''pop the last buffer and return the buffered output'''
748 self._bufferstates.pop()
748 self._bufferstates.pop()
749 if self._bufferstates:
749 if self._bufferstates:
750 self._bufferapplylabels = self._bufferstates[-1][2]
750 self._bufferapplylabels = self._bufferstates[-1][2]
751 else:
751 else:
752 self._bufferapplylabels = None
752 self._bufferapplylabels = None
753
753
754 return "".join(self._buffers.pop())
754 return "".join(self._buffers.pop())
755
755
756 def write(self, *args, **opts):
756 def write(self, *args, **opts):
757 '''write args to output
757 '''write args to output
758
758
759 By default, this method simply writes to the buffer or stdout.
759 By default, this method simply writes to the buffer or stdout.
760 Color mode can be set on the UI class to have the output decorated
760 Color mode can be set on the UI class to have the output decorated
761 with color modifier before being written to stdout.
761 with color modifier before being written to stdout.
762
762
763 The color used is controlled by an optional keyword argument, "label".
763 The color used is controlled by an optional keyword argument, "label".
764 This should be a string containing label names separated by space.
764 This should be a string containing label names separated by space.
765 Label names take the form of "topic.type". For example, ui.debug()
765 Label names take the form of "topic.type". For example, ui.debug()
766 issues a label of "ui.debug".
766 issues a label of "ui.debug".
767
767
768 When labeling output for a specific command, a label of
768 When labeling output for a specific command, a label of
769 "cmdname.type" is recommended. For example, status issues
769 "cmdname.type" is recommended. For example, status issues
770 a label of "status.modified" for modified files.
770 a label of "status.modified" for modified files.
771 '''
771 '''
772 if self._buffers and not opts.get('prompt', False):
772 if self._buffers and not opts.get('prompt', False):
773 if self._bufferapplylabels:
773 if self._bufferapplylabels:
774 label = opts.get('label', '')
774 label = opts.get('label', '')
775 self._buffers[-1].extend(self.label(a, label) for a in args)
775 self._buffers[-1].extend(self.label(a, label) for a in args)
776 else:
776 else:
777 self._buffers[-1].extend(args)
777 self._buffers[-1].extend(args)
778 elif self._colormode == 'win32':
778 elif self._colormode == 'win32':
779 # windows color printing is its own can of crab, defer to
779 # windows color printing is its own can of crab, defer to
780 # the color module and that is it.
780 # the color module and that is it.
781 color.win32print(self, self._write, *args, **opts)
781 color.win32print(self, self._write, *args, **opts)
782 else:
782 else:
783 msgs = args
783 msgs = args
784 if self._colormode is not None:
784 if self._colormode is not None:
785 label = opts.get('label', '')
785 label = opts.get('label', '')
786 msgs = [self.label(a, label) for a in args]
786 msgs = [self.label(a, label) for a in args]
787 self._write(*msgs, **opts)
787 self._write(*msgs, **opts)
788
788
789 def _write(self, *msgs, **opts):
789 def _write(self, *msgs, **opts):
790 self._progclear()
790 self._progclear()
791 # opencode timeblockedsection because this is a critical path
791 # opencode timeblockedsection because this is a critical path
792 starttime = util.timer()
792 starttime = util.timer()
793 try:
793 try:
794 for a in msgs:
794 for a in msgs:
795 self.fout.write(a)
795 self.fout.write(a)
796 except IOError as err:
796 except IOError as err:
797 raise error.StdioError(err)
797 raise error.StdioError(err)
798 finally:
798 finally:
799 self._blockedtimes['stdio_blocked'] += \
799 self._blockedtimes['stdio_blocked'] += \
800 (util.timer() - starttime) * 1000
800 (util.timer() - starttime) * 1000
801
801
802 def write_err(self, *args, **opts):
802 def write_err(self, *args, **opts):
803 self._progclear()
803 self._progclear()
804 if self._bufferstates and self._bufferstates[-1][0]:
804 if self._bufferstates and self._bufferstates[-1][0]:
805 self.write(*args, **opts)
805 self.write(*args, **opts)
806 elif self._colormode == 'win32':
806 elif self._colormode == 'win32':
807 # windows color printing is its own can of crab, defer to
807 # windows color printing is its own can of crab, defer to
808 # the color module and that is it.
808 # the color module and that is it.
809 color.win32print(self, self._write_err, *args, **opts)
809 color.win32print(self, self._write_err, *args, **opts)
810 else:
810 else:
811 msgs = args
811 msgs = args
812 if self._colormode is not None:
812 if self._colormode is not None:
813 label = opts.get('label', '')
813 label = opts.get('label', '')
814 msgs = [self.label(a, label) for a in args]
814 msgs = [self.label(a, label) for a in args]
815 self._write_err(*msgs, **opts)
815 self._write_err(*msgs, **opts)
816
816
817 def _write_err(self, *msgs, **opts):
817 def _write_err(self, *msgs, **opts):
818 try:
818 try:
819 with self.timeblockedsection('stdio'):
819 with self.timeblockedsection('stdio'):
820 if not getattr(self.fout, 'closed', False):
820 if not getattr(self.fout, 'closed', False):
821 self.fout.flush()
821 self.fout.flush()
822 for a in msgs:
822 for a in msgs:
823 self.ferr.write(a)
823 self.ferr.write(a)
824 # stderr may be buffered under win32 when redirected to files,
824 # stderr may be buffered under win32 when redirected to files,
825 # including stdout.
825 # including stdout.
826 if not getattr(self.ferr, 'closed', False):
826 if not getattr(self.ferr, 'closed', False):
827 self.ferr.flush()
827 self.ferr.flush()
828 except IOError as inst:
828 except IOError as inst:
829 raise error.StdioError(inst)
829 raise error.StdioError(inst)
830
830
831 def flush(self):
831 def flush(self):
832 # opencode timeblockedsection because this is a critical path
832 # opencode timeblockedsection because this is a critical path
833 starttime = util.timer()
833 starttime = util.timer()
834 try:
834 try:
835 try:
835 try:
836 self.fout.flush()
836 self.fout.flush()
837 except IOError as err:
837 except IOError as err:
838 raise error.StdioError(err)
838 raise error.StdioError(err)
839 finally:
839 finally:
840 try:
840 try:
841 self.ferr.flush()
841 self.ferr.flush()
842 except IOError as err:
842 except IOError as err:
843 raise error.StdioError(err)
843 raise error.StdioError(err)
844 finally:
844 finally:
845 self._blockedtimes['stdio_blocked'] += \
845 self._blockedtimes['stdio_blocked'] += \
846 (util.timer() - starttime) * 1000
846 (util.timer() - starttime) * 1000
847
847
848 def _isatty(self, fh):
848 def _isatty(self, fh):
849 if self.configbool('ui', 'nontty', False):
849 if self.configbool('ui', 'nontty', False):
850 return False
850 return False
851 return util.isatty(fh)
851 return util.isatty(fh)
852
852
853 def disablepager(self):
853 def disablepager(self):
854 self._disablepager = True
854 self._disablepager = True
855
855
856 def pager(self, command):
856 def pager(self, command):
857 """Start a pager for subsequent command output.
857 """Start a pager for subsequent command output.
858
858
859 Commands which produce a long stream of output should call
859 Commands which produce a long stream of output should call
860 this function to activate the user's preferred pagination
860 this function to activate the user's preferred pagination
861 mechanism (which may be no pager). Calling this function
861 mechanism (which may be no pager). Calling this function
862 precludes any future use of interactive functionality, such as
862 precludes any future use of interactive functionality, such as
863 prompting the user or activating curses.
863 prompting the user or activating curses.
864
864
865 Args:
865 Args:
866 command: The full, non-aliased name of the command. That is, "log"
866 command: The full, non-aliased name of the command. That is, "log"
867 not "history, "summary" not "summ", etc.
867 not "history, "summary" not "summ", etc.
868 """
868 """
869 if (self._disablepager
869 if (self._disablepager
870 or self.pageractive
870 or self.pageractive
871 or command in self.configlist('pager', 'ignore')
871 or command in self.configlist('pager', 'ignore')
872 or not self.configbool('ui', 'paginate', True)
872 or not self.configbool('ui', 'paginate', True)
873 or not self.configbool('pager', 'attend-' + command, True)
873 or not self.configbool('pager', 'attend-' + command, True)
874 # TODO: if we want to allow HGPLAINEXCEPT=pager,
874 # TODO: if we want to allow HGPLAINEXCEPT=pager,
875 # formatted() will need some adjustment.
875 # formatted() will need some adjustment.
876 or not self.formatted()
876 or not self.formatted()
877 or self.plain()
877 or self.plain()
878 # TODO: expose debugger-enabled on the UI object
878 # TODO: expose debugger-enabled on the UI object
879 or '--debugger' in pycompat.sysargv):
879 or '--debugger' in pycompat.sysargv):
880 # We only want to paginate if the ui appears to be
880 # We only want to paginate if the ui appears to be
881 # interactive, the user didn't say HGPLAIN or
881 # interactive, the user didn't say HGPLAIN or
882 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
882 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
883 return
883 return
884
884
885 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
885 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
886 if not pagercmd:
886 if not pagercmd:
887 return
887 return
888
888
889 pagerenv = {}
889 pagerenv = {}
890 for name, value in rcutil.defaultpagerenv().items():
890 for name, value in rcutil.defaultpagerenv().items():
891 if name not in encoding.environ:
891 if name not in encoding.environ:
892 pagerenv[name] = value
892 pagerenv[name] = value
893
893
894 self.debug('starting pager for command %r\n' % command)
894 self.debug('starting pager for command %r\n' % command)
895 self.flush()
895 self.flush()
896
896
897 wasformatted = self.formatted()
897 wasformatted = self.formatted()
898 if util.safehasattr(signal, "SIGPIPE"):
898 if util.safehasattr(signal, "SIGPIPE"):
899 signal.signal(signal.SIGPIPE, _catchterm)
899 signal.signal(signal.SIGPIPE, _catchterm)
900 if self._runpager(pagercmd, pagerenv):
900 if self._runpager(pagercmd, pagerenv):
901 self.pageractive = True
901 self.pageractive = True
902 # Preserve the formatted-ness of the UI. This is important
902 # Preserve the formatted-ness of the UI. This is important
903 # because we mess with stdout, which might confuse
903 # because we mess with stdout, which might confuse
904 # auto-detection of things being formatted.
904 # auto-detection of things being formatted.
905 self.setconfig('ui', 'formatted', wasformatted, 'pager')
905 self.setconfig('ui', 'formatted', wasformatted, 'pager')
906 self.setconfig('ui', 'interactive', False, 'pager')
906 self.setconfig('ui', 'interactive', False, 'pager')
907
907
908 # If pagermode differs from color.mode, reconfigure color now that
908 # If pagermode differs from color.mode, reconfigure color now that
909 # pageractive is set.
909 # pageractive is set.
910 cm = self._colormode
910 cm = self._colormode
911 if cm != self.config('color', 'pagermode', cm):
911 if cm != self.config('color', 'pagermode', cm):
912 color.setup(self)
912 color.setup(self)
913 else:
913 else:
914 # If the pager can't be spawned in dispatch when --pager=on is
914 # If the pager can't be spawned in dispatch when --pager=on is
915 # given, don't try again when the command runs, to avoid a duplicate
915 # given, don't try again when the command runs, to avoid a duplicate
916 # warning about a missing pager command.
916 # warning about a missing pager command.
917 self.disablepager()
917 self.disablepager()
918
918
919 def _runpager(self, command, env=None):
919 def _runpager(self, command, env=None):
920 """Actually start the pager and set up file descriptors.
920 """Actually start the pager and set up file descriptors.
921
921
922 This is separate in part so that extensions (like chg) can
922 This is separate in part so that extensions (like chg) can
923 override how a pager is invoked.
923 override how a pager is invoked.
924 """
924 """
925 if command == 'cat':
925 if command == 'cat':
926 # Save ourselves some work.
926 # Save ourselves some work.
927 return False
927 return False
928 # If the command doesn't contain any of these characters, we
928 # If the command doesn't contain any of these characters, we
929 # assume it's a binary and exec it directly. This means for
929 # assume it's a binary and exec it directly. This means for
930 # simple pager command configurations, we can degrade
930 # simple pager command configurations, we can degrade
931 # gracefully and tell the user about their broken pager.
931 # gracefully and tell the user about their broken pager.
932 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
932 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
933
933
934 if pycompat.osname == 'nt' and not shell:
934 if pycompat.osname == 'nt' and not shell:
935 # Window's built-in `more` cannot be invoked with shell=False, but
935 # Window's built-in `more` cannot be invoked with shell=False, but
936 # its `more.com` can. Hide this implementation detail from the
936 # its `more.com` can. Hide this implementation detail from the
937 # user so we can also get sane bad PAGER behavior. MSYS has
937 # user so we can also get sane bad PAGER behavior. MSYS has
938 # `more.exe`, so do a cmd.exe style resolution of the executable to
938 # `more.exe`, so do a cmd.exe style resolution of the executable to
939 # determine which one to use.
939 # determine which one to use.
940 fullcmd = util.findexe(command)
940 fullcmd = util.findexe(command)
941 if not fullcmd:
941 if not fullcmd:
942 self.warn(_("missing pager command '%s', skipping pager\n")
942 self.warn(_("missing pager command '%s', skipping pager\n")
943 % command)
943 % command)
944 return False
944 return False
945
945
946 command = fullcmd
946 command = fullcmd
947
947
948 try:
948 try:
949 pager = subprocess.Popen(
949 pager = subprocess.Popen(
950 command, shell=shell, bufsize=-1,
950 command, shell=shell, bufsize=-1,
951 close_fds=util.closefds, stdin=subprocess.PIPE,
951 close_fds=util.closefds, stdin=subprocess.PIPE,
952 stdout=util.stdout, stderr=util.stderr,
952 stdout=util.stdout, stderr=util.stderr,
953 env=util.shellenviron(env))
953 env=util.shellenviron(env))
954 except OSError as e:
954 except OSError as e:
955 if e.errno == errno.ENOENT and not shell:
955 if e.errno == errno.ENOENT and not shell:
956 self.warn(_("missing pager command '%s', skipping pager\n")
956 self.warn(_("missing pager command '%s', skipping pager\n")
957 % command)
957 % command)
958 return False
958 return False
959 raise
959 raise
960
960
961 # back up original file descriptors
961 # back up original file descriptors
962 stdoutfd = os.dup(util.stdout.fileno())
962 stdoutfd = os.dup(util.stdout.fileno())
963 stderrfd = os.dup(util.stderr.fileno())
963 stderrfd = os.dup(util.stderr.fileno())
964
964
965 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
965 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
966 if self._isatty(util.stderr):
966 if self._isatty(util.stderr):
967 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
967 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
968
968
969 @self.atexit
969 @self.atexit
970 def killpager():
970 def killpager():
971 if util.safehasattr(signal, "SIGINT"):
971 if util.safehasattr(signal, "SIGINT"):
972 signal.signal(signal.SIGINT, signal.SIG_IGN)
972 signal.signal(signal.SIGINT, signal.SIG_IGN)
973 # restore original fds, closing pager.stdin copies in the process
973 # restore original fds, closing pager.stdin copies in the process
974 os.dup2(stdoutfd, util.stdout.fileno())
974 os.dup2(stdoutfd, util.stdout.fileno())
975 os.dup2(stderrfd, util.stderr.fileno())
975 os.dup2(stderrfd, util.stderr.fileno())
976 pager.stdin.close()
976 pager.stdin.close()
977 pager.wait()
977 pager.wait()
978
978
979 return True
979 return True
980
980
981 def atexit(self, func, *args, **kwargs):
981 def atexit(self, func, *args, **kwargs):
982 '''register a function to run after dispatching a request
982 '''register a function to run after dispatching a request
983
983
984 Handlers do not stay registered across request boundaries.'''
984 Handlers do not stay registered across request boundaries.'''
985 self._exithandlers.append((func, args, kwargs))
985 self._exithandlers.append((func, args, kwargs))
986 return func
986 return func
987
987
988 def interface(self, feature):
988 def interface(self, feature):
989 """what interface to use for interactive console features?
989 """what interface to use for interactive console features?
990
990
991 The interface is controlled by the value of `ui.interface` but also by
991 The interface is controlled by the value of `ui.interface` but also by
992 the value of feature-specific configuration. For example:
992 the value of feature-specific configuration. For example:
993
993
994 ui.interface.histedit = text
994 ui.interface.histedit = text
995 ui.interface.chunkselector = curses
995 ui.interface.chunkselector = curses
996
996
997 Here the features are "histedit" and "chunkselector".
997 Here the features are "histedit" and "chunkselector".
998
998
999 The configuration above means that the default interfaces for commands
999 The configuration above means that the default interfaces for commands
1000 is curses, the interface for histedit is text and the interface for
1000 is curses, the interface for histedit is text and the interface for
1001 selecting chunk is crecord (the best curses interface available).
1001 selecting chunk is crecord (the best curses interface available).
1002
1002
1003 Consider the following example:
1003 Consider the following example:
1004 ui.interface = curses
1004 ui.interface = curses
1005 ui.interface.histedit = text
1005 ui.interface.histedit = text
1006
1006
1007 Then histedit will use the text interface and chunkselector will use
1007 Then histedit will use the text interface and chunkselector will use
1008 the default curses interface (crecord at the moment).
1008 the default curses interface (crecord at the moment).
1009 """
1009 """
1010 alldefaults = frozenset(["text", "curses"])
1010 alldefaults = frozenset(["text", "curses"])
1011
1011
1012 featureinterfaces = {
1012 featureinterfaces = {
1013 "chunkselector": [
1013 "chunkselector": [
1014 "text",
1014 "text",
1015 "curses",
1015 "curses",
1016 ]
1016 ]
1017 }
1017 }
1018
1018
1019 # Feature-specific interface
1019 # Feature-specific interface
1020 if feature not in featureinterfaces.keys():
1020 if feature not in featureinterfaces.keys():
1021 # Programming error, not user error
1021 # Programming error, not user error
1022 raise ValueError("Unknown feature requested %s" % feature)
1022 raise ValueError("Unknown feature requested %s" % feature)
1023
1023
1024 availableinterfaces = frozenset(featureinterfaces[feature])
1024 availableinterfaces = frozenset(featureinterfaces[feature])
1025 if alldefaults > availableinterfaces:
1025 if alldefaults > availableinterfaces:
1026 # Programming error, not user error. We need a use case to
1026 # Programming error, not user error. We need a use case to
1027 # define the right thing to do here.
1027 # define the right thing to do here.
1028 raise ValueError(
1028 raise ValueError(
1029 "Feature %s does not handle all default interfaces" %
1029 "Feature %s does not handle all default interfaces" %
1030 feature)
1030 feature)
1031
1031
1032 if self.plain():
1032 if self.plain():
1033 return "text"
1033 return "text"
1034
1034
1035 # Default interface for all the features
1035 # Default interface for all the features
1036 defaultinterface = "text"
1036 defaultinterface = "text"
1037 i = self.config("ui", "interface", None)
1037 i = self.config("ui", "interface", None)
1038 if i in alldefaults:
1038 if i in alldefaults:
1039 defaultinterface = i
1039 defaultinterface = i
1040
1040
1041 choseninterface = defaultinterface
1041 choseninterface = defaultinterface
1042 f = self.config("ui", "interface.%s" % feature, None)
1042 f = self.config("ui", "interface.%s" % feature, None)
1043 if f in availableinterfaces:
1043 if f in availableinterfaces:
1044 choseninterface = f
1044 choseninterface = f
1045
1045
1046 if i is not None and defaultinterface != i:
1046 if i is not None and defaultinterface != i:
1047 if f is not None:
1047 if f is not None:
1048 self.warn(_("invalid value for ui.interface: %s\n") %
1048 self.warn(_("invalid value for ui.interface: %s\n") %
1049 (i,))
1049 (i,))
1050 else:
1050 else:
1051 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1051 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1052 (i, choseninterface))
1052 (i, choseninterface))
1053 if f is not None and choseninterface != f:
1053 if f is not None and choseninterface != f:
1054 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1054 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1055 (feature, f, choseninterface))
1055 (feature, f, choseninterface))
1056
1056
1057 return choseninterface
1057 return choseninterface
1058
1058
1059 def interactive(self):
1059 def interactive(self):
1060 '''is interactive input allowed?
1060 '''is interactive input allowed?
1061
1061
1062 An interactive session is a session where input can be reasonably read
1062 An interactive session is a session where input can be reasonably read
1063 from `sys.stdin'. If this function returns false, any attempt to read
1063 from `sys.stdin'. If this function returns false, any attempt to read
1064 from stdin should fail with an error, unless a sensible default has been
1064 from stdin should fail with an error, unless a sensible default has been
1065 specified.
1065 specified.
1066
1066
1067 Interactiveness is triggered by the value of the `ui.interactive'
1067 Interactiveness is triggered by the value of the `ui.interactive'
1068 configuration variable or - if it is unset - when `sys.stdin' points
1068 configuration variable or - if it is unset - when `sys.stdin' points
1069 to a terminal device.
1069 to a terminal device.
1070
1070
1071 This function refers to input only; for output, see `ui.formatted()'.
1071 This function refers to input only; for output, see `ui.formatted()'.
1072 '''
1072 '''
1073 i = self.configbool("ui", "interactive", None)
1073 i = self.configbool("ui", "interactive", None)
1074 if i is None:
1074 if i is None:
1075 # some environments replace stdin without implementing isatty
1075 # some environments replace stdin without implementing isatty
1076 # usually those are non-interactive
1076 # usually those are non-interactive
1077 return self._isatty(self.fin)
1077 return self._isatty(self.fin)
1078
1078
1079 return i
1079 return i
1080
1080
1081 def termwidth(self):
1081 def termwidth(self):
1082 '''how wide is the terminal in columns?
1082 '''how wide is the terminal in columns?
1083 '''
1083 '''
1084 if 'COLUMNS' in encoding.environ:
1084 if 'COLUMNS' in encoding.environ:
1085 try:
1085 try:
1086 return int(encoding.environ['COLUMNS'])
1086 return int(encoding.environ['COLUMNS'])
1087 except ValueError:
1087 except ValueError:
1088 pass
1088 pass
1089 return scmutil.termsize(self)[0]
1089 return scmutil.termsize(self)[0]
1090
1090
1091 def formatted(self):
1091 def formatted(self):
1092 '''should formatted output be used?
1092 '''should formatted output be used?
1093
1093
1094 It is often desirable to format the output to suite the output medium.
1094 It is often desirable to format the output to suite the output medium.
1095 Examples of this are truncating long lines or colorizing messages.
1095 Examples of this are truncating long lines or colorizing messages.
1096 However, this is not often not desirable when piping output into other
1096 However, this is not often not desirable when piping output into other
1097 utilities, e.g. `grep'.
1097 utilities, e.g. `grep'.
1098
1098
1099 Formatted output is triggered by the value of the `ui.formatted'
1099 Formatted output is triggered by the value of the `ui.formatted'
1100 configuration variable or - if it is unset - when `sys.stdout' points
1100 configuration variable or - if it is unset - when `sys.stdout' points
1101 to a terminal device. Please note that `ui.formatted' should be
1101 to a terminal device. Please note that `ui.formatted' should be
1102 considered an implementation detail; it is not intended for use outside
1102 considered an implementation detail; it is not intended for use outside
1103 Mercurial or its extensions.
1103 Mercurial or its extensions.
1104
1104
1105 This function refers to output only; for input, see `ui.interactive()'.
1105 This function refers to output only; for input, see `ui.interactive()'.
1106 This function always returns false when in plain mode, see `ui.plain()'.
1106 This function always returns false when in plain mode, see `ui.plain()'.
1107 '''
1107 '''
1108 if self.plain():
1108 if self.plain():
1109 return False
1109 return False
1110
1110
1111 i = self.configbool("ui", "formatted", None)
1111 i = self.configbool("ui", "formatted", None)
1112 if i is None:
1112 if i is None:
1113 # some environments replace stdout without implementing isatty
1113 # some environments replace stdout without implementing isatty
1114 # usually those are non-interactive
1114 # usually those are non-interactive
1115 return self._isatty(self.fout)
1115 return self._isatty(self.fout)
1116
1116
1117 return i
1117 return i
1118
1118
1119 def _readline(self, prompt=''):
1119 def _readline(self, prompt=''):
1120 if self._isatty(self.fin):
1120 if self._isatty(self.fin):
1121 try:
1121 try:
1122 # magically add command line editing support, where
1122 # magically add command line editing support, where
1123 # available
1123 # available
1124 import readline
1124 import readline
1125 # force demandimport to really load the module
1125 # force demandimport to really load the module
1126 readline.read_history_file
1126 readline.read_history_file
1127 # windows sometimes raises something other than ImportError
1127 # windows sometimes raises something other than ImportError
1128 except Exception:
1128 except Exception:
1129 pass
1129 pass
1130
1130
1131 # call write() so output goes through subclassed implementation
1131 # call write() so output goes through subclassed implementation
1132 # e.g. color extension on Windows
1132 # e.g. color extension on Windows
1133 self.write(prompt, prompt=True)
1133 self.write(prompt, prompt=True)
1134
1134
1135 # instead of trying to emulate raw_input, swap (self.fin,
1135 # instead of trying to emulate raw_input, swap (self.fin,
1136 # self.fout) with (sys.stdin, sys.stdout)
1136 # self.fout) with (sys.stdin, sys.stdout)
1137 oldin = sys.stdin
1137 oldin = sys.stdin
1138 oldout = sys.stdout
1138 oldout = sys.stdout
1139 sys.stdin = self.fin
1139 sys.stdin = self.fin
1140 sys.stdout = self.fout
1140 sys.stdout = self.fout
1141 # prompt ' ' must exist; otherwise readline may delete entire line
1141 # prompt ' ' must exist; otherwise readline may delete entire line
1142 # - http://bugs.python.org/issue12833
1142 # - http://bugs.python.org/issue12833
1143 with self.timeblockedsection('stdio'):
1143 with self.timeblockedsection('stdio'):
1144 line = raw_input(' ')
1144 line = raw_input(' ')
1145 sys.stdin = oldin
1145 sys.stdin = oldin
1146 sys.stdout = oldout
1146 sys.stdout = oldout
1147
1147
1148 # When stdin is in binary mode on Windows, it can cause
1148 # When stdin is in binary mode on Windows, it can cause
1149 # raw_input() to emit an extra trailing carriage return
1149 # raw_input() to emit an extra trailing carriage return
1150 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1150 if pycompat.oslinesep == '\r\n' and line and line[-1] == '\r':
1151 line = line[:-1]
1151 line = line[:-1]
1152 return line
1152 return line
1153
1153
1154 def prompt(self, msg, default="y"):
1154 def prompt(self, msg, default="y"):
1155 """Prompt user with msg, read response.
1155 """Prompt user with msg, read response.
1156 If ui is not interactive, the default is returned.
1156 If ui is not interactive, the default is returned.
1157 """
1157 """
1158 if not self.interactive():
1158 if not self.interactive():
1159 self.write(msg, ' ', default or '', "\n")
1159 self.write(msg, ' ', default or '', "\n")
1160 return default
1160 return default
1161 try:
1161 try:
1162 r = self._readline(self.label(msg, 'ui.prompt'))
1162 r = self._readline(self.label(msg, 'ui.prompt'))
1163 if not r:
1163 if not r:
1164 r = default
1164 r = default
1165 if self.configbool('ui', 'promptecho'):
1165 if self.configbool('ui', 'promptecho'):
1166 self.write(r, "\n")
1166 self.write(r, "\n")
1167 return r
1167 return r
1168 except EOFError:
1168 except EOFError:
1169 raise error.ResponseExpected()
1169 raise error.ResponseExpected()
1170
1170
1171 @staticmethod
1171 @staticmethod
1172 def extractchoices(prompt):
1172 def extractchoices(prompt):
1173 """Extract prompt message and list of choices from specified prompt.
1173 """Extract prompt message and list of choices from specified prompt.
1174
1174
1175 This returns tuple "(message, choices)", and "choices" is the
1175 This returns tuple "(message, choices)", and "choices" is the
1176 list of tuple "(response character, text without &)".
1176 list of tuple "(response character, text without &)".
1177
1177
1178 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1178 >>> ui.extractchoices("awake? $$ &Yes $$ &No")
1179 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1179 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1180 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1180 >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
1181 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1181 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1182 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1182 >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
1183 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1183 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1184 """
1184 """
1185
1185
1186 # Sadly, the prompt string may have been built with a filename
1186 # Sadly, the prompt string may have been built with a filename
1187 # containing "$$" so let's try to find the first valid-looking
1187 # containing "$$" so let's try to find the first valid-looking
1188 # prompt to start parsing. Sadly, we also can't rely on
1188 # prompt to start parsing. Sadly, we also can't rely on
1189 # choices containing spaces, ASCII, or basically anything
1189 # choices containing spaces, ASCII, or basically anything
1190 # except an ampersand followed by a character.
1190 # except an ampersand followed by a character.
1191 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1191 m = re.match(r'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1192 msg = m.group(1)
1192 msg = m.group(1)
1193 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1193 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1194 return (msg,
1194 return (msg,
1195 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1195 [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
1196 for s in choices])
1196 for s in choices])
1197
1197
1198 def promptchoice(self, prompt, default=0):
1198 def promptchoice(self, prompt, default=0):
1199 """Prompt user with a message, read response, and ensure it matches
1199 """Prompt user with a message, read response, and ensure it matches
1200 one of the provided choices. The prompt is formatted as follows:
1200 one of the provided choices. The prompt is formatted as follows:
1201
1201
1202 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1202 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1203
1203
1204 The index of the choice is returned. Responses are case
1204 The index of the choice is returned. Responses are case
1205 insensitive. If ui is not interactive, the default is
1205 insensitive. If ui is not interactive, the default is
1206 returned.
1206 returned.
1207 """
1207 """
1208
1208
1209 msg, choices = self.extractchoices(prompt)
1209 msg, choices = self.extractchoices(prompt)
1210 resps = [r for r, t in choices]
1210 resps = [r for r, t in choices]
1211 while True:
1211 while True:
1212 r = self.prompt(msg, resps[default])
1212 r = self.prompt(msg, resps[default])
1213 if r.lower() in resps:
1213 if r.lower() in resps:
1214 return resps.index(r.lower())
1214 return resps.index(r.lower())
1215 self.write(_("unrecognized response\n"))
1215 self.write(_("unrecognized response\n"))
1216
1216
1217 def getpass(self, prompt=None, default=None):
1217 def getpass(self, prompt=None, default=None):
1218 if not self.interactive():
1218 if not self.interactive():
1219 return default
1219 return default
1220 try:
1220 try:
1221 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1221 self.write_err(self.label(prompt or _('password: '), 'ui.prompt'))
1222 # disable getpass() only if explicitly specified. it's still valid
1222 # disable getpass() only if explicitly specified. it's still valid
1223 # to interact with tty even if fin is not a tty.
1223 # to interact with tty even if fin is not a tty.
1224 with self.timeblockedsection('stdio'):
1224 with self.timeblockedsection('stdio'):
1225 if self.configbool('ui', 'nontty'):
1225 if self.configbool('ui', 'nontty'):
1226 l = self.fin.readline()
1226 l = self.fin.readline()
1227 if not l:
1227 if not l:
1228 raise EOFError
1228 raise EOFError
1229 return l.rstrip('\n')
1229 return l.rstrip('\n')
1230 else:
1230 else:
1231 return getpass.getpass('')
1231 return getpass.getpass('')
1232 except EOFError:
1232 except EOFError:
1233 raise error.ResponseExpected()
1233 raise error.ResponseExpected()
1234 def status(self, *msg, **opts):
1234 def status(self, *msg, **opts):
1235 '''write status message to output (if ui.quiet is False)
1235 '''write status message to output (if ui.quiet is False)
1236
1236
1237 This adds an output label of "ui.status".
1237 This adds an output label of "ui.status".
1238 '''
1238 '''
1239 if not self.quiet:
1239 if not self.quiet:
1240 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1240 opts[r'label'] = opts.get(r'label', '') + ' ui.status'
1241 self.write(*msg, **opts)
1241 self.write(*msg, **opts)
1242 def warn(self, *msg, **opts):
1242 def warn(self, *msg, **opts):
1243 '''write warning message to output (stderr)
1243 '''write warning message to output (stderr)
1244
1244
1245 This adds an output label of "ui.warning".
1245 This adds an output label of "ui.warning".
1246 '''
1246 '''
1247 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1247 opts[r'label'] = opts.get(r'label', '') + ' ui.warning'
1248 self.write_err(*msg, **opts)
1248 self.write_err(*msg, **opts)
1249 def note(self, *msg, **opts):
1249 def note(self, *msg, **opts):
1250 '''write note to output (if ui.verbose is True)
1250 '''write note to output (if ui.verbose is True)
1251
1251
1252 This adds an output label of "ui.note".
1252 This adds an output label of "ui.note".
1253 '''
1253 '''
1254 if self.verbose:
1254 if self.verbose:
1255 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1255 opts[r'label'] = opts.get(r'label', '') + ' ui.note'
1256 self.write(*msg, **opts)
1256 self.write(*msg, **opts)
1257 def debug(self, *msg, **opts):
1257 def debug(self, *msg, **opts):
1258 '''write debug message to output (if ui.debugflag is True)
1258 '''write debug message to output (if ui.debugflag is True)
1259
1259
1260 This adds an output label of "ui.debug".
1260 This adds an output label of "ui.debug".
1261 '''
1261 '''
1262 if self.debugflag:
1262 if self.debugflag:
1263 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1263 opts[r'label'] = opts.get(r'label', '') + ' ui.debug'
1264 self.write(*msg, **opts)
1264 self.write(*msg, **opts)
1265
1265
1266 def edit(self, text, user, extra=None, editform=None, pending=None,
1266 def edit(self, text, user, extra=None, editform=None, pending=None,
1267 repopath=None):
1267 repopath=None):
1268 extra_defaults = {
1268 extra_defaults = {
1269 'prefix': 'editor',
1269 'prefix': 'editor',
1270 'suffix': '.txt',
1270 'suffix': '.txt',
1271 }
1271 }
1272 if extra is not None:
1272 if extra is not None:
1273 extra_defaults.update(extra)
1273 extra_defaults.update(extra)
1274 extra = extra_defaults
1274 extra = extra_defaults
1275
1275
1276 rdir = None
1276 rdir = None
1277 if self.configbool('experimental', 'editortmpinhg'):
1277 if self.configbool('experimental', 'editortmpinhg'):
1278 rdir = repopath
1278 rdir = repopath
1279 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1279 (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1280 suffix=extra['suffix'],
1280 suffix=extra['suffix'],
1281 dir=rdir)
1281 dir=rdir)
1282 try:
1282 try:
1283 f = os.fdopen(fd, r'wb')
1283 f = os.fdopen(fd, r'wb')
1284 f.write(util.tonativeeol(text))
1284 f.write(util.tonativeeol(text))
1285 f.close()
1285 f.close()
1286
1286
1287 environ = {'HGUSER': user}
1287 environ = {'HGUSER': user}
1288 if 'transplant_source' in extra:
1288 if 'transplant_source' in extra:
1289 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1289 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1290 for label in ('intermediate-source', 'source', 'rebase_source'):
1290 for label in ('intermediate-source', 'source', 'rebase_source'):
1291 if label in extra:
1291 if label in extra:
1292 environ.update({'HGREVISION': extra[label]})
1292 environ.update({'HGREVISION': extra[label]})
1293 break
1293 break
1294 if editform:
1294 if editform:
1295 environ.update({'HGEDITFORM': editform})
1295 environ.update({'HGEDITFORM': editform})
1296 if pending:
1296 if pending:
1297 environ.update({'HG_PENDING': pending})
1297 environ.update({'HG_PENDING': pending})
1298
1298
1299 editor = self.geteditor()
1299 editor = self.geteditor()
1300
1300
1301 self.system("%s \"%s\"" % (editor, name),
1301 self.system("%s \"%s\"" % (editor, name),
1302 environ=environ,
1302 environ=environ,
1303 onerr=error.Abort, errprefix=_("edit failed"),
1303 onerr=error.Abort, errprefix=_("edit failed"),
1304 blockedtag='editor')
1304 blockedtag='editor')
1305
1305
1306 f = open(name, r'rb')
1306 f = open(name, r'rb')
1307 t = util.fromnativeeol(f.read())
1307 t = util.fromnativeeol(f.read())
1308 f.close()
1308 f.close()
1309 finally:
1309 finally:
1310 os.unlink(name)
1310 os.unlink(name)
1311
1311
1312 return t
1312 return t
1313
1313
1314 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1314 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1315 blockedtag=None):
1315 blockedtag=None):
1316 '''execute shell command with appropriate output stream. command
1316 '''execute shell command with appropriate output stream. command
1317 output will be redirected if fout is not stdout.
1317 output will be redirected if fout is not stdout.
1318
1318
1319 if command fails and onerr is None, return status, else raise onerr
1319 if command fails and onerr is None, return status, else raise onerr
1320 object as exception.
1320 object as exception.
1321 '''
1321 '''
1322 if blockedtag is None:
1322 if blockedtag is None:
1323 # Long cmds tend to be because of an absolute path on cmd. Keep
1323 # Long cmds tend to be because of an absolute path on cmd. Keep
1324 # the tail end instead
1324 # the tail end instead
1325 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1325 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1326 blockedtag = 'unknown_system_' + cmdsuffix
1326 blockedtag = 'unknown_system_' + cmdsuffix
1327 out = self.fout
1327 out = self.fout
1328 if any(s[1] for s in self._bufferstates):
1328 if any(s[1] for s in self._bufferstates):
1329 out = self
1329 out = self
1330 with self.timeblockedsection(blockedtag):
1330 with self.timeblockedsection(blockedtag):
1331 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1331 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1332 if rc and onerr:
1332 if rc and onerr:
1333 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1333 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1334 util.explainexit(rc)[0])
1334 util.explainexit(rc)[0])
1335 if errprefix:
1335 if errprefix:
1336 errmsg = '%s: %s' % (errprefix, errmsg)
1336 errmsg = '%s: %s' % (errprefix, errmsg)
1337 raise onerr(errmsg)
1337 raise onerr(errmsg)
1338 return rc
1338 return rc
1339
1339
1340 def _runsystem(self, cmd, environ, cwd, out):
1340 def _runsystem(self, cmd, environ, cwd, out):
1341 """actually execute the given shell command (can be overridden by
1341 """actually execute the given shell command (can be overridden by
1342 extensions like chg)"""
1342 extensions like chg)"""
1343 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1343 return util.system(cmd, environ=environ, cwd=cwd, out=out)
1344
1344
1345 def traceback(self, exc=None, force=False):
1345 def traceback(self, exc=None, force=False):
1346 '''print exception traceback if traceback printing enabled or forced.
1346 '''print exception traceback if traceback printing enabled or forced.
1347 only to call in exception handler. returns true if traceback
1347 only to call in exception handler. returns true if traceback
1348 printed.'''
1348 printed.'''
1349 if self.tracebackflag or force:
1349 if self.tracebackflag or force:
1350 if exc is None:
1350 if exc is None:
1351 exc = sys.exc_info()
1351 exc = sys.exc_info()
1352 cause = getattr(exc[1], 'cause', None)
1352 cause = getattr(exc[1], 'cause', None)
1353
1353
1354 if cause is not None:
1354 if cause is not None:
1355 causetb = traceback.format_tb(cause[2])
1355 causetb = traceback.format_tb(cause[2])
1356 exctb = traceback.format_tb(exc[2])
1356 exctb = traceback.format_tb(exc[2])
1357 exconly = traceback.format_exception_only(cause[0], cause[1])
1357 exconly = traceback.format_exception_only(cause[0], cause[1])
1358
1358
1359 # exclude frame where 'exc' was chained and rethrown from exctb
1359 # exclude frame where 'exc' was chained and rethrown from exctb
1360 self.write_err('Traceback (most recent call last):\n',
1360 self.write_err('Traceback (most recent call last):\n',
1361 ''.join(exctb[:-1]),
1361 ''.join(exctb[:-1]),
1362 ''.join(causetb),
1362 ''.join(causetb),
1363 ''.join(exconly))
1363 ''.join(exconly))
1364 else:
1364 else:
1365 output = traceback.format_exception(exc[0], exc[1], exc[2])
1365 output = traceback.format_exception(exc[0], exc[1], exc[2])
1366 data = r''.join(output)
1366 data = r''.join(output)
1367 if pycompat.ispy3:
1367 if pycompat.ispy3:
1368 enc = pycompat.sysstr(encoding.encoding)
1368 enc = pycompat.sysstr(encoding.encoding)
1369 data = data.encode(enc, errors=r'replace')
1369 data = data.encode(enc, errors=r'replace')
1370 self.write_err(data)
1370 self.write_err(data)
1371 return self.tracebackflag or force
1371 return self.tracebackflag or force
1372
1372
1373 def geteditor(self):
1373 def geteditor(self):
1374 '''return editor to use'''
1374 '''return editor to use'''
1375 if pycompat.sysplatform == 'plan9':
1375 if pycompat.sysplatform == 'plan9':
1376 # vi is the MIPS instruction simulator on Plan 9. We
1376 # vi is the MIPS instruction simulator on Plan 9. We
1377 # instead default to E to plumb commit messages to
1377 # instead default to E to plumb commit messages to
1378 # avoid confusion.
1378 # avoid confusion.
1379 editor = 'E'
1379 editor = 'E'
1380 else:
1380 else:
1381 editor = 'vi'
1381 editor = 'vi'
1382 return (encoding.environ.get("HGEDITOR") or
1382 return (encoding.environ.get("HGEDITOR") or
1383 self.config("ui", "editor", editor))
1383 self.config("ui", "editor", editor))
1384
1384
1385 @util.propertycache
1385 @util.propertycache
1386 def _progbar(self):
1386 def _progbar(self):
1387 """setup the progbar singleton to the ui object"""
1387 """setup the progbar singleton to the ui object"""
1388 if (self.quiet or self.debugflag
1388 if (self.quiet or self.debugflag
1389 or self.configbool('progress', 'disable', False)
1389 or self.configbool('progress', 'disable', False)
1390 or not progress.shouldprint(self)):
1390 or not progress.shouldprint(self)):
1391 return None
1391 return None
1392 return getprogbar(self)
1392 return getprogbar(self)
1393
1393
1394 def _progclear(self):
1394 def _progclear(self):
1395 """clear progress bar output if any. use it before any output"""
1395 """clear progress bar output if any. use it before any output"""
1396 if '_progbar' not in vars(self): # nothing loaded yet
1396 if '_progbar' not in vars(self): # nothing loaded yet
1397 return
1397 return
1398 if self._progbar is not None and self._progbar.printed:
1398 if self._progbar is not None and self._progbar.printed:
1399 self._progbar.clear()
1399 self._progbar.clear()
1400
1400
1401 def progress(self, topic, pos, item="", unit="", total=None):
1401 def progress(self, topic, pos, item="", unit="", total=None):
1402 '''show a progress message
1402 '''show a progress message
1403
1403
1404 By default a textual progress bar will be displayed if an operation
1404 By default a textual progress bar will be displayed if an operation
1405 takes too long. 'topic' is the current operation, 'item' is a
1405 takes too long. 'topic' is the current operation, 'item' is a
1406 non-numeric marker of the current position (i.e. the currently
1406 non-numeric marker of the current position (i.e. the currently
1407 in-process file), 'pos' is the current numeric position (i.e.
1407 in-process file), 'pos' is the current numeric position (i.e.
1408 revision, bytes, etc.), unit is a corresponding unit label,
1408 revision, bytes, etc.), unit is a corresponding unit label,
1409 and total is the highest expected pos.
1409 and total is the highest expected pos.
1410
1410
1411 Multiple nested topics may be active at a time.
1411 Multiple nested topics may be active at a time.
1412
1412
1413 All topics should be marked closed by setting pos to None at
1413 All topics should be marked closed by setting pos to None at
1414 termination.
1414 termination.
1415 '''
1415 '''
1416 if self._progbar is not None:
1416 if self._progbar is not None:
1417 self._progbar.progress(topic, pos, item=item, unit=unit,
1417 self._progbar.progress(topic, pos, item=item, unit=unit,
1418 total=total)
1418 total=total)
1419 if pos is None or not self.configbool('progress', 'debug'):
1419 if pos is None or not self.configbool('progress', 'debug'):
1420 return
1420 return
1421
1421
1422 if unit:
1422 if unit:
1423 unit = ' ' + unit
1423 unit = ' ' + unit
1424 if item:
1424 if item:
1425 item = ' ' + item
1425 item = ' ' + item
1426
1426
1427 if total:
1427 if total:
1428 pct = 100.0 * pos / total
1428 pct = 100.0 * pos / total
1429 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1429 self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
1430 % (topic, item, pos, total, unit, pct))
1430 % (topic, item, pos, total, unit, pct))
1431 else:
1431 else:
1432 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1432 self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
1433
1433
1434 def log(self, service, *msg, **opts):
1434 def log(self, service, *msg, **opts):
1435 '''hook for logging facility extensions
1435 '''hook for logging facility extensions
1436
1436
1437 service should be a readily-identifiable subsystem, which will
1437 service should be a readily-identifiable subsystem, which will
1438 allow filtering.
1438 allow filtering.
1439
1439
1440 *msg should be a newline-terminated format string to log, and
1440 *msg should be a newline-terminated format string to log, and
1441 then any values to %-format into that format string.
1441 then any values to %-format into that format string.
1442
1442
1443 **opts currently has no defined meanings.
1443 **opts currently has no defined meanings.
1444 '''
1444 '''
1445
1445
1446 def label(self, msg, label):
1446 def label(self, msg, label):
1447 '''style msg based on supplied label
1447 '''style msg based on supplied label
1448
1448
1449 If some color mode is enabled, this will add the necessary control
1449 If some color mode is enabled, this will add the necessary control
1450 characters to apply such color. In addition, 'debug' color mode adds
1450 characters to apply such color. In addition, 'debug' color mode adds
1451 markup showing which label affects a piece of text.
1451 markup showing which label affects a piece of text.
1452
1452
1453 ui.write(s, 'label') is equivalent to
1453 ui.write(s, 'label') is equivalent to
1454 ui.write(ui.label(s, 'label')).
1454 ui.write(ui.label(s, 'label')).
1455 '''
1455 '''
1456 if self._colormode is not None:
1456 if self._colormode is not None:
1457 return color.colorlabel(self, msg, label)
1457 return color.colorlabel(self, msg, label)
1458 return msg
1458 return msg
1459
1459
1460 def develwarn(self, msg, stacklevel=1, config=None):
1460 def develwarn(self, msg, stacklevel=1, config=None):
1461 """issue a developer warning message
1461 """issue a developer warning message
1462
1462
1463 Use 'stacklevel' to report the offender some layers further up in the
1463 Use 'stacklevel' to report the offender some layers further up in the
1464 stack.
1464 stack.
1465 """
1465 """
1466 if not self.configbool('devel', 'all-warnings'):
1466 if not self.configbool('devel', 'all-warnings'):
1467 if config is not None and not self.configbool('devel', config):
1467 if config is not None and not self.configbool('devel', config):
1468 return
1468 return
1469 msg = 'devel-warn: ' + msg
1469 msg = 'devel-warn: ' + msg
1470 stacklevel += 1 # get in develwarn
1470 stacklevel += 1 # get in develwarn
1471 if self.tracebackflag:
1471 if self.tracebackflag:
1472 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1472 util.debugstacktrace(msg, stacklevel, self.ferr, self.fout)
1473 self.log('develwarn', '%s at:\n%s' %
1473 self.log('develwarn', '%s at:\n%s' %
1474 (msg, ''.join(util.getstackframes(stacklevel))))
1474 (msg, ''.join(util.getstackframes(stacklevel))))
1475 else:
1475 else:
1476 curframe = inspect.currentframe()
1476 curframe = inspect.currentframe()
1477 calframe = inspect.getouterframes(curframe, 2)
1477 calframe = inspect.getouterframes(curframe, 2)
1478 self.write_err('%s at: %s:%s (%s)\n'
1478 self.write_err('%s at: %s:%s (%s)\n'
1479 % ((msg,) + calframe[stacklevel][1:4]))
1479 % ((msg,) + calframe[stacklevel][1:4]))
1480 self.log('develwarn', '%s at: %s:%s (%s)\n',
1480 self.log('develwarn', '%s at: %s:%s (%s)\n',
1481 msg, *calframe[stacklevel][1:4])
1481 msg, *calframe[stacklevel][1:4])
1482 curframe = calframe = None # avoid cycles
1482 curframe = calframe = None # avoid cycles
1483
1483
1484 def deprecwarn(self, msg, version):
1484 def deprecwarn(self, msg, version):
1485 """issue a deprecation warning
1485 """issue a deprecation warning
1486
1486
1487 - msg: message explaining what is deprecated and how to upgrade,
1487 - msg: message explaining what is deprecated and how to upgrade,
1488 - version: last version where the API will be supported,
1488 - version: last version where the API will be supported,
1489 """
1489 """
1490 if not (self.configbool('devel', 'all-warnings')
1490 if not (self.configbool('devel', 'all-warnings')
1491 or self.configbool('devel', 'deprec-warn')):
1491 or self.configbool('devel', 'deprec-warn')):
1492 return
1492 return
1493 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1493 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1494 " update your code.)") % version
1494 " update your code.)") % version
1495 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1495 self.develwarn(msg, stacklevel=2, config='deprec-warn')
1496
1496
1497 def exportableenviron(self):
1497 def exportableenviron(self):
1498 """The environment variables that are safe to export, e.g. through
1498 """The environment variables that are safe to export, e.g. through
1499 hgweb.
1499 hgweb.
1500 """
1500 """
1501 return self._exportableenviron
1501 return self._exportableenviron
1502
1502
1503 @contextlib.contextmanager
1503 @contextlib.contextmanager
1504 def configoverride(self, overrides, source=""):
1504 def configoverride(self, overrides, source=""):
1505 """Context manager for temporary config overrides
1505 """Context manager for temporary config overrides
1506 `overrides` must be a dict of the following structure:
1506 `overrides` must be a dict of the following structure:
1507 {(section, name) : value}"""
1507 {(section, name) : value}"""
1508 backups = {}
1508 backups = {}
1509 try:
1509 try:
1510 for (section, name), value in overrides.items():
1510 for (section, name), value in overrides.items():
1511 backups[(section, name)] = self.backupconfig(section, name)
1511 backups[(section, name)] = self.backupconfig(section, name)
1512 self.setconfig(section, name, value, source)
1512 self.setconfig(section, name, value, source)
1513 yield
1513 yield
1514 finally:
1514 finally:
1515 for __, backup in backups.items():
1515 for __, backup in backups.items():
1516 self.restoreconfig(backup)
1516 self.restoreconfig(backup)
1517 # just restoring ui.quiet config to the previous value is not enough
1517 # just restoring ui.quiet config to the previous value is not enough
1518 # as it does not update ui.quiet class member
1518 # as it does not update ui.quiet class member
1519 if ('ui', 'quiet') in overrides:
1519 if ('ui', 'quiet') in overrides:
1520 self.fixconfig(section='ui')
1520 self.fixconfig(section='ui')
1521
1521
1522 class paths(dict):
1522 class paths(dict):
1523 """Represents a collection of paths and their configs.
1523 """Represents a collection of paths and their configs.
1524
1524
1525 Data is initially derived from ui instances and the config files they have
1525 Data is initially derived from ui instances and the config files they have
1526 loaded.
1526 loaded.
1527 """
1527 """
1528 def __init__(self, ui):
1528 def __init__(self, ui):
1529 dict.__init__(self)
1529 dict.__init__(self)
1530
1530
1531 for name, loc in ui.configitems('paths', ignoresub=True):
1531 for name, loc in ui.configitems('paths', ignoresub=True):
1532 # No location is the same as not existing.
1532 # No location is the same as not existing.
1533 if not loc:
1533 if not loc:
1534 continue
1534 continue
1535 loc, sub = ui.configsuboptions('paths', name)
1535 loc, sub = ui.configsuboptions('paths', name)
1536 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1536 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1537
1537
1538 def getpath(self, name, default=None):
1538 def getpath(self, name, default=None):
1539 """Return a ``path`` from a string, falling back to default.
1539 """Return a ``path`` from a string, falling back to default.
1540
1540
1541 ``name`` can be a named path or locations. Locations are filesystem
1541 ``name`` can be a named path or locations. Locations are filesystem
1542 paths or URIs.
1542 paths or URIs.
1543
1543
1544 Returns None if ``name`` is not a registered path, a URI, or a local
1544 Returns None if ``name`` is not a registered path, a URI, or a local
1545 path to a repo.
1545 path to a repo.
1546 """
1546 """
1547 # Only fall back to default if no path was requested.
1547 # Only fall back to default if no path was requested.
1548 if name is None:
1548 if name is None:
1549 if not default:
1549 if not default:
1550 default = ()
1550 default = ()
1551 elif not isinstance(default, (tuple, list)):
1551 elif not isinstance(default, (tuple, list)):
1552 default = (default,)
1552 default = (default,)
1553 for k in default:
1553 for k in default:
1554 try:
1554 try:
1555 return self[k]
1555 return self[k]
1556 except KeyError:
1556 except KeyError:
1557 continue
1557 continue
1558 return None
1558 return None
1559
1559
1560 # Most likely empty string.
1560 # Most likely empty string.
1561 # This may need to raise in the future.
1561 # This may need to raise in the future.
1562 if not name:
1562 if not name:
1563 return None
1563 return None
1564
1564
1565 try:
1565 try:
1566 return self[name]
1566 return self[name]
1567 except KeyError:
1567 except KeyError:
1568 # Try to resolve as a local path or URI.
1568 # Try to resolve as a local path or URI.
1569 try:
1569 try:
1570 # We don't pass sub-options in, so no need to pass ui instance.
1570 # We don't pass sub-options in, so no need to pass ui instance.
1571 return path(None, None, rawloc=name)
1571 return path(None, None, rawloc=name)
1572 except ValueError:
1572 except ValueError:
1573 raise error.RepoError(_('repository %s does not exist') %
1573 raise error.RepoError(_('repository %s does not exist') %
1574 name)
1574 name)
1575
1575
1576 _pathsuboptions = {}
1576 _pathsuboptions = {}
1577
1577
1578 def pathsuboption(option, attr):
1578 def pathsuboption(option, attr):
1579 """Decorator used to declare a path sub-option.
1579 """Decorator used to declare a path sub-option.
1580
1580
1581 Arguments are the sub-option name and the attribute it should set on
1581 Arguments are the sub-option name and the attribute it should set on
1582 ``path`` instances.
1582 ``path`` instances.
1583
1583
1584 The decorated function will receive as arguments a ``ui`` instance,
1584 The decorated function will receive as arguments a ``ui`` instance,
1585 ``path`` instance, and the string value of this option from the config.
1585 ``path`` instance, and the string value of this option from the config.
1586 The function should return the value that will be set on the ``path``
1586 The function should return the value that will be set on the ``path``
1587 instance.
1587 instance.
1588
1588
1589 This decorator can be used to perform additional verification of
1589 This decorator can be used to perform additional verification of
1590 sub-options and to change the type of sub-options.
1590 sub-options and to change the type of sub-options.
1591 """
1591 """
1592 def register(func):
1592 def register(func):
1593 _pathsuboptions[option] = (attr, func)
1593 _pathsuboptions[option] = (attr, func)
1594 return func
1594 return func
1595 return register
1595 return register
1596
1596
1597 @pathsuboption('pushurl', 'pushloc')
1597 @pathsuboption('pushurl', 'pushloc')
1598 def pushurlpathoption(ui, path, value):
1598 def pushurlpathoption(ui, path, value):
1599 u = util.url(value)
1599 u = util.url(value)
1600 # Actually require a URL.
1600 # Actually require a URL.
1601 if not u.scheme:
1601 if not u.scheme:
1602 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1602 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1603 return None
1603 return None
1604
1604
1605 # Don't support the #foo syntax in the push URL to declare branch to
1605 # Don't support the #foo syntax in the push URL to declare branch to
1606 # push.
1606 # push.
1607 if u.fragment:
1607 if u.fragment:
1608 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1608 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1609 'ignoring)\n') % path.name)
1609 'ignoring)\n') % path.name)
1610 u.fragment = None
1610 u.fragment = None
1611
1611
1612 return str(u)
1612 return str(u)
1613
1613
1614 @pathsuboption('pushrev', 'pushrev')
1614 @pathsuboption('pushrev', 'pushrev')
1615 def pushrevpathoption(ui, path, value):
1615 def pushrevpathoption(ui, path, value):
1616 return value
1616 return value
1617
1617
1618 class path(object):
1618 class path(object):
1619 """Represents an individual path and its configuration."""
1619 """Represents an individual path and its configuration."""
1620
1620
1621 def __init__(self, ui, name, rawloc=None, suboptions=None):
1621 def __init__(self, ui, name, rawloc=None, suboptions=None):
1622 """Construct a path from its config options.
1622 """Construct a path from its config options.
1623
1623
1624 ``ui`` is the ``ui`` instance the path is coming from.
1624 ``ui`` is the ``ui`` instance the path is coming from.
1625 ``name`` is the symbolic name of the path.
1625 ``name`` is the symbolic name of the path.
1626 ``rawloc`` is the raw location, as defined in the config.
1626 ``rawloc`` is the raw location, as defined in the config.
1627 ``pushloc`` is the raw locations pushes should be made to.
1627 ``pushloc`` is the raw locations pushes should be made to.
1628
1628
1629 If ``name`` is not defined, we require that the location be a) a local
1629 If ``name`` is not defined, we require that the location be a) a local
1630 filesystem path with a .hg directory or b) a URL. If not,
1630 filesystem path with a .hg directory or b) a URL. If not,
1631 ``ValueError`` is raised.
1631 ``ValueError`` is raised.
1632 """
1632 """
1633 if not rawloc:
1633 if not rawloc:
1634 raise ValueError('rawloc must be defined')
1634 raise ValueError('rawloc must be defined')
1635
1635
1636 # Locations may define branches via syntax <base>#<branch>.
1636 # Locations may define branches via syntax <base>#<branch>.
1637 u = util.url(rawloc)
1637 u = util.url(rawloc)
1638 branch = None
1638 branch = None
1639 if u.fragment:
1639 if u.fragment:
1640 branch = u.fragment
1640 branch = u.fragment
1641 u.fragment = None
1641 u.fragment = None
1642
1642
1643 self.url = u
1643 self.url = u
1644 self.branch = branch
1644 self.branch = branch
1645
1645
1646 self.name = name
1646 self.name = name
1647 self.rawloc = rawloc
1647 self.rawloc = rawloc
1648 self.loc = '%s' % u
1648 self.loc = '%s' % u
1649
1649
1650 # When given a raw location but not a symbolic name, validate the
1650 # When given a raw location but not a symbolic name, validate the
1651 # location is valid.
1651 # location is valid.
1652 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1652 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1653 raise ValueError('location is not a URL or path to a local '
1653 raise ValueError('location is not a URL or path to a local '
1654 'repo: %s' % rawloc)
1654 'repo: %s' % rawloc)
1655
1655
1656 suboptions = suboptions or {}
1656 suboptions = suboptions or {}
1657
1657
1658 # Now process the sub-options. If a sub-option is registered, its
1658 # Now process the sub-options. If a sub-option is registered, its
1659 # attribute will always be present. The value will be None if there
1659 # attribute will always be present. The value will be None if there
1660 # was no valid sub-option.
1660 # was no valid sub-option.
1661 for suboption, (attr, func) in _pathsuboptions.iteritems():
1661 for suboption, (attr, func) in _pathsuboptions.iteritems():
1662 if suboption not in suboptions:
1662 if suboption not in suboptions:
1663 setattr(self, attr, None)
1663 setattr(self, attr, None)
1664 continue
1664 continue
1665
1665
1666 value = func(ui, self, suboptions[suboption])
1666 value = func(ui, self, suboptions[suboption])
1667 setattr(self, attr, value)
1667 setattr(self, attr, value)
1668
1668
1669 def _isvalidlocalpath(self, path):
1669 def _isvalidlocalpath(self, path):
1670 """Returns True if the given path is a potentially valid repository.
1670 """Returns True if the given path is a potentially valid repository.
1671 This is its own function so that extensions can change the definition of
1671 This is its own function so that extensions can change the definition of
1672 'valid' in this case (like when pulling from a git repo into a hg
1672 'valid' in this case (like when pulling from a git repo into a hg
1673 one)."""
1673 one)."""
1674 return os.path.isdir(os.path.join(path, '.hg'))
1674 return os.path.isdir(os.path.join(path, '.hg'))
1675
1675
1676 @property
1676 @property
1677 def suboptions(self):
1677 def suboptions(self):
1678 """Return sub-options and their values for this path.
1678 """Return sub-options and their values for this path.
1679
1679
1680 This is intended to be used for presentation purposes.
1680 This is intended to be used for presentation purposes.
1681 """
1681 """
1682 d = {}
1682 d = {}
1683 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1683 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1684 value = getattr(self, attr)
1684 value = getattr(self, attr)
1685 if value is not None:
1685 if value is not None:
1686 d[subopt] = value
1686 d[subopt] = value
1687 return d
1687 return d
1688
1688
1689 # we instantiate one globally shared progress bar to avoid
1689 # we instantiate one globally shared progress bar to avoid
1690 # competing progress bars when multiple UI objects get created
1690 # competing progress bars when multiple UI objects get created
1691 _progresssingleton = None
1691 _progresssingleton = None
1692
1692
1693 def getprogbar(ui):
1693 def getprogbar(ui):
1694 global _progresssingleton
1694 global _progresssingleton
1695 if _progresssingleton is None:
1695 if _progresssingleton is None:
1696 # passing 'ui' object to the singleton is fishy,
1696 # passing 'ui' object to the singleton is fishy,
1697 # this is how the extension used to work but feel free to rework it.
1697 # this is how the extension used to work but feel free to rework it.
1698 _progresssingleton = progress.progbar(ui)
1698 _progresssingleton = progress.progbar(ui)
1699 return _progresssingleton
1699 return _progresssingleton
General Comments 0
You need to be logged in to leave comments. Login now