##// END OF EJS Templates
progress: avoid ui.configbool() lookup when progress bar is active...
Gregory Szorc -
r41093:6603de28 default
parent child Browse files
Show More
@@ -1,303 +1,305 b''
1 # progress.py progress bars related code
1 # progress.py progress bars related code
2 #
2 #
3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.com>
3 # Copyright (C) 2010 Augie Fackler <durin42@gmail.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 errno
10 import errno
11 import threading
11 import threading
12 import time
12 import time
13
13
14 from .i18n import _
14 from .i18n import _
15 from . import encoding
15 from . import encoding
16
16
17 def spacejoin(*args):
17 def spacejoin(*args):
18 return ' '.join(s for s in args if s)
18 return ' '.join(s for s in args if s)
19
19
20 def shouldprint(ui):
20 def shouldprint(ui):
21 return not (ui.quiet or ui.plain('progress')) and (
21 return not (ui.quiet or ui.plain('progress')) and (
22 ui._isatty(ui.ferr) or ui.configbool('progress', 'assume-tty'))
22 ui._isatty(ui.ferr) or ui.configbool('progress', 'assume-tty'))
23
23
24 def fmtremaining(seconds):
24 def fmtremaining(seconds):
25 """format a number of remaining seconds in human readable way
25 """format a number of remaining seconds in human readable way
26
26
27 This will properly display seconds, minutes, hours, days if needed"""
27 This will properly display seconds, minutes, hours, days if needed"""
28 if seconds < 60:
28 if seconds < 60:
29 # i18n: format XX seconds as "XXs"
29 # i18n: format XX seconds as "XXs"
30 return _("%02ds") % (seconds)
30 return _("%02ds") % (seconds)
31 minutes = seconds // 60
31 minutes = seconds // 60
32 if minutes < 60:
32 if minutes < 60:
33 seconds -= minutes * 60
33 seconds -= minutes * 60
34 # i18n: format X minutes and YY seconds as "XmYYs"
34 # i18n: format X minutes and YY seconds as "XmYYs"
35 return _("%dm%02ds") % (minutes, seconds)
35 return _("%dm%02ds") % (minutes, seconds)
36 # we're going to ignore seconds in this case
36 # we're going to ignore seconds in this case
37 minutes += 1
37 minutes += 1
38 hours = minutes // 60
38 hours = minutes // 60
39 minutes -= hours * 60
39 minutes -= hours * 60
40 if hours < 30:
40 if hours < 30:
41 # i18n: format X hours and YY minutes as "XhYYm"
41 # i18n: format X hours and YY minutes as "XhYYm"
42 return _("%dh%02dm") % (hours, minutes)
42 return _("%dh%02dm") % (hours, minutes)
43 # we're going to ignore minutes in this case
43 # we're going to ignore minutes in this case
44 hours += 1
44 hours += 1
45 days = hours // 24
45 days = hours // 24
46 hours -= days * 24
46 hours -= days * 24
47 if days < 15:
47 if days < 15:
48 # i18n: format X days and YY hours as "XdYYh"
48 # i18n: format X days and YY hours as "XdYYh"
49 return _("%dd%02dh") % (days, hours)
49 return _("%dd%02dh") % (days, hours)
50 # we're going to ignore hours in this case
50 # we're going to ignore hours in this case
51 days += 1
51 days += 1
52 weeks = days // 7
52 weeks = days // 7
53 days -= weeks * 7
53 days -= weeks * 7
54 if weeks < 55:
54 if weeks < 55:
55 # i18n: format X weeks and YY days as "XwYYd"
55 # i18n: format X weeks and YY days as "XwYYd"
56 return _("%dw%02dd") % (weeks, days)
56 return _("%dw%02dd") % (weeks, days)
57 # we're going to ignore days and treat a year as 52 weeks
57 # we're going to ignore days and treat a year as 52 weeks
58 weeks += 1
58 weeks += 1
59 years = weeks // 52
59 years = weeks // 52
60 weeks -= years * 52
60 weeks -= years * 52
61 # i18n: format X years and YY weeks as "XyYYw"
61 # i18n: format X years and YY weeks as "XyYYw"
62 return _("%dy%02dw") % (years, weeks)
62 return _("%dy%02dw") % (years, weeks)
63
63
64 # file_write() and file_flush() of Python 2 do not restart on EINTR if
64 # file_write() and file_flush() of Python 2 do not restart on EINTR if
65 # the file is attached to a "slow" device (e.g. a terminal) and raise
65 # the file is attached to a "slow" device (e.g. a terminal) and raise
66 # IOError. We cannot know how many bytes would be written by file_write(),
66 # IOError. We cannot know how many bytes would be written by file_write(),
67 # but a progress text is known to be short enough to be written by a
67 # but a progress text is known to be short enough to be written by a
68 # single write() syscall, so we can just retry file_write() with the whole
68 # single write() syscall, so we can just retry file_write() with the whole
69 # text. (issue5532)
69 # text. (issue5532)
70 #
70 #
71 # This should be a short-term workaround. We'll need to fix every occurrence
71 # This should be a short-term workaround. We'll need to fix every occurrence
72 # of write() to a terminal or pipe.
72 # of write() to a terminal or pipe.
73 def _eintrretry(func, *args):
73 def _eintrretry(func, *args):
74 while True:
74 while True:
75 try:
75 try:
76 return func(*args)
76 return func(*args)
77 except IOError as err:
77 except IOError as err:
78 if err.errno == errno.EINTR:
78 if err.errno == errno.EINTR:
79 continue
79 continue
80 raise
80 raise
81
81
82 class progbar(object):
82 class progbar(object):
83 def __init__(self, ui):
83 def __init__(self, ui):
84 self.ui = ui
84 self.ui = ui
85 self._refreshlock = threading.Lock()
85 self._refreshlock = threading.Lock()
86 self.resetstate()
86 self.resetstate()
87
87
88 def resetstate(self):
88 def resetstate(self):
89 self.topics = []
89 self.topics = []
90 self.topicstates = {}
90 self.topicstates = {}
91 self.starttimes = {}
91 self.starttimes = {}
92 self.startvals = {}
92 self.startvals = {}
93 self.printed = False
93 self.printed = False
94 self.lastprint = time.time() + float(self.ui.config(
94 self.lastprint = time.time() + float(self.ui.config(
95 'progress', 'delay'))
95 'progress', 'delay'))
96 self.curtopic = None
96 self.curtopic = None
97 self.lasttopic = None
97 self.lasttopic = None
98 self.indetcount = 0
98 self.indetcount = 0
99 self.refresh = float(self.ui.config(
99 self.refresh = float(self.ui.config(
100 'progress', 'refresh'))
100 'progress', 'refresh'))
101 self.changedelay = max(3 * self.refresh,
101 self.changedelay = max(3 * self.refresh,
102 float(self.ui.config(
102 float(self.ui.config(
103 'progress', 'changedelay')))
103 'progress', 'changedelay')))
104 self.order = self.ui.configlist('progress', 'format')
104 self.order = self.ui.configlist('progress', 'format')
105 self.estimateinterval = self.ui.configwith(
105 self.estimateinterval = self.ui.configwith(
106 float, 'progress', 'estimateinterval')
106 float, 'progress', 'estimateinterval')
107 # developer config: progress.debug
108 self.debug = self.ui.configbool('progress', 'debug')
107
109
108 def show(self, now, topic, pos, item, unit, total):
110 def show(self, now, topic, pos, item, unit, total):
109 if not shouldprint(self.ui):
111 if not shouldprint(self.ui):
110 return
112 return
111 termwidth = self.width()
113 termwidth = self.width()
112 self.printed = True
114 self.printed = True
113 head = ''
115 head = ''
114 needprogress = False
116 needprogress = False
115 tail = ''
117 tail = ''
116 for indicator in self.order:
118 for indicator in self.order:
117 add = ''
119 add = ''
118 if indicator == 'topic':
120 if indicator == 'topic':
119 add = topic
121 add = topic
120 elif indicator == 'number':
122 elif indicator == 'number':
121 if total:
123 if total:
122 add = b'%*d/%d' % (len(str(total)), pos, total)
124 add = b'%*d/%d' % (len(str(total)), pos, total)
123 else:
125 else:
124 add = b'%d' % pos
126 add = b'%d' % pos
125 elif indicator.startswith('item') and item:
127 elif indicator.startswith('item') and item:
126 slice = 'end'
128 slice = 'end'
127 if '-' in indicator:
129 if '-' in indicator:
128 wid = int(indicator.split('-')[1])
130 wid = int(indicator.split('-')[1])
129 elif '+' in indicator:
131 elif '+' in indicator:
130 slice = 'beginning'
132 slice = 'beginning'
131 wid = int(indicator.split('+')[1])
133 wid = int(indicator.split('+')[1])
132 else:
134 else:
133 wid = 20
135 wid = 20
134 if slice == 'end':
136 if slice == 'end':
135 add = encoding.trim(item, wid, leftside=True)
137 add = encoding.trim(item, wid, leftside=True)
136 else:
138 else:
137 add = encoding.trim(item, wid)
139 add = encoding.trim(item, wid)
138 add += (wid - encoding.colwidth(add)) * ' '
140 add += (wid - encoding.colwidth(add)) * ' '
139 elif indicator == 'bar':
141 elif indicator == 'bar':
140 add = ''
142 add = ''
141 needprogress = True
143 needprogress = True
142 elif indicator == 'unit' and unit:
144 elif indicator == 'unit' and unit:
143 add = unit
145 add = unit
144 elif indicator == 'estimate':
146 elif indicator == 'estimate':
145 add = self.estimate(topic, pos, total, now)
147 add = self.estimate(topic, pos, total, now)
146 elif indicator == 'speed':
148 elif indicator == 'speed':
147 add = self.speed(topic, pos, unit, now)
149 add = self.speed(topic, pos, unit, now)
148 if not needprogress:
150 if not needprogress:
149 head = spacejoin(head, add)
151 head = spacejoin(head, add)
150 else:
152 else:
151 tail = spacejoin(tail, add)
153 tail = spacejoin(tail, add)
152 if needprogress:
154 if needprogress:
153 used = 0
155 used = 0
154 if head:
156 if head:
155 used += encoding.colwidth(head) + 1
157 used += encoding.colwidth(head) + 1
156 if tail:
158 if tail:
157 used += encoding.colwidth(tail) + 1
159 used += encoding.colwidth(tail) + 1
158 progwidth = termwidth - used - 3
160 progwidth = termwidth - used - 3
159 if total and pos <= total:
161 if total and pos <= total:
160 amt = pos * progwidth // total
162 amt = pos * progwidth // total
161 bar = '=' * (amt - 1)
163 bar = '=' * (amt - 1)
162 if amt > 0:
164 if amt > 0:
163 bar += '>'
165 bar += '>'
164 bar += ' ' * (progwidth - amt)
166 bar += ' ' * (progwidth - amt)
165 else:
167 else:
166 progwidth -= 3
168 progwidth -= 3
167 self.indetcount += 1
169 self.indetcount += 1
168 # mod the count by twice the width so we can make the
170 # mod the count by twice the width so we can make the
169 # cursor bounce between the right and left sides
171 # cursor bounce between the right and left sides
170 amt = self.indetcount % (2 * progwidth)
172 amt = self.indetcount % (2 * progwidth)
171 amt -= progwidth
173 amt -= progwidth
172 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
174 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
173 ' ' * int(abs(amt)))
175 ' ' * int(abs(amt)))
174 prog = ''.join(('[', bar, ']'))
176 prog = ''.join(('[', bar, ']'))
175 out = spacejoin(head, prog, tail)
177 out = spacejoin(head, prog, tail)
176 else:
178 else:
177 out = spacejoin(head, tail)
179 out = spacejoin(head, tail)
178 self._writeerr('\r' + encoding.trim(out, termwidth))
180 self._writeerr('\r' + encoding.trim(out, termwidth))
179 self.lasttopic = topic
181 self.lasttopic = topic
180 self._flusherr()
182 self._flusherr()
181
183
182 def clear(self):
184 def clear(self):
183 if not self.printed or not self.lastprint or not shouldprint(self.ui):
185 if not self.printed or not self.lastprint or not shouldprint(self.ui):
184 return
186 return
185 self._writeerr('\r%s\r' % (' ' * self.width()))
187 self._writeerr('\r%s\r' % (' ' * self.width()))
186 if self.printed:
188 if self.printed:
187 # force immediate re-paint of progress bar
189 # force immediate re-paint of progress bar
188 self.lastprint = 0
190 self.lastprint = 0
189
191
190 def complete(self):
192 def complete(self):
191 if not shouldprint(self.ui):
193 if not shouldprint(self.ui):
192 return
194 return
193 if self.ui.configbool('progress', 'clear-complete'):
195 if self.ui.configbool('progress', 'clear-complete'):
194 self.clear()
196 self.clear()
195 else:
197 else:
196 self._writeerr('\n')
198 self._writeerr('\n')
197 self._flusherr()
199 self._flusherr()
198
200
199 def _flusherr(self):
201 def _flusherr(self):
200 _eintrretry(self.ui.ferr.flush)
202 _eintrretry(self.ui.ferr.flush)
201
203
202 def _writeerr(self, msg):
204 def _writeerr(self, msg):
203 _eintrretry(self.ui.ferr.write, msg)
205 _eintrretry(self.ui.ferr.write, msg)
204
206
205 def width(self):
207 def width(self):
206 tw = self.ui.termwidth()
208 tw = self.ui.termwidth()
207 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
209 return min(int(self.ui.config('progress', 'width', default=tw)), tw)
208
210
209 def estimate(self, topic, pos, total, now):
211 def estimate(self, topic, pos, total, now):
210 if total is None:
212 if total is None:
211 return ''
213 return ''
212 initialpos = self.startvals[topic]
214 initialpos = self.startvals[topic]
213 target = total - initialpos
215 target = total - initialpos
214 delta = pos - initialpos
216 delta = pos - initialpos
215 if delta > 0:
217 if delta > 0:
216 elapsed = now - self.starttimes[topic]
218 elapsed = now - self.starttimes[topic]
217 seconds = (elapsed * (target - delta)) // delta + 1
219 seconds = (elapsed * (target - delta)) // delta + 1
218 return fmtremaining(seconds)
220 return fmtremaining(seconds)
219 return ''
221 return ''
220
222
221 def speed(self, topic, pos, unit, now):
223 def speed(self, topic, pos, unit, now):
222 initialpos = self.startvals[topic]
224 initialpos = self.startvals[topic]
223 delta = pos - initialpos
225 delta = pos - initialpos
224 elapsed = now - self.starttimes[topic]
226 elapsed = now - self.starttimes[topic]
225 if elapsed > 0:
227 if elapsed > 0:
226 return _('%d %s/sec') % (delta / elapsed, unit)
228 return _('%d %s/sec') % (delta / elapsed, unit)
227 return ''
229 return ''
228
230
229 def _oktoprint(self, now):
231 def _oktoprint(self, now):
230 '''Check if conditions are met to print - e.g. changedelay elapsed'''
232 '''Check if conditions are met to print - e.g. changedelay elapsed'''
231 if (self.lasttopic is None # first time we printed
233 if (self.lasttopic is None # first time we printed
232 # not a topic change
234 # not a topic change
233 or self.curtopic == self.lasttopic
235 or self.curtopic == self.lasttopic
234 # it's been long enough we should print anyway
236 # it's been long enough we should print anyway
235 or now - self.lastprint >= self.changedelay):
237 or now - self.lastprint >= self.changedelay):
236 return True
238 return True
237 else:
239 else:
238 return False
240 return False
239
241
240 def _calibrateestimate(self, topic, now, pos):
242 def _calibrateestimate(self, topic, now, pos):
241 '''Adjust starttimes and startvals for topic so ETA works better
243 '''Adjust starttimes and startvals for topic so ETA works better
242
244
243 If progress is non-linear (ex. get much slower in the last minute),
245 If progress is non-linear (ex. get much slower in the last minute),
244 it's more friendly to only use a recent time span for ETA and speed
246 it's more friendly to only use a recent time span for ETA and speed
245 calculation.
247 calculation.
246
248
247 [======================================> ]
249 [======================================> ]
248 ^^^^^^^
250 ^^^^^^^
249 estimateinterval, only use this for estimation
251 estimateinterval, only use this for estimation
250 '''
252 '''
251 interval = self.estimateinterval
253 interval = self.estimateinterval
252 if interval <= 0:
254 if interval <= 0:
253 return
255 return
254 elapsed = now - self.starttimes[topic]
256 elapsed = now - self.starttimes[topic]
255 if elapsed > interval:
257 if elapsed > interval:
256 delta = pos - self.startvals[topic]
258 delta = pos - self.startvals[topic]
257 newdelta = delta * interval / elapsed
259 newdelta = delta * interval / elapsed
258 # If a stall happens temporarily, ETA could change dramatically
260 # If a stall happens temporarily, ETA could change dramatically
259 # frequently. This is to avoid such dramatical change and make ETA
261 # frequently. This is to avoid such dramatical change and make ETA
260 # smoother.
262 # smoother.
261 if newdelta < 0.1:
263 if newdelta < 0.1:
262 return
264 return
263 self.startvals[topic] = pos - newdelta
265 self.startvals[topic] = pos - newdelta
264 self.starttimes[topic] = now - interval
266 self.starttimes[topic] = now - interval
265
267
266 def progress(self, topic, pos, item='', unit='', total=None):
268 def progress(self, topic, pos, item='', unit='', total=None):
267 if pos is None:
269 if pos is None:
268 self.closetopic(topic)
270 self.closetopic(topic)
269 return
271 return
270 now = time.time()
272 now = time.time()
271 with self._refreshlock:
273 with self._refreshlock:
272 if topic not in self.topics:
274 if topic not in self.topics:
273 self.starttimes[topic] = now
275 self.starttimes[topic] = now
274 self.startvals[topic] = pos
276 self.startvals[topic] = pos
275 self.topics.append(topic)
277 self.topics.append(topic)
276 self.topicstates[topic] = pos, item, unit, total
278 self.topicstates[topic] = pos, item, unit, total
277 self.curtopic = topic
279 self.curtopic = topic
278 self._calibrateestimate(topic, now, pos)
280 self._calibrateestimate(topic, now, pos)
279 if now - self.lastprint >= self.refresh and self.topics:
281 if now - self.lastprint >= self.refresh and self.topics:
280 if self._oktoprint(now):
282 if self._oktoprint(now):
281 self.lastprint = now
283 self.lastprint = now
282 self.show(now, topic, *self.topicstates[topic])
284 self.show(now, topic, *self.topicstates[topic])
283
285
284 def closetopic(self, topic):
286 def closetopic(self, topic):
285 with self._refreshlock:
287 with self._refreshlock:
286 self.starttimes.pop(topic, None)
288 self.starttimes.pop(topic, None)
287 self.startvals.pop(topic, None)
289 self.startvals.pop(topic, None)
288 self.topicstates.pop(topic, None)
290 self.topicstates.pop(topic, None)
289 # reset the progress bar if this is the outermost topic
291 # reset the progress bar if this is the outermost topic
290 if self.topics and self.topics[0] == topic and self.printed:
292 if self.topics and self.topics[0] == topic and self.printed:
291 self.complete()
293 self.complete()
292 self.resetstate()
294 self.resetstate()
293 # truncate the list of topics assuming all topics within
295 # truncate the list of topics assuming all topics within
294 # this one are also closed
296 # this one are also closed
295 if topic in self.topics:
297 if topic in self.topics:
296 self.topics = self.topics[:self.topics.index(topic)]
298 self.topics = self.topics[:self.topics.index(topic)]
297 # reset the last topic to the one we just unwound to,
299 # reset the last topic to the one we just unwound to,
298 # so that higher-level topics will be stickier than
300 # so that higher-level topics will be stickier than
299 # lower-level topics
301 # lower-level topics
300 if self.topics:
302 if self.topics:
301 self.lasttopic = self.topics[-1]
303 self.lasttopic = self.topics[-1]
302 else:
304 else:
303 self.lasttopic = None
305 self.lasttopic = None
@@ -1,2048 +1,2056 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 traceback
21 import traceback
22
22
23 from .i18n import _
23 from .i18n import _
24 from .node import hex
24 from .node import hex
25
25
26 from . import (
26 from . import (
27 color,
27 color,
28 config,
28 config,
29 configitems,
29 configitems,
30 encoding,
30 encoding,
31 error,
31 error,
32 formatter,
32 formatter,
33 loggingutil,
33 loggingutil,
34 progress,
34 progress,
35 pycompat,
35 pycompat,
36 rcutil,
36 rcutil,
37 scmutil,
37 scmutil,
38 util,
38 util,
39 )
39 )
40 from .utils import (
40 from .utils import (
41 dateutil,
41 dateutil,
42 procutil,
42 procutil,
43 stringutil,
43 stringutil,
44 )
44 )
45
45
46 urlreq = util.urlreq
46 urlreq = util.urlreq
47
47
48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
48 # for use with str.translate(None, _keepalnum), to keep just alphanumerics
49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
49 _keepalnum = ''.join(c for c in map(pycompat.bytechr, range(256))
50 if not c.isalnum())
50 if not c.isalnum())
51
51
52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
52 # The config knobs that will be altered (if unset) by ui.tweakdefaults.
53 tweakrc = b"""
53 tweakrc = b"""
54 [ui]
54 [ui]
55 # The rollback command is dangerous. As a rule, don't use it.
55 # The rollback command is dangerous. As a rule, don't use it.
56 rollback = False
56 rollback = False
57 # Make `hg status` report copy information
57 # Make `hg status` report copy information
58 statuscopies = yes
58 statuscopies = yes
59 # Prefer curses UIs when available. Revert to plain-text with `text`.
59 # Prefer curses UIs when available. Revert to plain-text with `text`.
60 interface = curses
60 interface = curses
61
61
62 [commands]
62 [commands]
63 # Grep working directory by default.
63 # Grep working directory by default.
64 grep.all-files = True
64 grep.all-files = True
65 # Make `hg status` emit cwd-relative paths by default.
65 # Make `hg status` emit cwd-relative paths by default.
66 status.relative = yes
66 status.relative = yes
67 # Refuse to perform an `hg update` that would cause a file content merge
67 # Refuse to perform an `hg update` that would cause a file content merge
68 update.check = noconflict
68 update.check = noconflict
69 # Show conflicts information in `hg status`
69 # Show conflicts information in `hg status`
70 status.verbose = True
70 status.verbose = True
71
71
72 [diff]
72 [diff]
73 git = 1
73 git = 1
74 showfunc = 1
74 showfunc = 1
75 word-diff = 1
75 word-diff = 1
76 """
76 """
77
77
78 samplehgrcs = {
78 samplehgrcs = {
79 'user':
79 'user':
80 b"""# example user config (see 'hg help config' for more info)
80 b"""# example user config (see 'hg help config' for more info)
81 [ui]
81 [ui]
82 # name and email, e.g.
82 # name and email, e.g.
83 # username = Jane Doe <jdoe@example.com>
83 # username = Jane Doe <jdoe@example.com>
84 username =
84 username =
85
85
86 # We recommend enabling tweakdefaults to get slight improvements to
86 # We recommend enabling tweakdefaults to get slight improvements to
87 # the UI over time. Make sure to set HGPLAIN in the environment when
87 # the UI over time. Make sure to set HGPLAIN in the environment when
88 # writing scripts!
88 # writing scripts!
89 # tweakdefaults = True
89 # tweakdefaults = True
90
90
91 # uncomment to disable color in command output
91 # uncomment to disable color in command output
92 # (see 'hg help color' for details)
92 # (see 'hg help color' for details)
93 # color = never
93 # color = never
94
94
95 # uncomment to disable command output pagination
95 # uncomment to disable command output pagination
96 # (see 'hg help pager' for details)
96 # (see 'hg help pager' for details)
97 # paginate = never
97 # paginate = never
98
98
99 [extensions]
99 [extensions]
100 # uncomment these lines to enable some popular extensions
100 # uncomment these lines to enable some popular extensions
101 # (see 'hg help extensions' for more info)
101 # (see 'hg help extensions' for more info)
102 #
102 #
103 # churn =
103 # churn =
104 """,
104 """,
105
105
106 'cloned':
106 'cloned':
107 b"""# example repository config (see 'hg help config' for more info)
107 b"""# example repository config (see 'hg help config' for more info)
108 [paths]
108 [paths]
109 default = %s
109 default = %s
110
110
111 # path aliases to other clones of this repo in URLs or filesystem paths
111 # path aliases to other clones of this repo in URLs or filesystem paths
112 # (see 'hg help config.paths' for more info)
112 # (see 'hg help config.paths' for more info)
113 #
113 #
114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
114 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
115 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
116 # my-clone = /home/jdoe/jdoes-clone
116 # my-clone = /home/jdoe/jdoes-clone
117
117
118 [ui]
118 [ui]
119 # name and email (local to this repository, optional), e.g.
119 # name and email (local to this repository, optional), e.g.
120 # username = Jane Doe <jdoe@example.com>
120 # username = Jane Doe <jdoe@example.com>
121 """,
121 """,
122
122
123 'local':
123 'local':
124 b"""# example repository config (see 'hg help config' for more info)
124 b"""# example repository config (see 'hg help config' for more info)
125 [paths]
125 [paths]
126 # path aliases to other clones of this repo in URLs or filesystem paths
126 # path aliases to other clones of this repo in URLs or filesystem paths
127 # (see 'hg help config.paths' for more info)
127 # (see 'hg help config.paths' for more info)
128 #
128 #
129 # default = http://example.com/hg/example-repo
129 # default = http://example.com/hg/example-repo
130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
130 # default:pushurl = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
131 # my-fork = ssh://jdoe@example.net/hg/jdoes-fork
132 # my-clone = /home/jdoe/jdoes-clone
132 # my-clone = /home/jdoe/jdoes-clone
133
133
134 [ui]
134 [ui]
135 # name and email (local to this repository, optional), e.g.
135 # name and email (local to this repository, optional), e.g.
136 # username = Jane Doe <jdoe@example.com>
136 # username = Jane Doe <jdoe@example.com>
137 """,
137 """,
138
138
139 'global':
139 'global':
140 b"""# example system-wide hg config (see 'hg help config' for more info)
140 b"""# example system-wide hg config (see 'hg help config' for more info)
141
141
142 [ui]
142 [ui]
143 # uncomment to disable color in command output
143 # uncomment to disable color in command output
144 # (see 'hg help color' for details)
144 # (see 'hg help color' for details)
145 # color = never
145 # color = never
146
146
147 # uncomment to disable command output pagination
147 # uncomment to disable command output pagination
148 # (see 'hg help pager' for details)
148 # (see 'hg help pager' for details)
149 # paginate = never
149 # paginate = never
150
150
151 [extensions]
151 [extensions]
152 # uncomment these lines to enable some popular extensions
152 # uncomment these lines to enable some popular extensions
153 # (see 'hg help extensions' for more info)
153 # (see 'hg help extensions' for more info)
154 #
154 #
155 # blackbox =
155 # blackbox =
156 # churn =
156 # churn =
157 """,
157 """,
158 }
158 }
159
159
160 def _maybestrurl(maybebytes):
160 def _maybestrurl(maybebytes):
161 return pycompat.rapply(pycompat.strurl, maybebytes)
161 return pycompat.rapply(pycompat.strurl, maybebytes)
162
162
163 def _maybebytesurl(maybestr):
163 def _maybebytesurl(maybestr):
164 return pycompat.rapply(pycompat.bytesurl, maybestr)
164 return pycompat.rapply(pycompat.bytesurl, maybestr)
165
165
166 class httppasswordmgrdbproxy(object):
166 class httppasswordmgrdbproxy(object):
167 """Delays loading urllib2 until it's needed."""
167 """Delays loading urllib2 until it's needed."""
168 def __init__(self):
168 def __init__(self):
169 self._mgr = None
169 self._mgr = None
170
170
171 def _get_mgr(self):
171 def _get_mgr(self):
172 if self._mgr is None:
172 if self._mgr is None:
173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
173 self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
174 return self._mgr
174 return self._mgr
175
175
176 def add_password(self, realm, uris, user, passwd):
176 def add_password(self, realm, uris, user, passwd):
177 return self._get_mgr().add_password(
177 return self._get_mgr().add_password(
178 _maybestrurl(realm), _maybestrurl(uris),
178 _maybestrurl(realm), _maybestrurl(uris),
179 _maybestrurl(user), _maybestrurl(passwd))
179 _maybestrurl(user), _maybestrurl(passwd))
180
180
181 def find_user_password(self, realm, uri):
181 def find_user_password(self, realm, uri):
182 mgr = self._get_mgr()
182 mgr = self._get_mgr()
183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
183 return _maybebytesurl(mgr.find_user_password(_maybestrurl(realm),
184 _maybestrurl(uri)))
184 _maybestrurl(uri)))
185
185
186 def _catchterm(*args):
186 def _catchterm(*args):
187 raise error.SignalInterrupt
187 raise error.SignalInterrupt
188
188
189 # unique object used to detect no default value has been provided when
189 # unique object used to detect no default value has been provided when
190 # retrieving configuration value.
190 # retrieving configuration value.
191 _unset = object()
191 _unset = object()
192
192
193 # _reqexithandlers: callbacks run at the end of a request
193 # _reqexithandlers: callbacks run at the end of a request
194 _reqexithandlers = []
194 _reqexithandlers = []
195
195
196 class ui(object):
196 class ui(object):
197 def __init__(self, src=None):
197 def __init__(self, src=None):
198 """Create a fresh new ui object if no src given
198 """Create a fresh new ui object if no src given
199
199
200 Use uimod.ui.load() to create a ui which knows global and user configs.
200 Use uimod.ui.load() to create a ui which knows global and user configs.
201 In most cases, you should use ui.copy() to create a copy of an existing
201 In most cases, you should use ui.copy() to create a copy of an existing
202 ui object.
202 ui object.
203 """
203 """
204 # _buffers: used for temporary capture of output
204 # _buffers: used for temporary capture of output
205 self._buffers = []
205 self._buffers = []
206 # 3-tuple describing how each buffer in the stack behaves.
206 # 3-tuple describing how each buffer in the stack behaves.
207 # Values are (capture stderr, capture subprocesses, apply labels).
207 # Values are (capture stderr, capture subprocesses, apply labels).
208 self._bufferstates = []
208 self._bufferstates = []
209 # When a buffer is active, defines whether we are expanding labels.
209 # When a buffer is active, defines whether we are expanding labels.
210 # This exists to prevent an extra list lookup.
210 # This exists to prevent an extra list lookup.
211 self._bufferapplylabels = None
211 self._bufferapplylabels = None
212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
212 self.quiet = self.verbose = self.debugflag = self.tracebackflag = False
213 self._reportuntrusted = True
213 self._reportuntrusted = True
214 self._knownconfig = configitems.coreitems
214 self._knownconfig = configitems.coreitems
215 self._ocfg = config.config() # overlay
215 self._ocfg = config.config() # overlay
216 self._tcfg = config.config() # trusted
216 self._tcfg = config.config() # trusted
217 self._ucfg = config.config() # untrusted
217 self._ucfg = config.config() # untrusted
218 self._trustusers = set()
218 self._trustusers = set()
219 self._trustgroups = set()
219 self._trustgroups = set()
220 self.callhooks = True
220 self.callhooks = True
221 # Insecure server connections requested.
221 # Insecure server connections requested.
222 self.insecureconnections = False
222 self.insecureconnections = False
223 # Blocked time
223 # Blocked time
224 self.logblockedtimes = False
224 self.logblockedtimes = False
225 # color mode: see mercurial/color.py for possible value
225 # color mode: see mercurial/color.py for possible value
226 self._colormode = None
226 self._colormode = None
227 self._terminfoparams = {}
227 self._terminfoparams = {}
228 self._styles = {}
228 self._styles = {}
229 self._uninterruptible = False
229 self._uninterruptible = False
230
230
231 if src:
231 if src:
232 self._fout = src._fout
232 self._fout = src._fout
233 self._ferr = src._ferr
233 self._ferr = src._ferr
234 self._fin = src._fin
234 self._fin = src._fin
235 self._fmsg = src._fmsg
235 self._fmsg = src._fmsg
236 self._fmsgout = src._fmsgout
236 self._fmsgout = src._fmsgout
237 self._fmsgerr = src._fmsgerr
237 self._fmsgerr = src._fmsgerr
238 self._finoutredirected = src._finoutredirected
238 self._finoutredirected = src._finoutredirected
239 self._loggers = src._loggers.copy()
239 self._loggers = src._loggers.copy()
240 self.pageractive = src.pageractive
240 self.pageractive = src.pageractive
241 self._disablepager = src._disablepager
241 self._disablepager = src._disablepager
242 self._tweaked = src._tweaked
242 self._tweaked = src._tweaked
243
243
244 self._tcfg = src._tcfg.copy()
244 self._tcfg = src._tcfg.copy()
245 self._ucfg = src._ucfg.copy()
245 self._ucfg = src._ucfg.copy()
246 self._ocfg = src._ocfg.copy()
246 self._ocfg = src._ocfg.copy()
247 self._trustusers = src._trustusers.copy()
247 self._trustusers = src._trustusers.copy()
248 self._trustgroups = src._trustgroups.copy()
248 self._trustgroups = src._trustgroups.copy()
249 self.environ = src.environ
249 self.environ = src.environ
250 self.callhooks = src.callhooks
250 self.callhooks = src.callhooks
251 self.insecureconnections = src.insecureconnections
251 self.insecureconnections = src.insecureconnections
252 self._colormode = src._colormode
252 self._colormode = src._colormode
253 self._terminfoparams = src._terminfoparams.copy()
253 self._terminfoparams = src._terminfoparams.copy()
254 self._styles = src._styles.copy()
254 self._styles = src._styles.copy()
255
255
256 self.fixconfig()
256 self.fixconfig()
257
257
258 self.httppasswordmgrdb = src.httppasswordmgrdb
258 self.httppasswordmgrdb = src.httppasswordmgrdb
259 self._blockedtimes = src._blockedtimes
259 self._blockedtimes = src._blockedtimes
260 else:
260 else:
261 self._fout = procutil.stdout
261 self._fout = procutil.stdout
262 self._ferr = procutil.stderr
262 self._ferr = procutil.stderr
263 self._fin = procutil.stdin
263 self._fin = procutil.stdin
264 self._fmsg = None
264 self._fmsg = None
265 self._fmsgout = self.fout # configurable
265 self._fmsgout = self.fout # configurable
266 self._fmsgerr = self.ferr # configurable
266 self._fmsgerr = self.ferr # configurable
267 self._finoutredirected = False
267 self._finoutredirected = False
268 self._loggers = {}
268 self._loggers = {}
269 self.pageractive = False
269 self.pageractive = False
270 self._disablepager = False
270 self._disablepager = False
271 self._tweaked = False
271 self._tweaked = False
272
272
273 # shared read-only environment
273 # shared read-only environment
274 self.environ = encoding.environ
274 self.environ = encoding.environ
275
275
276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
276 self.httppasswordmgrdb = httppasswordmgrdbproxy()
277 self._blockedtimes = collections.defaultdict(int)
277 self._blockedtimes = collections.defaultdict(int)
278
278
279 allowed = self.configlist('experimental', 'exportableenviron')
279 allowed = self.configlist('experimental', 'exportableenviron')
280 if '*' in allowed:
280 if '*' in allowed:
281 self._exportableenviron = self.environ
281 self._exportableenviron = self.environ
282 else:
282 else:
283 self._exportableenviron = {}
283 self._exportableenviron = {}
284 for k in allowed:
284 for k in allowed:
285 if k in self.environ:
285 if k in self.environ:
286 self._exportableenviron[k] = self.environ[k]
286 self._exportableenviron[k] = self.environ[k]
287
287
288 @classmethod
288 @classmethod
289 def load(cls):
289 def load(cls):
290 """Create a ui and load global and user configs"""
290 """Create a ui and load global and user configs"""
291 u = cls()
291 u = cls()
292 # we always trust global config files and environment variables
292 # we always trust global config files and environment variables
293 for t, f in rcutil.rccomponents():
293 for t, f in rcutil.rccomponents():
294 if t == 'path':
294 if t == 'path':
295 u.readconfig(f, trust=True)
295 u.readconfig(f, trust=True)
296 elif t == 'items':
296 elif t == 'items':
297 sections = set()
297 sections = set()
298 for section, name, value, source in f:
298 for section, name, value, source in f:
299 # do not set u._ocfg
299 # do not set u._ocfg
300 # XXX clean this up once immutable config object is a thing
300 # XXX clean this up once immutable config object is a thing
301 u._tcfg.set(section, name, value, source)
301 u._tcfg.set(section, name, value, source)
302 u._ucfg.set(section, name, value, source)
302 u._ucfg.set(section, name, value, source)
303 sections.add(section)
303 sections.add(section)
304 for section in sections:
304 for section in sections:
305 u.fixconfig(section=section)
305 u.fixconfig(section=section)
306 else:
306 else:
307 raise error.ProgrammingError('unknown rctype: %s' % t)
307 raise error.ProgrammingError('unknown rctype: %s' % t)
308 u._maybetweakdefaults()
308 u._maybetweakdefaults()
309 return u
309 return u
310
310
311 def _maybetweakdefaults(self):
311 def _maybetweakdefaults(self):
312 if not self.configbool('ui', 'tweakdefaults'):
312 if not self.configbool('ui', 'tweakdefaults'):
313 return
313 return
314 if self._tweaked or self.plain('tweakdefaults'):
314 if self._tweaked or self.plain('tweakdefaults'):
315 return
315 return
316
316
317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
317 # Note: it is SUPER IMPORTANT that you set self._tweaked to
318 # True *before* any calls to setconfig(), otherwise you'll get
318 # True *before* any calls to setconfig(), otherwise you'll get
319 # infinite recursion between setconfig and this method.
319 # infinite recursion between setconfig and this method.
320 #
320 #
321 # TODO: We should extract an inner method in setconfig() to
321 # TODO: We should extract an inner method in setconfig() to
322 # avoid this weirdness.
322 # avoid this weirdness.
323 self._tweaked = True
323 self._tweaked = True
324 tmpcfg = config.config()
324 tmpcfg = config.config()
325 tmpcfg.parse('<tweakdefaults>', tweakrc)
325 tmpcfg.parse('<tweakdefaults>', tweakrc)
326 for section in tmpcfg:
326 for section in tmpcfg:
327 for name, value in tmpcfg.items(section):
327 for name, value in tmpcfg.items(section):
328 if not self.hasconfig(section, name):
328 if not self.hasconfig(section, name):
329 self.setconfig(section, name, value, "<tweakdefaults>")
329 self.setconfig(section, name, value, "<tweakdefaults>")
330
330
331 def copy(self):
331 def copy(self):
332 return self.__class__(self)
332 return self.__class__(self)
333
333
334 def resetstate(self):
334 def resetstate(self):
335 """Clear internal state that shouldn't persist across commands"""
335 """Clear internal state that shouldn't persist across commands"""
336 if self._progbar:
336 if self._progbar:
337 self._progbar.resetstate() # reset last-print time of progress bar
337 self._progbar.resetstate() # reset last-print time of progress bar
338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
338 self.httppasswordmgrdb = httppasswordmgrdbproxy()
339
339
340 @contextlib.contextmanager
340 @contextlib.contextmanager
341 def timeblockedsection(self, key):
341 def timeblockedsection(self, key):
342 # this is open-coded below - search for timeblockedsection to find them
342 # this is open-coded below - search for timeblockedsection to find them
343 starttime = util.timer()
343 starttime = util.timer()
344 try:
344 try:
345 yield
345 yield
346 finally:
346 finally:
347 self._blockedtimes[key + '_blocked'] += \
347 self._blockedtimes[key + '_blocked'] += \
348 (util.timer() - starttime) * 1000
348 (util.timer() - starttime) * 1000
349
349
350 @contextlib.contextmanager
350 @contextlib.contextmanager
351 def uninterruptable(self):
351 def uninterruptable(self):
352 """Mark an operation as unsafe.
352 """Mark an operation as unsafe.
353
353
354 Most operations on a repository are safe to interrupt, but a
354 Most operations on a repository are safe to interrupt, but a
355 few are risky (for example repair.strip). This context manager
355 few are risky (for example repair.strip). This context manager
356 lets you advise Mercurial that something risky is happening so
356 lets you advise Mercurial that something risky is happening so
357 that control-C etc can be blocked if desired.
357 that control-C etc can be blocked if desired.
358 """
358 """
359 enabled = self.configbool('experimental', 'nointerrupt')
359 enabled = self.configbool('experimental', 'nointerrupt')
360 if (enabled and
360 if (enabled and
361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
361 self.configbool('experimental', 'nointerrupt-interactiveonly')):
362 enabled = self.interactive()
362 enabled = self.interactive()
363 if self._uninterruptible or not enabled:
363 if self._uninterruptible or not enabled:
364 # if nointerrupt support is turned off, the process isn't
364 # if nointerrupt support is turned off, the process isn't
365 # interactive, or we're already in an uninterruptable
365 # interactive, or we're already in an uninterruptable
366 # block, do nothing.
366 # block, do nothing.
367 yield
367 yield
368 return
368 return
369 def warn():
369 def warn():
370 self.warn(_("shutting down cleanly\n"))
370 self.warn(_("shutting down cleanly\n"))
371 self.warn(
371 self.warn(
372 _("press ^C again to terminate immediately (dangerous)\n"))
372 _("press ^C again to terminate immediately (dangerous)\n"))
373 return True
373 return True
374 with procutil.uninterruptable(warn):
374 with procutil.uninterruptable(warn):
375 try:
375 try:
376 self._uninterruptible = True
376 self._uninterruptible = True
377 yield
377 yield
378 finally:
378 finally:
379 self._uninterruptible = False
379 self._uninterruptible = False
380
380
381 def formatter(self, topic, opts):
381 def formatter(self, topic, opts):
382 return formatter.formatter(self, self, topic, opts)
382 return formatter.formatter(self, self, topic, opts)
383
383
384 def _trusted(self, fp, f):
384 def _trusted(self, fp, f):
385 st = util.fstat(fp)
385 st = util.fstat(fp)
386 if util.isowner(st):
386 if util.isowner(st):
387 return True
387 return True
388
388
389 tusers, tgroups = self._trustusers, self._trustgroups
389 tusers, tgroups = self._trustusers, self._trustgroups
390 if '*' in tusers or '*' in tgroups:
390 if '*' in tusers or '*' in tgroups:
391 return True
391 return True
392
392
393 user = util.username(st.st_uid)
393 user = util.username(st.st_uid)
394 group = util.groupname(st.st_gid)
394 group = util.groupname(st.st_gid)
395 if user in tusers or group in tgroups or user == util.username():
395 if user in tusers or group in tgroups or user == util.username():
396 return True
396 return True
397
397
398 if self._reportuntrusted:
398 if self._reportuntrusted:
399 self.warn(_('not trusting file %s from untrusted '
399 self.warn(_('not trusting file %s from untrusted '
400 'user %s, group %s\n') % (f, user, group))
400 'user %s, group %s\n') % (f, user, group))
401 return False
401 return False
402
402
403 def readconfig(self, filename, root=None, trust=False,
403 def readconfig(self, filename, root=None, trust=False,
404 sections=None, remap=None):
404 sections=None, remap=None):
405 try:
405 try:
406 fp = open(filename, r'rb')
406 fp = open(filename, r'rb')
407 except IOError:
407 except IOError:
408 if not sections: # ignore unless we were looking for something
408 if not sections: # ignore unless we were looking for something
409 return
409 return
410 raise
410 raise
411
411
412 cfg = config.config()
412 cfg = config.config()
413 trusted = sections or trust or self._trusted(fp, filename)
413 trusted = sections or trust or self._trusted(fp, filename)
414
414
415 try:
415 try:
416 cfg.read(filename, fp, sections=sections, remap=remap)
416 cfg.read(filename, fp, sections=sections, remap=remap)
417 fp.close()
417 fp.close()
418 except error.ConfigError as inst:
418 except error.ConfigError as inst:
419 if trusted:
419 if trusted:
420 raise
420 raise
421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
421 self.warn(_("ignored: %s\n") % stringutil.forcebytestr(inst))
422
422
423 if self.plain():
423 if self.plain():
424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
424 for k in ('debug', 'fallbackencoding', 'quiet', 'slash',
425 'logtemplate', 'message-output', 'statuscopies', 'style',
425 'logtemplate', 'message-output', 'statuscopies', 'style',
426 'traceback', 'verbose'):
426 'traceback', 'verbose'):
427 if k in cfg['ui']:
427 if k in cfg['ui']:
428 del cfg['ui'][k]
428 del cfg['ui'][k]
429 for k, v in cfg.items('defaults'):
429 for k, v in cfg.items('defaults'):
430 del cfg['defaults'][k]
430 del cfg['defaults'][k]
431 for k, v in cfg.items('commands'):
431 for k, v in cfg.items('commands'):
432 del cfg['commands'][k]
432 del cfg['commands'][k]
433 # Don't remove aliases from the configuration if in the exceptionlist
433 # Don't remove aliases from the configuration if in the exceptionlist
434 if self.plain('alias'):
434 if self.plain('alias'):
435 for k, v in cfg.items('alias'):
435 for k, v in cfg.items('alias'):
436 del cfg['alias'][k]
436 del cfg['alias'][k]
437 if self.plain('revsetalias'):
437 if self.plain('revsetalias'):
438 for k, v in cfg.items('revsetalias'):
438 for k, v in cfg.items('revsetalias'):
439 del cfg['revsetalias'][k]
439 del cfg['revsetalias'][k]
440 if self.plain('templatealias'):
440 if self.plain('templatealias'):
441 for k, v in cfg.items('templatealias'):
441 for k, v in cfg.items('templatealias'):
442 del cfg['templatealias'][k]
442 del cfg['templatealias'][k]
443
443
444 if trusted:
444 if trusted:
445 self._tcfg.update(cfg)
445 self._tcfg.update(cfg)
446 self._tcfg.update(self._ocfg)
446 self._tcfg.update(self._ocfg)
447 self._ucfg.update(cfg)
447 self._ucfg.update(cfg)
448 self._ucfg.update(self._ocfg)
448 self._ucfg.update(self._ocfg)
449
449
450 if root is None:
450 if root is None:
451 root = os.path.expanduser('~')
451 root = os.path.expanduser('~')
452 self.fixconfig(root=root)
452 self.fixconfig(root=root)
453
453
454 def fixconfig(self, root=None, section=None):
454 def fixconfig(self, root=None, section=None):
455 if section in (None, 'paths'):
455 if section in (None, 'paths'):
456 # expand vars and ~
456 # expand vars and ~
457 # translate paths relative to root (or home) into absolute paths
457 # translate paths relative to root (or home) into absolute paths
458 root = root or encoding.getcwd()
458 root = root or encoding.getcwd()
459 for c in self._tcfg, self._ucfg, self._ocfg:
459 for c in self._tcfg, self._ucfg, self._ocfg:
460 for n, p in c.items('paths'):
460 for n, p in c.items('paths'):
461 # Ignore sub-options.
461 # Ignore sub-options.
462 if ':' in n:
462 if ':' in n:
463 continue
463 continue
464 if not p:
464 if not p:
465 continue
465 continue
466 if '%%' in p:
466 if '%%' in p:
467 s = self.configsource('paths', n) or 'none'
467 s = self.configsource('paths', n) or 'none'
468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
468 self.warn(_("(deprecated '%%' in path %s=%s from %s)\n")
469 % (n, p, s))
469 % (n, p, s))
470 p = p.replace('%%', '%')
470 p = p.replace('%%', '%')
471 p = util.expandpath(p)
471 p = util.expandpath(p)
472 if not util.hasscheme(p) and not os.path.isabs(p):
472 if not util.hasscheme(p) and not os.path.isabs(p):
473 p = os.path.normpath(os.path.join(root, p))
473 p = os.path.normpath(os.path.join(root, p))
474 c.set("paths", n, p)
474 c.set("paths", n, p)
475
475
476 if section in (None, 'ui'):
476 if section in (None, 'ui'):
477 # update ui options
477 # update ui options
478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
478 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
479 self.debugflag = self.configbool('ui', 'debug')
479 self.debugflag = self.configbool('ui', 'debug')
480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
480 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
481 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
482 if self.verbose and self.quiet:
482 if self.verbose and self.quiet:
483 self.quiet = self.verbose = False
483 self.quiet = self.verbose = False
484 self._reportuntrusted = self.debugflag or self.configbool("ui",
484 self._reportuntrusted = self.debugflag or self.configbool("ui",
485 "report_untrusted")
485 "report_untrusted")
486 self.tracebackflag = self.configbool('ui', 'traceback')
486 self.tracebackflag = self.configbool('ui', 'traceback')
487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
487 self.logblockedtimes = self.configbool('ui', 'logblockedtimes')
488
488
489 if section in (None, 'trusted'):
489 if section in (None, 'trusted'):
490 # update trust information
490 # update trust information
491 self._trustusers.update(self.configlist('trusted', 'users'))
491 self._trustusers.update(self.configlist('trusted', 'users'))
492 self._trustgroups.update(self.configlist('trusted', 'groups'))
492 self._trustgroups.update(self.configlist('trusted', 'groups'))
493
493
494 if section in (None, b'devel', b'ui') and self.debugflag:
494 if section in (None, b'devel', b'ui') and self.debugflag:
495 tracked = set()
495 tracked = set()
496 if self.configbool(b'devel', b'debug.extensions'):
496 if self.configbool(b'devel', b'debug.extensions'):
497 tracked.add(b'extension')
497 tracked.add(b'extension')
498 if tracked:
498 if tracked:
499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
499 logger = loggingutil.fileobjectlogger(self._ferr, tracked)
500 self.setlogger(b'debug', logger)
500 self.setlogger(b'debug', logger)
501
501
502 def backupconfig(self, section, item):
502 def backupconfig(self, section, item):
503 return (self._ocfg.backup(section, item),
503 return (self._ocfg.backup(section, item),
504 self._tcfg.backup(section, item),
504 self._tcfg.backup(section, item),
505 self._ucfg.backup(section, item),)
505 self._ucfg.backup(section, item),)
506 def restoreconfig(self, data):
506 def restoreconfig(self, data):
507 self._ocfg.restore(data[0])
507 self._ocfg.restore(data[0])
508 self._tcfg.restore(data[1])
508 self._tcfg.restore(data[1])
509 self._ucfg.restore(data[2])
509 self._ucfg.restore(data[2])
510
510
511 def setconfig(self, section, name, value, source=''):
511 def setconfig(self, section, name, value, source=''):
512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
512 for cfg in (self._ocfg, self._tcfg, self._ucfg):
513 cfg.set(section, name, value, source)
513 cfg.set(section, name, value, source)
514 self.fixconfig(section=section)
514 self.fixconfig(section=section)
515 self._maybetweakdefaults()
515 self._maybetweakdefaults()
516
516
517 def _data(self, untrusted):
517 def _data(self, untrusted):
518 return untrusted and self._ucfg or self._tcfg
518 return untrusted and self._ucfg or self._tcfg
519
519
520 def configsource(self, section, name, untrusted=False):
520 def configsource(self, section, name, untrusted=False):
521 return self._data(untrusted).source(section, name)
521 return self._data(untrusted).source(section, name)
522
522
523 def config(self, section, name, default=_unset, untrusted=False):
523 def config(self, section, name, default=_unset, untrusted=False):
524 """return the plain string version of a config"""
524 """return the plain string version of a config"""
525 value = self._config(section, name, default=default,
525 value = self._config(section, name, default=default,
526 untrusted=untrusted)
526 untrusted=untrusted)
527 if value is _unset:
527 if value is _unset:
528 return None
528 return None
529 return value
529 return value
530
530
531 def _config(self, section, name, default=_unset, untrusted=False):
531 def _config(self, section, name, default=_unset, untrusted=False):
532 value = itemdefault = default
532 value = itemdefault = default
533 item = self._knownconfig.get(section, {}).get(name)
533 item = self._knownconfig.get(section, {}).get(name)
534 alternates = [(section, name)]
534 alternates = [(section, name)]
535
535
536 if item is not None:
536 if item is not None:
537 alternates.extend(item.alias)
537 alternates.extend(item.alias)
538 if callable(item.default):
538 if callable(item.default):
539 itemdefault = item.default()
539 itemdefault = item.default()
540 else:
540 else:
541 itemdefault = item.default
541 itemdefault = item.default
542 else:
542 else:
543 msg = ("accessing unregistered config item: '%s.%s'")
543 msg = ("accessing unregistered config item: '%s.%s'")
544 msg %= (section, name)
544 msg %= (section, name)
545 self.develwarn(msg, 2, 'warn-config-unknown')
545 self.develwarn(msg, 2, 'warn-config-unknown')
546
546
547 if default is _unset:
547 if default is _unset:
548 if item is None:
548 if item is None:
549 value = default
549 value = default
550 elif item.default is configitems.dynamicdefault:
550 elif item.default is configitems.dynamicdefault:
551 value = None
551 value = None
552 msg = "config item requires an explicit default value: '%s.%s'"
552 msg = "config item requires an explicit default value: '%s.%s'"
553 msg %= (section, name)
553 msg %= (section, name)
554 self.develwarn(msg, 2, 'warn-config-default')
554 self.develwarn(msg, 2, 'warn-config-default')
555 else:
555 else:
556 value = itemdefault
556 value = itemdefault
557 elif (item is not None
557 elif (item is not None
558 and item.default is not configitems.dynamicdefault
558 and item.default is not configitems.dynamicdefault
559 and default != itemdefault):
559 and default != itemdefault):
560 msg = ("specifying a mismatched default value for a registered "
560 msg = ("specifying a mismatched default value for a registered "
561 "config item: '%s.%s' '%s'")
561 "config item: '%s.%s' '%s'")
562 msg %= (section, name, pycompat.bytestr(default))
562 msg %= (section, name, pycompat.bytestr(default))
563 self.develwarn(msg, 2, 'warn-config-default')
563 self.develwarn(msg, 2, 'warn-config-default')
564
564
565 for s, n in alternates:
565 for s, n in alternates:
566 candidate = self._data(untrusted).get(s, n, None)
566 candidate = self._data(untrusted).get(s, n, None)
567 if candidate is not None:
567 if candidate is not None:
568 value = candidate
568 value = candidate
569 section = s
569 section = s
570 name = n
570 name = n
571 break
571 break
572
572
573 if self.debugflag and not untrusted and self._reportuntrusted:
573 if self.debugflag and not untrusted and self._reportuntrusted:
574 for s, n in alternates:
574 for s, n in alternates:
575 uvalue = self._ucfg.get(s, n)
575 uvalue = self._ucfg.get(s, n)
576 if uvalue is not None and uvalue != value:
576 if uvalue is not None and uvalue != value:
577 self.debug("ignoring untrusted configuration option "
577 self.debug("ignoring untrusted configuration option "
578 "%s.%s = %s\n" % (s, n, uvalue))
578 "%s.%s = %s\n" % (s, n, uvalue))
579 return value
579 return value
580
580
581 def configsuboptions(self, section, name, default=_unset, untrusted=False):
581 def configsuboptions(self, section, name, default=_unset, untrusted=False):
582 """Get a config option and all sub-options.
582 """Get a config option and all sub-options.
583
583
584 Some config options have sub-options that are declared with the
584 Some config options have sub-options that are declared with the
585 format "key:opt = value". This method is used to return the main
585 format "key:opt = value". This method is used to return the main
586 option and all its declared sub-options.
586 option and all its declared sub-options.
587
587
588 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
588 Returns a 2-tuple of ``(option, sub-options)``, where `sub-options``
589 is a dict of defined sub-options where keys and values are strings.
589 is a dict of defined sub-options where keys and values are strings.
590 """
590 """
591 main = self.config(section, name, default, untrusted=untrusted)
591 main = self.config(section, name, default, untrusted=untrusted)
592 data = self._data(untrusted)
592 data = self._data(untrusted)
593 sub = {}
593 sub = {}
594 prefix = '%s:' % name
594 prefix = '%s:' % name
595 for k, v in data.items(section):
595 for k, v in data.items(section):
596 if k.startswith(prefix):
596 if k.startswith(prefix):
597 sub[k[len(prefix):]] = v
597 sub[k[len(prefix):]] = v
598
598
599 if self.debugflag and not untrusted and self._reportuntrusted:
599 if self.debugflag and not untrusted and self._reportuntrusted:
600 for k, v in sub.items():
600 for k, v in sub.items():
601 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
601 uvalue = self._ucfg.get(section, '%s:%s' % (name, k))
602 if uvalue is not None and uvalue != v:
602 if uvalue is not None and uvalue != v:
603 self.debug('ignoring untrusted configuration option '
603 self.debug('ignoring untrusted configuration option '
604 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
604 '%s:%s.%s = %s\n' % (section, name, k, uvalue))
605
605
606 return main, sub
606 return main, sub
607
607
608 def configpath(self, section, name, default=_unset, untrusted=False):
608 def configpath(self, section, name, default=_unset, untrusted=False):
609 'get a path config item, expanded relative to repo root or config file'
609 'get a path config item, expanded relative to repo root or config file'
610 v = self.config(section, name, default, untrusted)
610 v = self.config(section, name, default, untrusted)
611 if v is None:
611 if v is None:
612 return None
612 return None
613 if not os.path.isabs(v) or "://" not in v:
613 if not os.path.isabs(v) or "://" not in v:
614 src = self.configsource(section, name, untrusted)
614 src = self.configsource(section, name, untrusted)
615 if ':' in src:
615 if ':' in src:
616 base = os.path.dirname(src.rsplit(':')[0])
616 base = os.path.dirname(src.rsplit(':')[0])
617 v = os.path.join(base, os.path.expanduser(v))
617 v = os.path.join(base, os.path.expanduser(v))
618 return v
618 return v
619
619
620 def configbool(self, section, name, default=_unset, untrusted=False):
620 def configbool(self, section, name, default=_unset, untrusted=False):
621 """parse a configuration element as a boolean
621 """parse a configuration element as a boolean
622
622
623 >>> u = ui(); s = b'foo'
623 >>> u = ui(); s = b'foo'
624 >>> u.setconfig(s, b'true', b'yes')
624 >>> u.setconfig(s, b'true', b'yes')
625 >>> u.configbool(s, b'true')
625 >>> u.configbool(s, b'true')
626 True
626 True
627 >>> u.setconfig(s, b'false', b'no')
627 >>> u.setconfig(s, b'false', b'no')
628 >>> u.configbool(s, b'false')
628 >>> u.configbool(s, b'false')
629 False
629 False
630 >>> u.configbool(s, b'unknown')
630 >>> u.configbool(s, b'unknown')
631 False
631 False
632 >>> u.configbool(s, b'unknown', True)
632 >>> u.configbool(s, b'unknown', True)
633 True
633 True
634 >>> u.setconfig(s, b'invalid', b'somevalue')
634 >>> u.setconfig(s, b'invalid', b'somevalue')
635 >>> u.configbool(s, b'invalid')
635 >>> u.configbool(s, b'invalid')
636 Traceback (most recent call last):
636 Traceback (most recent call last):
637 ...
637 ...
638 ConfigError: foo.invalid is not a boolean ('somevalue')
638 ConfigError: foo.invalid is not a boolean ('somevalue')
639 """
639 """
640
640
641 v = self._config(section, name, default, untrusted=untrusted)
641 v = self._config(section, name, default, untrusted=untrusted)
642 if v is None:
642 if v is None:
643 return v
643 return v
644 if v is _unset:
644 if v is _unset:
645 if default is _unset:
645 if default is _unset:
646 return False
646 return False
647 return default
647 return default
648 if isinstance(v, bool):
648 if isinstance(v, bool):
649 return v
649 return v
650 b = stringutil.parsebool(v)
650 b = stringutil.parsebool(v)
651 if b is None:
651 if b is None:
652 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
652 raise error.ConfigError(_("%s.%s is not a boolean ('%s')")
653 % (section, name, v))
653 % (section, name, v))
654 return b
654 return b
655
655
656 def configwith(self, convert, section, name, default=_unset,
656 def configwith(self, convert, section, name, default=_unset,
657 desc=None, untrusted=False):
657 desc=None, untrusted=False):
658 """parse a configuration element with a conversion function
658 """parse a configuration element with a conversion function
659
659
660 >>> u = ui(); s = b'foo'
660 >>> u = ui(); s = b'foo'
661 >>> u.setconfig(s, b'float1', b'42')
661 >>> u.setconfig(s, b'float1', b'42')
662 >>> u.configwith(float, s, b'float1')
662 >>> u.configwith(float, s, b'float1')
663 42.0
663 42.0
664 >>> u.setconfig(s, b'float2', b'-4.25')
664 >>> u.setconfig(s, b'float2', b'-4.25')
665 >>> u.configwith(float, s, b'float2')
665 >>> u.configwith(float, s, b'float2')
666 -4.25
666 -4.25
667 >>> u.configwith(float, s, b'unknown', 7)
667 >>> u.configwith(float, s, b'unknown', 7)
668 7.0
668 7.0
669 >>> u.setconfig(s, b'invalid', b'somevalue')
669 >>> u.setconfig(s, b'invalid', b'somevalue')
670 >>> u.configwith(float, s, b'invalid')
670 >>> u.configwith(float, s, b'invalid')
671 Traceback (most recent call last):
671 Traceback (most recent call last):
672 ...
672 ...
673 ConfigError: foo.invalid is not a valid float ('somevalue')
673 ConfigError: foo.invalid is not a valid float ('somevalue')
674 >>> u.configwith(float, s, b'invalid', desc=b'womble')
674 >>> u.configwith(float, s, b'invalid', desc=b'womble')
675 Traceback (most recent call last):
675 Traceback (most recent call last):
676 ...
676 ...
677 ConfigError: foo.invalid is not a valid womble ('somevalue')
677 ConfigError: foo.invalid is not a valid womble ('somevalue')
678 """
678 """
679
679
680 v = self.config(section, name, default, untrusted)
680 v = self.config(section, name, default, untrusted)
681 if v is None:
681 if v is None:
682 return v # do not attempt to convert None
682 return v # do not attempt to convert None
683 try:
683 try:
684 return convert(v)
684 return convert(v)
685 except (ValueError, error.ParseError):
685 except (ValueError, error.ParseError):
686 if desc is None:
686 if desc is None:
687 desc = pycompat.sysbytes(convert.__name__)
687 desc = pycompat.sysbytes(convert.__name__)
688 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
688 raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
689 % (section, name, desc, v))
689 % (section, name, desc, v))
690
690
691 def configint(self, section, name, default=_unset, untrusted=False):
691 def configint(self, section, name, default=_unset, untrusted=False):
692 """parse a configuration element as an integer
692 """parse a configuration element as an integer
693
693
694 >>> u = ui(); s = b'foo'
694 >>> u = ui(); s = b'foo'
695 >>> u.setconfig(s, b'int1', b'42')
695 >>> u.setconfig(s, b'int1', b'42')
696 >>> u.configint(s, b'int1')
696 >>> u.configint(s, b'int1')
697 42
697 42
698 >>> u.setconfig(s, b'int2', b'-42')
698 >>> u.setconfig(s, b'int2', b'-42')
699 >>> u.configint(s, b'int2')
699 >>> u.configint(s, b'int2')
700 -42
700 -42
701 >>> u.configint(s, b'unknown', 7)
701 >>> u.configint(s, b'unknown', 7)
702 7
702 7
703 >>> u.setconfig(s, b'invalid', b'somevalue')
703 >>> u.setconfig(s, b'invalid', b'somevalue')
704 >>> u.configint(s, b'invalid')
704 >>> u.configint(s, b'invalid')
705 Traceback (most recent call last):
705 Traceback (most recent call last):
706 ...
706 ...
707 ConfigError: foo.invalid is not a valid integer ('somevalue')
707 ConfigError: foo.invalid is not a valid integer ('somevalue')
708 """
708 """
709
709
710 return self.configwith(int, section, name, default, 'integer',
710 return self.configwith(int, section, name, default, 'integer',
711 untrusted)
711 untrusted)
712
712
713 def configbytes(self, section, name, default=_unset, untrusted=False):
713 def configbytes(self, section, name, default=_unset, untrusted=False):
714 """parse a configuration element as a quantity in bytes
714 """parse a configuration element as a quantity in bytes
715
715
716 Units can be specified as b (bytes), k or kb (kilobytes), m or
716 Units can be specified as b (bytes), k or kb (kilobytes), m or
717 mb (megabytes), g or gb (gigabytes).
717 mb (megabytes), g or gb (gigabytes).
718
718
719 >>> u = ui(); s = b'foo'
719 >>> u = ui(); s = b'foo'
720 >>> u.setconfig(s, b'val1', b'42')
720 >>> u.setconfig(s, b'val1', b'42')
721 >>> u.configbytes(s, b'val1')
721 >>> u.configbytes(s, b'val1')
722 42
722 42
723 >>> u.setconfig(s, b'val2', b'42.5 kb')
723 >>> u.setconfig(s, b'val2', b'42.5 kb')
724 >>> u.configbytes(s, b'val2')
724 >>> u.configbytes(s, b'val2')
725 43520
725 43520
726 >>> u.configbytes(s, b'unknown', b'7 MB')
726 >>> u.configbytes(s, b'unknown', b'7 MB')
727 7340032
727 7340032
728 >>> u.setconfig(s, b'invalid', b'somevalue')
728 >>> u.setconfig(s, b'invalid', b'somevalue')
729 >>> u.configbytes(s, b'invalid')
729 >>> u.configbytes(s, b'invalid')
730 Traceback (most recent call last):
730 Traceback (most recent call last):
731 ...
731 ...
732 ConfigError: foo.invalid is not a byte quantity ('somevalue')
732 ConfigError: foo.invalid is not a byte quantity ('somevalue')
733 """
733 """
734
734
735 value = self._config(section, name, default, untrusted)
735 value = self._config(section, name, default, untrusted)
736 if value is _unset:
736 if value is _unset:
737 if default is _unset:
737 if default is _unset:
738 default = 0
738 default = 0
739 value = default
739 value = default
740 if not isinstance(value, bytes):
740 if not isinstance(value, bytes):
741 return value
741 return value
742 try:
742 try:
743 return util.sizetoint(value)
743 return util.sizetoint(value)
744 except error.ParseError:
744 except error.ParseError:
745 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
745 raise error.ConfigError(_("%s.%s is not a byte quantity ('%s')")
746 % (section, name, value))
746 % (section, name, value))
747
747
748 def configlist(self, section, name, default=_unset, untrusted=False):
748 def configlist(self, section, name, default=_unset, untrusted=False):
749 """parse a configuration element as a list of comma/space separated
749 """parse a configuration element as a list of comma/space separated
750 strings
750 strings
751
751
752 >>> u = ui(); s = b'foo'
752 >>> u = ui(); s = b'foo'
753 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
753 >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
754 >>> u.configlist(s, b'list1')
754 >>> u.configlist(s, b'list1')
755 ['this', 'is', 'a small', 'test']
755 ['this', 'is', 'a small', 'test']
756 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
756 >>> u.setconfig(s, b'list2', b'this, is "a small" , test ')
757 >>> u.configlist(s, b'list2')
757 >>> u.configlist(s, b'list2')
758 ['this', 'is', 'a small', 'test']
758 ['this', 'is', 'a small', 'test']
759 """
759 """
760 # default is not always a list
760 # default is not always a list
761 v = self.configwith(config.parselist, section, name, default,
761 v = self.configwith(config.parselist, section, name, default,
762 'list', untrusted)
762 'list', untrusted)
763 if isinstance(v, bytes):
763 if isinstance(v, bytes):
764 return config.parselist(v)
764 return config.parselist(v)
765 elif v is None:
765 elif v is None:
766 return []
766 return []
767 return v
767 return v
768
768
769 def configdate(self, section, name, default=_unset, untrusted=False):
769 def configdate(self, section, name, default=_unset, untrusted=False):
770 """parse a configuration element as a tuple of ints
770 """parse a configuration element as a tuple of ints
771
771
772 >>> u = ui(); s = b'foo'
772 >>> u = ui(); s = b'foo'
773 >>> u.setconfig(s, b'date', b'0 0')
773 >>> u.setconfig(s, b'date', b'0 0')
774 >>> u.configdate(s, b'date')
774 >>> u.configdate(s, b'date')
775 (0, 0)
775 (0, 0)
776 """
776 """
777 if self.config(section, name, default, untrusted):
777 if self.config(section, name, default, untrusted):
778 return self.configwith(dateutil.parsedate, section, name, default,
778 return self.configwith(dateutil.parsedate, section, name, default,
779 'date', untrusted)
779 'date', untrusted)
780 if default is _unset:
780 if default is _unset:
781 return None
781 return None
782 return default
782 return default
783
783
784 def hasconfig(self, section, name, untrusted=False):
784 def hasconfig(self, section, name, untrusted=False):
785 return self._data(untrusted).hasitem(section, name)
785 return self._data(untrusted).hasitem(section, name)
786
786
787 def has_section(self, section, untrusted=False):
787 def has_section(self, section, untrusted=False):
788 '''tell whether section exists in config.'''
788 '''tell whether section exists in config.'''
789 return section in self._data(untrusted)
789 return section in self._data(untrusted)
790
790
791 def configitems(self, section, untrusted=False, ignoresub=False):
791 def configitems(self, section, untrusted=False, ignoresub=False):
792 items = self._data(untrusted).items(section)
792 items = self._data(untrusted).items(section)
793 if ignoresub:
793 if ignoresub:
794 items = [i for i in items if ':' not in i[0]]
794 items = [i for i in items if ':' not in i[0]]
795 if self.debugflag and not untrusted and self._reportuntrusted:
795 if self.debugflag and not untrusted and self._reportuntrusted:
796 for k, v in self._ucfg.items(section):
796 for k, v in self._ucfg.items(section):
797 if self._tcfg.get(section, k) != v:
797 if self._tcfg.get(section, k) != v:
798 self.debug("ignoring untrusted configuration option "
798 self.debug("ignoring untrusted configuration option "
799 "%s.%s = %s\n" % (section, k, v))
799 "%s.%s = %s\n" % (section, k, v))
800 return items
800 return items
801
801
802 def walkconfig(self, untrusted=False):
802 def walkconfig(self, untrusted=False):
803 cfg = self._data(untrusted)
803 cfg = self._data(untrusted)
804 for section in cfg.sections():
804 for section in cfg.sections():
805 for name, value in self.configitems(section, untrusted):
805 for name, value in self.configitems(section, untrusted):
806 yield section, name, value
806 yield section, name, value
807
807
808 def plain(self, feature=None):
808 def plain(self, feature=None):
809 '''is plain mode active?
809 '''is plain mode active?
810
810
811 Plain mode means that all configuration variables which affect
811 Plain mode means that all configuration variables which affect
812 the behavior and output of Mercurial should be
812 the behavior and output of Mercurial should be
813 ignored. Additionally, the output should be stable,
813 ignored. Additionally, the output should be stable,
814 reproducible and suitable for use in scripts or applications.
814 reproducible and suitable for use in scripts or applications.
815
815
816 The only way to trigger plain mode is by setting either the
816 The only way to trigger plain mode is by setting either the
817 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
817 `HGPLAIN' or `HGPLAINEXCEPT' environment variables.
818
818
819 The return value can either be
819 The return value can either be
820 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
820 - False if HGPLAIN is not set, or feature is in HGPLAINEXCEPT
821 - False if feature is disabled by default and not included in HGPLAIN
821 - False if feature is disabled by default and not included in HGPLAIN
822 - True otherwise
822 - True otherwise
823 '''
823 '''
824 if ('HGPLAIN' not in encoding.environ and
824 if ('HGPLAIN' not in encoding.environ and
825 'HGPLAINEXCEPT' not in encoding.environ):
825 'HGPLAINEXCEPT' not in encoding.environ):
826 return False
826 return False
827 exceptions = encoding.environ.get('HGPLAINEXCEPT',
827 exceptions = encoding.environ.get('HGPLAINEXCEPT',
828 '').strip().split(',')
828 '').strip().split(',')
829 # TODO: add support for HGPLAIN=+feature,-feature syntax
829 # TODO: add support for HGPLAIN=+feature,-feature syntax
830 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
830 if '+strictflags' not in encoding.environ.get('HGPLAIN', '').split(','):
831 exceptions.append('strictflags')
831 exceptions.append('strictflags')
832 if feature and exceptions:
832 if feature and exceptions:
833 return feature not in exceptions
833 return feature not in exceptions
834 return True
834 return True
835
835
836 def username(self, acceptempty=False):
836 def username(self, acceptempty=False):
837 """Return default username to be used in commits.
837 """Return default username to be used in commits.
838
838
839 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
839 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
840 and stop searching if one of these is set.
840 and stop searching if one of these is set.
841 If not found and acceptempty is True, returns None.
841 If not found and acceptempty is True, returns None.
842 If not found and ui.askusername is True, ask the user, else use
842 If not found and ui.askusername is True, ask the user, else use
843 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
843 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
844 If no username could be found, raise an Abort error.
844 If no username could be found, raise an Abort error.
845 """
845 """
846 user = encoding.environ.get("HGUSER")
846 user = encoding.environ.get("HGUSER")
847 if user is None:
847 if user is None:
848 user = self.config("ui", "username")
848 user = self.config("ui", "username")
849 if user is not None:
849 if user is not None:
850 user = os.path.expandvars(user)
850 user = os.path.expandvars(user)
851 if user is None:
851 if user is None:
852 user = encoding.environ.get("EMAIL")
852 user = encoding.environ.get("EMAIL")
853 if user is None and acceptempty:
853 if user is None and acceptempty:
854 return user
854 return user
855 if user is None and self.configbool("ui", "askusername"):
855 if user is None and self.configbool("ui", "askusername"):
856 user = self.prompt(_("enter a commit username:"), default=None)
856 user = self.prompt(_("enter a commit username:"), default=None)
857 if user is None and not self.interactive():
857 if user is None and not self.interactive():
858 try:
858 try:
859 user = '%s@%s' % (procutil.getuser(),
859 user = '%s@%s' % (procutil.getuser(),
860 encoding.strtolocal(socket.getfqdn()))
860 encoding.strtolocal(socket.getfqdn()))
861 self.warn(_("no username found, using '%s' instead\n") % user)
861 self.warn(_("no username found, using '%s' instead\n") % user)
862 except KeyError:
862 except KeyError:
863 pass
863 pass
864 if not user:
864 if not user:
865 raise error.Abort(_('no username supplied'),
865 raise error.Abort(_('no username supplied'),
866 hint=_("use 'hg config --edit' "
866 hint=_("use 'hg config --edit' "
867 'to set your username'))
867 'to set your username'))
868 if "\n" in user:
868 if "\n" in user:
869 raise error.Abort(_("username %r contains a newline\n")
869 raise error.Abort(_("username %r contains a newline\n")
870 % pycompat.bytestr(user))
870 % pycompat.bytestr(user))
871 return user
871 return user
872
872
873 def shortuser(self, user):
873 def shortuser(self, user):
874 """Return a short representation of a user name or email address."""
874 """Return a short representation of a user name or email address."""
875 if not self.verbose:
875 if not self.verbose:
876 user = stringutil.shortuser(user)
876 user = stringutil.shortuser(user)
877 return user
877 return user
878
878
879 def expandpath(self, loc, default=None):
879 def expandpath(self, loc, default=None):
880 """Return repository location relative to cwd or from [paths]"""
880 """Return repository location relative to cwd or from [paths]"""
881 try:
881 try:
882 p = self.paths.getpath(loc)
882 p = self.paths.getpath(loc)
883 if p:
883 if p:
884 return p.rawloc
884 return p.rawloc
885 except error.RepoError:
885 except error.RepoError:
886 pass
886 pass
887
887
888 if default:
888 if default:
889 try:
889 try:
890 p = self.paths.getpath(default)
890 p = self.paths.getpath(default)
891 if p:
891 if p:
892 return p.rawloc
892 return p.rawloc
893 except error.RepoError:
893 except error.RepoError:
894 pass
894 pass
895
895
896 return loc
896 return loc
897
897
898 @util.propertycache
898 @util.propertycache
899 def paths(self):
899 def paths(self):
900 return paths(self)
900 return paths(self)
901
901
902 @property
902 @property
903 def fout(self):
903 def fout(self):
904 return self._fout
904 return self._fout
905
905
906 @fout.setter
906 @fout.setter
907 def fout(self, f):
907 def fout(self, f):
908 self._fout = f
908 self._fout = f
909 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
909 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
910
910
911 @property
911 @property
912 def ferr(self):
912 def ferr(self):
913 return self._ferr
913 return self._ferr
914
914
915 @ferr.setter
915 @ferr.setter
916 def ferr(self, f):
916 def ferr(self, f):
917 self._ferr = f
917 self._ferr = f
918 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
918 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
919
919
920 @property
920 @property
921 def fin(self):
921 def fin(self):
922 return self._fin
922 return self._fin
923
923
924 @fin.setter
924 @fin.setter
925 def fin(self, f):
925 def fin(self, f):
926 self._fin = f
926 self._fin = f
927
927
928 @property
928 @property
929 def fmsg(self):
929 def fmsg(self):
930 """Stream dedicated for status/error messages; may be None if
930 """Stream dedicated for status/error messages; may be None if
931 fout/ferr are used"""
931 fout/ferr are used"""
932 return self._fmsg
932 return self._fmsg
933
933
934 @fmsg.setter
934 @fmsg.setter
935 def fmsg(self, f):
935 def fmsg(self, f):
936 self._fmsg = f
936 self._fmsg = f
937 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
937 self._fmsgout, self._fmsgerr = _selectmsgdests(self)
938
938
939 def pushbuffer(self, error=False, subproc=False, labeled=False):
939 def pushbuffer(self, error=False, subproc=False, labeled=False):
940 """install a buffer to capture standard output of the ui object
940 """install a buffer to capture standard output of the ui object
941
941
942 If error is True, the error output will be captured too.
942 If error is True, the error output will be captured too.
943
943
944 If subproc is True, output from subprocesses (typically hooks) will be
944 If subproc is True, output from subprocesses (typically hooks) will be
945 captured too.
945 captured too.
946
946
947 If labeled is True, any labels associated with buffered
947 If labeled is True, any labels associated with buffered
948 output will be handled. By default, this has no effect
948 output will be handled. By default, this has no effect
949 on the output returned, but extensions and GUI tools may
949 on the output returned, but extensions and GUI tools may
950 handle this argument and returned styled output. If output
950 handle this argument and returned styled output. If output
951 is being buffered so it can be captured and parsed or
951 is being buffered so it can be captured and parsed or
952 processed, labeled should not be set to True.
952 processed, labeled should not be set to True.
953 """
953 """
954 self._buffers.append([])
954 self._buffers.append([])
955 self._bufferstates.append((error, subproc, labeled))
955 self._bufferstates.append((error, subproc, labeled))
956 self._bufferapplylabels = labeled
956 self._bufferapplylabels = labeled
957
957
958 def popbuffer(self):
958 def popbuffer(self):
959 '''pop the last buffer and return the buffered output'''
959 '''pop the last buffer and return the buffered output'''
960 self._bufferstates.pop()
960 self._bufferstates.pop()
961 if self._bufferstates:
961 if self._bufferstates:
962 self._bufferapplylabels = self._bufferstates[-1][2]
962 self._bufferapplylabels = self._bufferstates[-1][2]
963 else:
963 else:
964 self._bufferapplylabels = None
964 self._bufferapplylabels = None
965
965
966 return "".join(self._buffers.pop())
966 return "".join(self._buffers.pop())
967
967
968 def _isbuffered(self, dest):
968 def _isbuffered(self, dest):
969 if dest is self._fout:
969 if dest is self._fout:
970 return bool(self._buffers)
970 return bool(self._buffers)
971 if dest is self._ferr:
971 if dest is self._ferr:
972 return bool(self._bufferstates and self._bufferstates[-1][0])
972 return bool(self._bufferstates and self._bufferstates[-1][0])
973 return False
973 return False
974
974
975 def canwritewithoutlabels(self):
975 def canwritewithoutlabels(self):
976 '''check if write skips the label'''
976 '''check if write skips the label'''
977 if self._buffers and not self._bufferapplylabels:
977 if self._buffers and not self._bufferapplylabels:
978 return True
978 return True
979 return self._colormode is None
979 return self._colormode is None
980
980
981 def canbatchlabeledwrites(self):
981 def canbatchlabeledwrites(self):
982 '''check if write calls with labels are batchable'''
982 '''check if write calls with labels are batchable'''
983 # Windows color printing is special, see ``write``.
983 # Windows color printing is special, see ``write``.
984 return self._colormode != 'win32'
984 return self._colormode != 'win32'
985
985
986 def write(self, *args, **opts):
986 def write(self, *args, **opts):
987 '''write args to output
987 '''write args to output
988
988
989 By default, this method simply writes to the buffer or stdout.
989 By default, this method simply writes to the buffer or stdout.
990 Color mode can be set on the UI class to have the output decorated
990 Color mode can be set on the UI class to have the output decorated
991 with color modifier before being written to stdout.
991 with color modifier before being written to stdout.
992
992
993 The color used is controlled by an optional keyword argument, "label".
993 The color used is controlled by an optional keyword argument, "label".
994 This should be a string containing label names separated by space.
994 This should be a string containing label names separated by space.
995 Label names take the form of "topic.type". For example, ui.debug()
995 Label names take the form of "topic.type". For example, ui.debug()
996 issues a label of "ui.debug".
996 issues a label of "ui.debug".
997
997
998 When labeling output for a specific command, a label of
998 When labeling output for a specific command, a label of
999 "cmdname.type" is recommended. For example, status issues
999 "cmdname.type" is recommended. For example, status issues
1000 a label of "status.modified" for modified files.
1000 a label of "status.modified" for modified files.
1001 '''
1001 '''
1002 self._write(self._fout, *args, **opts)
1002 self._write(self._fout, *args, **opts)
1003
1003
1004 def write_err(self, *args, **opts):
1004 def write_err(self, *args, **opts):
1005 self._write(self._ferr, *args, **opts)
1005 self._write(self._ferr, *args, **opts)
1006
1006
1007 def _write(self, dest, *args, **opts):
1007 def _write(self, dest, *args, **opts):
1008 if self._isbuffered(dest):
1008 if self._isbuffered(dest):
1009 if self._bufferapplylabels:
1009 if self._bufferapplylabels:
1010 label = opts.get(r'label', '')
1010 label = opts.get(r'label', '')
1011 self._buffers[-1].extend(self.label(a, label) for a in args)
1011 self._buffers[-1].extend(self.label(a, label) for a in args)
1012 else:
1012 else:
1013 self._buffers[-1].extend(args)
1013 self._buffers[-1].extend(args)
1014 else:
1014 else:
1015 self._writenobuf(dest, *args, **opts)
1015 self._writenobuf(dest, *args, **opts)
1016
1016
1017 def _writenobuf(self, dest, *args, **opts):
1017 def _writenobuf(self, dest, *args, **opts):
1018 self._progclear()
1018 self._progclear()
1019 msg = b''.join(args)
1019 msg = b''.join(args)
1020
1020
1021 # opencode timeblockedsection because this is a critical path
1021 # opencode timeblockedsection because this is a critical path
1022 starttime = util.timer()
1022 starttime = util.timer()
1023 try:
1023 try:
1024 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1024 if dest is self._ferr and not getattr(self._fout, 'closed', False):
1025 self._fout.flush()
1025 self._fout.flush()
1026 if getattr(dest, 'structured', False):
1026 if getattr(dest, 'structured', False):
1027 # channel for machine-readable output with metadata, where
1027 # channel for machine-readable output with metadata, where
1028 # no extra colorization is necessary.
1028 # no extra colorization is necessary.
1029 dest.write(msg, **opts)
1029 dest.write(msg, **opts)
1030 elif self._colormode == 'win32':
1030 elif self._colormode == 'win32':
1031 # windows color printing is its own can of crab, defer to
1031 # windows color printing is its own can of crab, defer to
1032 # the color module and that is it.
1032 # the color module and that is it.
1033 color.win32print(self, dest.write, msg, **opts)
1033 color.win32print(self, dest.write, msg, **opts)
1034 else:
1034 else:
1035 if self._colormode is not None:
1035 if self._colormode is not None:
1036 label = opts.get(r'label', '')
1036 label = opts.get(r'label', '')
1037 msg = self.label(msg, label)
1037 msg = self.label(msg, label)
1038 dest.write(msg)
1038 dest.write(msg)
1039 # stderr may be buffered under win32 when redirected to files,
1039 # stderr may be buffered under win32 when redirected to files,
1040 # including stdout.
1040 # including stdout.
1041 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1041 if dest is self._ferr and not getattr(self._ferr, 'closed', False):
1042 dest.flush()
1042 dest.flush()
1043 except IOError as err:
1043 except IOError as err:
1044 if (dest is self._ferr
1044 if (dest is self._ferr
1045 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1045 and err.errno in (errno.EPIPE, errno.EIO, errno.EBADF)):
1046 # no way to report the error, so ignore it
1046 # no way to report the error, so ignore it
1047 return
1047 return
1048 raise error.StdioError(err)
1048 raise error.StdioError(err)
1049 finally:
1049 finally:
1050 self._blockedtimes['stdio_blocked'] += \
1050 self._blockedtimes['stdio_blocked'] += \
1051 (util.timer() - starttime) * 1000
1051 (util.timer() - starttime) * 1000
1052
1052
1053 def _writemsg(self, dest, *args, **opts):
1053 def _writemsg(self, dest, *args, **opts):
1054 _writemsgwith(self._write, dest, *args, **opts)
1054 _writemsgwith(self._write, dest, *args, **opts)
1055
1055
1056 def _writemsgnobuf(self, dest, *args, **opts):
1056 def _writemsgnobuf(self, dest, *args, **opts):
1057 _writemsgwith(self._writenobuf, dest, *args, **opts)
1057 _writemsgwith(self._writenobuf, dest, *args, **opts)
1058
1058
1059 def flush(self):
1059 def flush(self):
1060 # opencode timeblockedsection because this is a critical path
1060 # opencode timeblockedsection because this is a critical path
1061 starttime = util.timer()
1061 starttime = util.timer()
1062 try:
1062 try:
1063 try:
1063 try:
1064 self._fout.flush()
1064 self._fout.flush()
1065 except IOError as err:
1065 except IOError as err:
1066 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1066 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1067 raise error.StdioError(err)
1067 raise error.StdioError(err)
1068 finally:
1068 finally:
1069 try:
1069 try:
1070 self._ferr.flush()
1070 self._ferr.flush()
1071 except IOError as err:
1071 except IOError as err:
1072 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1072 if err.errno not in (errno.EPIPE, errno.EIO, errno.EBADF):
1073 raise error.StdioError(err)
1073 raise error.StdioError(err)
1074 finally:
1074 finally:
1075 self._blockedtimes['stdio_blocked'] += \
1075 self._blockedtimes['stdio_blocked'] += \
1076 (util.timer() - starttime) * 1000
1076 (util.timer() - starttime) * 1000
1077
1077
1078 def _isatty(self, fh):
1078 def _isatty(self, fh):
1079 if self.configbool('ui', 'nontty'):
1079 if self.configbool('ui', 'nontty'):
1080 return False
1080 return False
1081 return procutil.isatty(fh)
1081 return procutil.isatty(fh)
1082
1082
1083 def disablepager(self):
1083 def disablepager(self):
1084 self._disablepager = True
1084 self._disablepager = True
1085
1085
1086 def pager(self, command):
1086 def pager(self, command):
1087 """Start a pager for subsequent command output.
1087 """Start a pager for subsequent command output.
1088
1088
1089 Commands which produce a long stream of output should call
1089 Commands which produce a long stream of output should call
1090 this function to activate the user's preferred pagination
1090 this function to activate the user's preferred pagination
1091 mechanism (which may be no pager). Calling this function
1091 mechanism (which may be no pager). Calling this function
1092 precludes any future use of interactive functionality, such as
1092 precludes any future use of interactive functionality, such as
1093 prompting the user or activating curses.
1093 prompting the user or activating curses.
1094
1094
1095 Args:
1095 Args:
1096 command: The full, non-aliased name of the command. That is, "log"
1096 command: The full, non-aliased name of the command. That is, "log"
1097 not "history, "summary" not "summ", etc.
1097 not "history, "summary" not "summ", etc.
1098 """
1098 """
1099 if (self._disablepager
1099 if (self._disablepager
1100 or self.pageractive):
1100 or self.pageractive):
1101 # how pager should do is already determined
1101 # how pager should do is already determined
1102 return
1102 return
1103
1103
1104 if not command.startswith('internal-always-') and (
1104 if not command.startswith('internal-always-') and (
1105 # explicit --pager=on (= 'internal-always-' prefix) should
1105 # explicit --pager=on (= 'internal-always-' prefix) should
1106 # take precedence over disabling factors below
1106 # take precedence over disabling factors below
1107 command in self.configlist('pager', 'ignore')
1107 command in self.configlist('pager', 'ignore')
1108 or not self.configbool('ui', 'paginate')
1108 or not self.configbool('ui', 'paginate')
1109 or not self.configbool('pager', 'attend-' + command, True)
1109 or not self.configbool('pager', 'attend-' + command, True)
1110 or encoding.environ.get('TERM') == 'dumb'
1110 or encoding.environ.get('TERM') == 'dumb'
1111 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1111 # TODO: if we want to allow HGPLAINEXCEPT=pager,
1112 # formatted() will need some adjustment.
1112 # formatted() will need some adjustment.
1113 or not self.formatted()
1113 or not self.formatted()
1114 or self.plain()
1114 or self.plain()
1115 or self._buffers
1115 or self._buffers
1116 # TODO: expose debugger-enabled on the UI object
1116 # TODO: expose debugger-enabled on the UI object
1117 or '--debugger' in pycompat.sysargv):
1117 or '--debugger' in pycompat.sysargv):
1118 # We only want to paginate if the ui appears to be
1118 # We only want to paginate if the ui appears to be
1119 # interactive, the user didn't say HGPLAIN or
1119 # interactive, the user didn't say HGPLAIN or
1120 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1120 # HGPLAINEXCEPT=pager, and the user didn't specify --debug.
1121 return
1121 return
1122
1122
1123 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1123 pagercmd = self.config('pager', 'pager', rcutil.fallbackpager)
1124 if not pagercmd:
1124 if not pagercmd:
1125 return
1125 return
1126
1126
1127 pagerenv = {}
1127 pagerenv = {}
1128 for name, value in rcutil.defaultpagerenv().items():
1128 for name, value in rcutil.defaultpagerenv().items():
1129 if name not in encoding.environ:
1129 if name not in encoding.environ:
1130 pagerenv[name] = value
1130 pagerenv[name] = value
1131
1131
1132 self.debug('starting pager for command %s\n' %
1132 self.debug('starting pager for command %s\n' %
1133 stringutil.pprint(command))
1133 stringutil.pprint(command))
1134 self.flush()
1134 self.flush()
1135
1135
1136 wasformatted = self.formatted()
1136 wasformatted = self.formatted()
1137 if util.safehasattr(signal, "SIGPIPE"):
1137 if util.safehasattr(signal, "SIGPIPE"):
1138 signal.signal(signal.SIGPIPE, _catchterm)
1138 signal.signal(signal.SIGPIPE, _catchterm)
1139 if self._runpager(pagercmd, pagerenv):
1139 if self._runpager(pagercmd, pagerenv):
1140 self.pageractive = True
1140 self.pageractive = True
1141 # Preserve the formatted-ness of the UI. This is important
1141 # Preserve the formatted-ness of the UI. This is important
1142 # because we mess with stdout, which might confuse
1142 # because we mess with stdout, which might confuse
1143 # auto-detection of things being formatted.
1143 # auto-detection of things being formatted.
1144 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1144 self.setconfig('ui', 'formatted', wasformatted, 'pager')
1145 self.setconfig('ui', 'interactive', False, 'pager')
1145 self.setconfig('ui', 'interactive', False, 'pager')
1146
1146
1147 # If pagermode differs from color.mode, reconfigure color now that
1147 # If pagermode differs from color.mode, reconfigure color now that
1148 # pageractive is set.
1148 # pageractive is set.
1149 cm = self._colormode
1149 cm = self._colormode
1150 if cm != self.config('color', 'pagermode', cm):
1150 if cm != self.config('color', 'pagermode', cm):
1151 color.setup(self)
1151 color.setup(self)
1152 else:
1152 else:
1153 # If the pager can't be spawned in dispatch when --pager=on is
1153 # If the pager can't be spawned in dispatch when --pager=on is
1154 # given, don't try again when the command runs, to avoid a duplicate
1154 # given, don't try again when the command runs, to avoid a duplicate
1155 # warning about a missing pager command.
1155 # warning about a missing pager command.
1156 self.disablepager()
1156 self.disablepager()
1157
1157
1158 def _runpager(self, command, env=None):
1158 def _runpager(self, command, env=None):
1159 """Actually start the pager and set up file descriptors.
1159 """Actually start the pager and set up file descriptors.
1160
1160
1161 This is separate in part so that extensions (like chg) can
1161 This is separate in part so that extensions (like chg) can
1162 override how a pager is invoked.
1162 override how a pager is invoked.
1163 """
1163 """
1164 if command == 'cat':
1164 if command == 'cat':
1165 # Save ourselves some work.
1165 # Save ourselves some work.
1166 return False
1166 return False
1167 # If the command doesn't contain any of these characters, we
1167 # If the command doesn't contain any of these characters, we
1168 # assume it's a binary and exec it directly. This means for
1168 # assume it's a binary and exec it directly. This means for
1169 # simple pager command configurations, we can degrade
1169 # simple pager command configurations, we can degrade
1170 # gracefully and tell the user about their broken pager.
1170 # gracefully and tell the user about their broken pager.
1171 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1171 shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
1172
1172
1173 if pycompat.iswindows and not shell:
1173 if pycompat.iswindows and not shell:
1174 # Window's built-in `more` cannot be invoked with shell=False, but
1174 # Window's built-in `more` cannot be invoked with shell=False, but
1175 # its `more.com` can. Hide this implementation detail from the
1175 # its `more.com` can. Hide this implementation detail from the
1176 # user so we can also get sane bad PAGER behavior. MSYS has
1176 # user so we can also get sane bad PAGER behavior. MSYS has
1177 # `more.exe`, so do a cmd.exe style resolution of the executable to
1177 # `more.exe`, so do a cmd.exe style resolution of the executable to
1178 # determine which one to use.
1178 # determine which one to use.
1179 fullcmd = procutil.findexe(command)
1179 fullcmd = procutil.findexe(command)
1180 if not fullcmd:
1180 if not fullcmd:
1181 self.warn(_("missing pager command '%s', skipping pager\n")
1181 self.warn(_("missing pager command '%s', skipping pager\n")
1182 % command)
1182 % command)
1183 return False
1183 return False
1184
1184
1185 command = fullcmd
1185 command = fullcmd
1186
1186
1187 try:
1187 try:
1188 pager = subprocess.Popen(
1188 pager = subprocess.Popen(
1189 procutil.tonativestr(command), shell=shell, bufsize=-1,
1189 procutil.tonativestr(command), shell=shell, bufsize=-1,
1190 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1190 close_fds=procutil.closefds, stdin=subprocess.PIPE,
1191 stdout=procutil.stdout, stderr=procutil.stderr,
1191 stdout=procutil.stdout, stderr=procutil.stderr,
1192 env=procutil.tonativeenv(procutil.shellenviron(env)))
1192 env=procutil.tonativeenv(procutil.shellenviron(env)))
1193 except OSError as e:
1193 except OSError as e:
1194 if e.errno == errno.ENOENT and not shell:
1194 if e.errno == errno.ENOENT and not shell:
1195 self.warn(_("missing pager command '%s', skipping pager\n")
1195 self.warn(_("missing pager command '%s', skipping pager\n")
1196 % command)
1196 % command)
1197 return False
1197 return False
1198 raise
1198 raise
1199
1199
1200 # back up original file descriptors
1200 # back up original file descriptors
1201 stdoutfd = os.dup(procutil.stdout.fileno())
1201 stdoutfd = os.dup(procutil.stdout.fileno())
1202 stderrfd = os.dup(procutil.stderr.fileno())
1202 stderrfd = os.dup(procutil.stderr.fileno())
1203
1203
1204 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1204 os.dup2(pager.stdin.fileno(), procutil.stdout.fileno())
1205 if self._isatty(procutil.stderr):
1205 if self._isatty(procutil.stderr):
1206 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1206 os.dup2(pager.stdin.fileno(), procutil.stderr.fileno())
1207
1207
1208 @self.atexit
1208 @self.atexit
1209 def killpager():
1209 def killpager():
1210 if util.safehasattr(signal, "SIGINT"):
1210 if util.safehasattr(signal, "SIGINT"):
1211 signal.signal(signal.SIGINT, signal.SIG_IGN)
1211 signal.signal(signal.SIGINT, signal.SIG_IGN)
1212 # restore original fds, closing pager.stdin copies in the process
1212 # restore original fds, closing pager.stdin copies in the process
1213 os.dup2(stdoutfd, procutil.stdout.fileno())
1213 os.dup2(stdoutfd, procutil.stdout.fileno())
1214 os.dup2(stderrfd, procutil.stderr.fileno())
1214 os.dup2(stderrfd, procutil.stderr.fileno())
1215 pager.stdin.close()
1215 pager.stdin.close()
1216 pager.wait()
1216 pager.wait()
1217
1217
1218 return True
1218 return True
1219
1219
1220 @property
1220 @property
1221 def _exithandlers(self):
1221 def _exithandlers(self):
1222 return _reqexithandlers
1222 return _reqexithandlers
1223
1223
1224 def atexit(self, func, *args, **kwargs):
1224 def atexit(self, func, *args, **kwargs):
1225 '''register a function to run after dispatching a request
1225 '''register a function to run after dispatching a request
1226
1226
1227 Handlers do not stay registered across request boundaries.'''
1227 Handlers do not stay registered across request boundaries.'''
1228 self._exithandlers.append((func, args, kwargs))
1228 self._exithandlers.append((func, args, kwargs))
1229 return func
1229 return func
1230
1230
1231 def interface(self, feature):
1231 def interface(self, feature):
1232 """what interface to use for interactive console features?
1232 """what interface to use for interactive console features?
1233
1233
1234 The interface is controlled by the value of `ui.interface` but also by
1234 The interface is controlled by the value of `ui.interface` but also by
1235 the value of feature-specific configuration. For example:
1235 the value of feature-specific configuration. For example:
1236
1236
1237 ui.interface.histedit = text
1237 ui.interface.histedit = text
1238 ui.interface.chunkselector = curses
1238 ui.interface.chunkselector = curses
1239
1239
1240 Here the features are "histedit" and "chunkselector".
1240 Here the features are "histedit" and "chunkselector".
1241
1241
1242 The configuration above means that the default interfaces for commands
1242 The configuration above means that the default interfaces for commands
1243 is curses, the interface for histedit is text and the interface for
1243 is curses, the interface for histedit is text and the interface for
1244 selecting chunk is crecord (the best curses interface available).
1244 selecting chunk is crecord (the best curses interface available).
1245
1245
1246 Consider the following example:
1246 Consider the following example:
1247 ui.interface = curses
1247 ui.interface = curses
1248 ui.interface.histedit = text
1248 ui.interface.histedit = text
1249
1249
1250 Then histedit will use the text interface and chunkselector will use
1250 Then histedit will use the text interface and chunkselector will use
1251 the default curses interface (crecord at the moment).
1251 the default curses interface (crecord at the moment).
1252 """
1252 """
1253 alldefaults = frozenset(["text", "curses"])
1253 alldefaults = frozenset(["text", "curses"])
1254
1254
1255 featureinterfaces = {
1255 featureinterfaces = {
1256 "chunkselector": [
1256 "chunkselector": [
1257 "text",
1257 "text",
1258 "curses",
1258 "curses",
1259 ],
1259 ],
1260 "histedit": [
1260 "histedit": [
1261 "text",
1261 "text",
1262 "curses",
1262 "curses",
1263 ],
1263 ],
1264 }
1264 }
1265
1265
1266 # Feature-specific interface
1266 # Feature-specific interface
1267 if feature not in featureinterfaces.keys():
1267 if feature not in featureinterfaces.keys():
1268 # Programming error, not user error
1268 # Programming error, not user error
1269 raise ValueError("Unknown feature requested %s" % feature)
1269 raise ValueError("Unknown feature requested %s" % feature)
1270
1270
1271 availableinterfaces = frozenset(featureinterfaces[feature])
1271 availableinterfaces = frozenset(featureinterfaces[feature])
1272 if alldefaults > availableinterfaces:
1272 if alldefaults > availableinterfaces:
1273 # Programming error, not user error. We need a use case to
1273 # Programming error, not user error. We need a use case to
1274 # define the right thing to do here.
1274 # define the right thing to do here.
1275 raise ValueError(
1275 raise ValueError(
1276 "Feature %s does not handle all default interfaces" %
1276 "Feature %s does not handle all default interfaces" %
1277 feature)
1277 feature)
1278
1278
1279 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1279 if self.plain() or encoding.environ.get('TERM') == 'dumb':
1280 return "text"
1280 return "text"
1281
1281
1282 # Default interface for all the features
1282 # Default interface for all the features
1283 defaultinterface = "text"
1283 defaultinterface = "text"
1284 i = self.config("ui", "interface")
1284 i = self.config("ui", "interface")
1285 if i in alldefaults:
1285 if i in alldefaults:
1286 defaultinterface = i
1286 defaultinterface = i
1287
1287
1288 choseninterface = defaultinterface
1288 choseninterface = defaultinterface
1289 f = self.config("ui", "interface.%s" % feature)
1289 f = self.config("ui", "interface.%s" % feature)
1290 if f in availableinterfaces:
1290 if f in availableinterfaces:
1291 choseninterface = f
1291 choseninterface = f
1292
1292
1293 if i is not None and defaultinterface != i:
1293 if i is not None and defaultinterface != i:
1294 if f is not None:
1294 if f is not None:
1295 self.warn(_("invalid value for ui.interface: %s\n") %
1295 self.warn(_("invalid value for ui.interface: %s\n") %
1296 (i,))
1296 (i,))
1297 else:
1297 else:
1298 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1298 self.warn(_("invalid value for ui.interface: %s (using %s)\n") %
1299 (i, choseninterface))
1299 (i, choseninterface))
1300 if f is not None and choseninterface != f:
1300 if f is not None and choseninterface != f:
1301 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1301 self.warn(_("invalid value for ui.interface.%s: %s (using %s)\n") %
1302 (feature, f, choseninterface))
1302 (feature, f, choseninterface))
1303
1303
1304 return choseninterface
1304 return choseninterface
1305
1305
1306 def interactive(self):
1306 def interactive(self):
1307 '''is interactive input allowed?
1307 '''is interactive input allowed?
1308
1308
1309 An interactive session is a session where input can be reasonably read
1309 An interactive session is a session where input can be reasonably read
1310 from `sys.stdin'. If this function returns false, any attempt to read
1310 from `sys.stdin'. If this function returns false, any attempt to read
1311 from stdin should fail with an error, unless a sensible default has been
1311 from stdin should fail with an error, unless a sensible default has been
1312 specified.
1312 specified.
1313
1313
1314 Interactiveness is triggered by the value of the `ui.interactive'
1314 Interactiveness is triggered by the value of the `ui.interactive'
1315 configuration variable or - if it is unset - when `sys.stdin' points
1315 configuration variable or - if it is unset - when `sys.stdin' points
1316 to a terminal device.
1316 to a terminal device.
1317
1317
1318 This function refers to input only; for output, see `ui.formatted()'.
1318 This function refers to input only; for output, see `ui.formatted()'.
1319 '''
1319 '''
1320 i = self.configbool("ui", "interactive")
1320 i = self.configbool("ui", "interactive")
1321 if i is None:
1321 if i is None:
1322 # some environments replace stdin without implementing isatty
1322 # some environments replace stdin without implementing isatty
1323 # usually those are non-interactive
1323 # usually those are non-interactive
1324 return self._isatty(self._fin)
1324 return self._isatty(self._fin)
1325
1325
1326 return i
1326 return i
1327
1327
1328 def termwidth(self):
1328 def termwidth(self):
1329 '''how wide is the terminal in columns?
1329 '''how wide is the terminal in columns?
1330 '''
1330 '''
1331 if 'COLUMNS' in encoding.environ:
1331 if 'COLUMNS' in encoding.environ:
1332 try:
1332 try:
1333 return int(encoding.environ['COLUMNS'])
1333 return int(encoding.environ['COLUMNS'])
1334 except ValueError:
1334 except ValueError:
1335 pass
1335 pass
1336 return scmutil.termsize(self)[0]
1336 return scmutil.termsize(self)[0]
1337
1337
1338 def formatted(self):
1338 def formatted(self):
1339 '''should formatted output be used?
1339 '''should formatted output be used?
1340
1340
1341 It is often desirable to format the output to suite the output medium.
1341 It is often desirable to format the output to suite the output medium.
1342 Examples of this are truncating long lines or colorizing messages.
1342 Examples of this are truncating long lines or colorizing messages.
1343 However, this is not often not desirable when piping output into other
1343 However, this is not often not desirable when piping output into other
1344 utilities, e.g. `grep'.
1344 utilities, e.g. `grep'.
1345
1345
1346 Formatted output is triggered by the value of the `ui.formatted'
1346 Formatted output is triggered by the value of the `ui.formatted'
1347 configuration variable or - if it is unset - when `sys.stdout' points
1347 configuration variable or - if it is unset - when `sys.stdout' points
1348 to a terminal device. Please note that `ui.formatted' should be
1348 to a terminal device. Please note that `ui.formatted' should be
1349 considered an implementation detail; it is not intended for use outside
1349 considered an implementation detail; it is not intended for use outside
1350 Mercurial or its extensions.
1350 Mercurial or its extensions.
1351
1351
1352 This function refers to output only; for input, see `ui.interactive()'.
1352 This function refers to output only; for input, see `ui.interactive()'.
1353 This function always returns false when in plain mode, see `ui.plain()'.
1353 This function always returns false when in plain mode, see `ui.plain()'.
1354 '''
1354 '''
1355 if self.plain():
1355 if self.plain():
1356 return False
1356 return False
1357
1357
1358 i = self.configbool("ui", "formatted")
1358 i = self.configbool("ui", "formatted")
1359 if i is None:
1359 if i is None:
1360 # some environments replace stdout without implementing isatty
1360 # some environments replace stdout without implementing isatty
1361 # usually those are non-interactive
1361 # usually those are non-interactive
1362 return self._isatty(self._fout)
1362 return self._isatty(self._fout)
1363
1363
1364 return i
1364 return i
1365
1365
1366 def _readline(self):
1366 def _readline(self):
1367 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1367 # Replacing stdin/stdout temporarily is a hard problem on Python 3
1368 # because they have to be text streams with *no buffering*. Instead,
1368 # because they have to be text streams with *no buffering*. Instead,
1369 # we use rawinput() only if call_readline() will be invoked by
1369 # we use rawinput() only if call_readline() will be invoked by
1370 # PyOS_Readline(), so no I/O will be made at Python layer.
1370 # PyOS_Readline(), so no I/O will be made at Python layer.
1371 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1371 usereadline = (self._isatty(self._fin) and self._isatty(self._fout)
1372 and procutil.isstdin(self._fin)
1372 and procutil.isstdin(self._fin)
1373 and procutil.isstdout(self._fout))
1373 and procutil.isstdout(self._fout))
1374 if usereadline:
1374 if usereadline:
1375 try:
1375 try:
1376 # magically add command line editing support, where
1376 # magically add command line editing support, where
1377 # available
1377 # available
1378 import readline
1378 import readline
1379 # force demandimport to really load the module
1379 # force demandimport to really load the module
1380 readline.read_history_file
1380 readline.read_history_file
1381 # windows sometimes raises something other than ImportError
1381 # windows sometimes raises something other than ImportError
1382 except Exception:
1382 except Exception:
1383 usereadline = False
1383 usereadline = False
1384
1384
1385 # prompt ' ' must exist; otherwise readline may delete entire line
1385 # prompt ' ' must exist; otherwise readline may delete entire line
1386 # - http://bugs.python.org/issue12833
1386 # - http://bugs.python.org/issue12833
1387 with self.timeblockedsection('stdio'):
1387 with self.timeblockedsection('stdio'):
1388 if usereadline:
1388 if usereadline:
1389 line = encoding.strtolocal(pycompat.rawinput(r' '))
1389 line = encoding.strtolocal(pycompat.rawinput(r' '))
1390 # When stdin is in binary mode on Windows, it can cause
1390 # When stdin is in binary mode on Windows, it can cause
1391 # raw_input() to emit an extra trailing carriage return
1391 # raw_input() to emit an extra trailing carriage return
1392 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1392 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
1393 line = line[:-1]
1393 line = line[:-1]
1394 else:
1394 else:
1395 self._fout.write(b' ')
1395 self._fout.write(b' ')
1396 self._fout.flush()
1396 self._fout.flush()
1397 line = self._fin.readline()
1397 line = self._fin.readline()
1398 if not line:
1398 if not line:
1399 raise EOFError
1399 raise EOFError
1400 line = line.rstrip(pycompat.oslinesep)
1400 line = line.rstrip(pycompat.oslinesep)
1401
1401
1402 return line
1402 return line
1403
1403
1404 def prompt(self, msg, default="y"):
1404 def prompt(self, msg, default="y"):
1405 """Prompt user with msg, read response.
1405 """Prompt user with msg, read response.
1406 If ui is not interactive, the default is returned.
1406 If ui is not interactive, the default is returned.
1407 """
1407 """
1408 return self._prompt(msg, default=default)
1408 return self._prompt(msg, default=default)
1409
1409
1410 def _prompt(self, msg, **opts):
1410 def _prompt(self, msg, **opts):
1411 default = opts[r'default']
1411 default = opts[r'default']
1412 if not self.interactive():
1412 if not self.interactive():
1413 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1413 self._writemsg(self._fmsgout, msg, ' ', type='prompt', **opts)
1414 self._writemsg(self._fmsgout, default or '', "\n",
1414 self._writemsg(self._fmsgout, default or '', "\n",
1415 type='promptecho')
1415 type='promptecho')
1416 return default
1416 return default
1417 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1417 self._writemsgnobuf(self._fmsgout, msg, type='prompt', **opts)
1418 self.flush()
1418 self.flush()
1419 try:
1419 try:
1420 r = self._readline()
1420 r = self._readline()
1421 if not r:
1421 if not r:
1422 r = default
1422 r = default
1423 if self.configbool('ui', 'promptecho'):
1423 if self.configbool('ui', 'promptecho'):
1424 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1424 self._writemsg(self._fmsgout, r, "\n", type='promptecho')
1425 return r
1425 return r
1426 except EOFError:
1426 except EOFError:
1427 raise error.ResponseExpected()
1427 raise error.ResponseExpected()
1428
1428
1429 @staticmethod
1429 @staticmethod
1430 def extractchoices(prompt):
1430 def extractchoices(prompt):
1431 """Extract prompt message and list of choices from specified prompt.
1431 """Extract prompt message and list of choices from specified prompt.
1432
1432
1433 This returns tuple "(message, choices)", and "choices" is the
1433 This returns tuple "(message, choices)", and "choices" is the
1434 list of tuple "(response character, text without &)".
1434 list of tuple "(response character, text without &)".
1435
1435
1436 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1436 >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
1437 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1437 ('awake? ', [('y', 'Yes'), ('n', 'No')])
1438 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1438 >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
1439 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1439 ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
1440 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1440 >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
1441 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1441 ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
1442 """
1442 """
1443
1443
1444 # Sadly, the prompt string may have been built with a filename
1444 # Sadly, the prompt string may have been built with a filename
1445 # containing "$$" so let's try to find the first valid-looking
1445 # containing "$$" so let's try to find the first valid-looking
1446 # prompt to start parsing. Sadly, we also can't rely on
1446 # prompt to start parsing. Sadly, we also can't rely on
1447 # choices containing spaces, ASCII, or basically anything
1447 # choices containing spaces, ASCII, or basically anything
1448 # except an ampersand followed by a character.
1448 # except an ampersand followed by a character.
1449 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1449 m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
1450 msg = m.group(1)
1450 msg = m.group(1)
1451 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1451 choices = [p.strip(' ') for p in m.group(2).split('$$')]
1452 def choicetuple(s):
1452 def choicetuple(s):
1453 ampidx = s.index('&')
1453 ampidx = s.index('&')
1454 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1454 return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
1455 return (msg, [choicetuple(s) for s in choices])
1455 return (msg, [choicetuple(s) for s in choices])
1456
1456
1457 def promptchoice(self, prompt, default=0):
1457 def promptchoice(self, prompt, default=0):
1458 """Prompt user with a message, read response, and ensure it matches
1458 """Prompt user with a message, read response, and ensure it matches
1459 one of the provided choices. The prompt is formatted as follows:
1459 one of the provided choices. The prompt is formatted as follows:
1460
1460
1461 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1461 "would you like fries with that (Yn)? $$ &Yes $$ &No"
1462
1462
1463 The index of the choice is returned. Responses are case
1463 The index of the choice is returned. Responses are case
1464 insensitive. If ui is not interactive, the default is
1464 insensitive. If ui is not interactive, the default is
1465 returned.
1465 returned.
1466 """
1466 """
1467
1467
1468 msg, choices = self.extractchoices(prompt)
1468 msg, choices = self.extractchoices(prompt)
1469 resps = [r for r, t in choices]
1469 resps = [r for r, t in choices]
1470 while True:
1470 while True:
1471 r = self._prompt(msg, default=resps[default], choices=choices)
1471 r = self._prompt(msg, default=resps[default], choices=choices)
1472 if r.lower() in resps:
1472 if r.lower() in resps:
1473 return resps.index(r.lower())
1473 return resps.index(r.lower())
1474 # TODO: shouldn't it be a warning?
1474 # TODO: shouldn't it be a warning?
1475 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1475 self._writemsg(self._fmsgout, _("unrecognized response\n"))
1476
1476
1477 def getpass(self, prompt=None, default=None):
1477 def getpass(self, prompt=None, default=None):
1478 if not self.interactive():
1478 if not self.interactive():
1479 return default
1479 return default
1480 try:
1480 try:
1481 self._writemsg(self._fmsgerr, prompt or _('password: '),
1481 self._writemsg(self._fmsgerr, prompt or _('password: '),
1482 type='prompt', password=True)
1482 type='prompt', password=True)
1483 # disable getpass() only if explicitly specified. it's still valid
1483 # disable getpass() only if explicitly specified. it's still valid
1484 # to interact with tty even if fin is not a tty.
1484 # to interact with tty even if fin is not a tty.
1485 with self.timeblockedsection('stdio'):
1485 with self.timeblockedsection('stdio'):
1486 if self.configbool('ui', 'nontty'):
1486 if self.configbool('ui', 'nontty'):
1487 l = self._fin.readline()
1487 l = self._fin.readline()
1488 if not l:
1488 if not l:
1489 raise EOFError
1489 raise EOFError
1490 return l.rstrip('\n')
1490 return l.rstrip('\n')
1491 else:
1491 else:
1492 return getpass.getpass('')
1492 return getpass.getpass('')
1493 except EOFError:
1493 except EOFError:
1494 raise error.ResponseExpected()
1494 raise error.ResponseExpected()
1495
1495
1496 def status(self, *msg, **opts):
1496 def status(self, *msg, **opts):
1497 '''write status message to output (if ui.quiet is False)
1497 '''write status message to output (if ui.quiet is False)
1498
1498
1499 This adds an output label of "ui.status".
1499 This adds an output label of "ui.status".
1500 '''
1500 '''
1501 if not self.quiet:
1501 if not self.quiet:
1502 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1502 self._writemsg(self._fmsgout, type='status', *msg, **opts)
1503
1503
1504 def warn(self, *msg, **opts):
1504 def warn(self, *msg, **opts):
1505 '''write warning message to output (stderr)
1505 '''write warning message to output (stderr)
1506
1506
1507 This adds an output label of "ui.warning".
1507 This adds an output label of "ui.warning".
1508 '''
1508 '''
1509 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1509 self._writemsg(self._fmsgerr, type='warning', *msg, **opts)
1510
1510
1511 def error(self, *msg, **opts):
1511 def error(self, *msg, **opts):
1512 '''write error message to output (stderr)
1512 '''write error message to output (stderr)
1513
1513
1514 This adds an output label of "ui.error".
1514 This adds an output label of "ui.error".
1515 '''
1515 '''
1516 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1516 self._writemsg(self._fmsgerr, type='error', *msg, **opts)
1517
1517
1518 def note(self, *msg, **opts):
1518 def note(self, *msg, **opts):
1519 '''write note to output (if ui.verbose is True)
1519 '''write note to output (if ui.verbose is True)
1520
1520
1521 This adds an output label of "ui.note".
1521 This adds an output label of "ui.note".
1522 '''
1522 '''
1523 if self.verbose:
1523 if self.verbose:
1524 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1524 self._writemsg(self._fmsgout, type='note', *msg, **opts)
1525
1525
1526 def debug(self, *msg, **opts):
1526 def debug(self, *msg, **opts):
1527 '''write debug message to output (if ui.debugflag is True)
1527 '''write debug message to output (if ui.debugflag is True)
1528
1528
1529 This adds an output label of "ui.debug".
1529 This adds an output label of "ui.debug".
1530 '''
1530 '''
1531 if self.debugflag:
1531 if self.debugflag:
1532 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1532 self._writemsg(self._fmsgout, type='debug', *msg, **opts)
1533 self.log(b'debug', b'%s', b''.join(msg))
1533 self.log(b'debug', b'%s', b''.join(msg))
1534
1534
1535 def edit(self, text, user, extra=None, editform=None, pending=None,
1535 def edit(self, text, user, extra=None, editform=None, pending=None,
1536 repopath=None, action=None):
1536 repopath=None, action=None):
1537 if action is None:
1537 if action is None:
1538 self.develwarn('action is None but will soon be a required '
1538 self.develwarn('action is None but will soon be a required '
1539 'parameter to ui.edit()')
1539 'parameter to ui.edit()')
1540 extra_defaults = {
1540 extra_defaults = {
1541 'prefix': 'editor',
1541 'prefix': 'editor',
1542 'suffix': '.txt',
1542 'suffix': '.txt',
1543 }
1543 }
1544 if extra is not None:
1544 if extra is not None:
1545 if extra.get('suffix') is not None:
1545 if extra.get('suffix') is not None:
1546 self.develwarn('extra.suffix is not None but will soon be '
1546 self.develwarn('extra.suffix is not None but will soon be '
1547 'ignored by ui.edit()')
1547 'ignored by ui.edit()')
1548 extra_defaults.update(extra)
1548 extra_defaults.update(extra)
1549 extra = extra_defaults
1549 extra = extra_defaults
1550
1550
1551 if action == 'diff':
1551 if action == 'diff':
1552 suffix = '.diff'
1552 suffix = '.diff'
1553 elif action:
1553 elif action:
1554 suffix = '.%s.hg.txt' % action
1554 suffix = '.%s.hg.txt' % action
1555 else:
1555 else:
1556 suffix = extra['suffix']
1556 suffix = extra['suffix']
1557
1557
1558 rdir = None
1558 rdir = None
1559 if self.configbool('experimental', 'editortmpinhg'):
1559 if self.configbool('experimental', 'editortmpinhg'):
1560 rdir = repopath
1560 rdir = repopath
1561 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1561 (fd, name) = pycompat.mkstemp(prefix='hg-' + extra['prefix'] + '-',
1562 suffix=suffix,
1562 suffix=suffix,
1563 dir=rdir)
1563 dir=rdir)
1564 try:
1564 try:
1565 f = os.fdopen(fd, r'wb')
1565 f = os.fdopen(fd, r'wb')
1566 f.write(util.tonativeeol(text))
1566 f.write(util.tonativeeol(text))
1567 f.close()
1567 f.close()
1568
1568
1569 environ = {'HGUSER': user}
1569 environ = {'HGUSER': user}
1570 if 'transplant_source' in extra:
1570 if 'transplant_source' in extra:
1571 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1571 environ.update({'HGREVISION': hex(extra['transplant_source'])})
1572 for label in ('intermediate-source', 'source', 'rebase_source'):
1572 for label in ('intermediate-source', 'source', 'rebase_source'):
1573 if label in extra:
1573 if label in extra:
1574 environ.update({'HGREVISION': extra[label]})
1574 environ.update({'HGREVISION': extra[label]})
1575 break
1575 break
1576 if editform:
1576 if editform:
1577 environ.update({'HGEDITFORM': editform})
1577 environ.update({'HGEDITFORM': editform})
1578 if pending:
1578 if pending:
1579 environ.update({'HG_PENDING': pending})
1579 environ.update({'HG_PENDING': pending})
1580
1580
1581 editor = self.geteditor()
1581 editor = self.geteditor()
1582
1582
1583 self.system("%s \"%s\"" % (editor, name),
1583 self.system("%s \"%s\"" % (editor, name),
1584 environ=environ,
1584 environ=environ,
1585 onerr=error.Abort, errprefix=_("edit failed"),
1585 onerr=error.Abort, errprefix=_("edit failed"),
1586 blockedtag='editor')
1586 blockedtag='editor')
1587
1587
1588 f = open(name, r'rb')
1588 f = open(name, r'rb')
1589 t = util.fromnativeeol(f.read())
1589 t = util.fromnativeeol(f.read())
1590 f.close()
1590 f.close()
1591 finally:
1591 finally:
1592 os.unlink(name)
1592 os.unlink(name)
1593
1593
1594 return t
1594 return t
1595
1595
1596 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1596 def system(self, cmd, environ=None, cwd=None, onerr=None, errprefix=None,
1597 blockedtag=None):
1597 blockedtag=None):
1598 '''execute shell command with appropriate output stream. command
1598 '''execute shell command with appropriate output stream. command
1599 output will be redirected if fout is not stdout.
1599 output will be redirected if fout is not stdout.
1600
1600
1601 if command fails and onerr is None, return status, else raise onerr
1601 if command fails and onerr is None, return status, else raise onerr
1602 object as exception.
1602 object as exception.
1603 '''
1603 '''
1604 if blockedtag is None:
1604 if blockedtag is None:
1605 # Long cmds tend to be because of an absolute path on cmd. Keep
1605 # Long cmds tend to be because of an absolute path on cmd. Keep
1606 # the tail end instead
1606 # the tail end instead
1607 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1607 cmdsuffix = cmd.translate(None, _keepalnum)[-85:]
1608 blockedtag = 'unknown_system_' + cmdsuffix
1608 blockedtag = 'unknown_system_' + cmdsuffix
1609 out = self._fout
1609 out = self._fout
1610 if any(s[1] for s in self._bufferstates):
1610 if any(s[1] for s in self._bufferstates):
1611 out = self
1611 out = self
1612 with self.timeblockedsection(blockedtag):
1612 with self.timeblockedsection(blockedtag):
1613 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1613 rc = self._runsystem(cmd, environ=environ, cwd=cwd, out=out)
1614 if rc and onerr:
1614 if rc and onerr:
1615 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1615 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
1616 procutil.explainexit(rc))
1616 procutil.explainexit(rc))
1617 if errprefix:
1617 if errprefix:
1618 errmsg = '%s: %s' % (errprefix, errmsg)
1618 errmsg = '%s: %s' % (errprefix, errmsg)
1619 raise onerr(errmsg)
1619 raise onerr(errmsg)
1620 return rc
1620 return rc
1621
1621
1622 def _runsystem(self, cmd, environ, cwd, out):
1622 def _runsystem(self, cmd, environ, cwd, out):
1623 """actually execute the given shell command (can be overridden by
1623 """actually execute the given shell command (can be overridden by
1624 extensions like chg)"""
1624 extensions like chg)"""
1625 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1625 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
1626
1626
1627 def traceback(self, exc=None, force=False):
1627 def traceback(self, exc=None, force=False):
1628 '''print exception traceback if traceback printing enabled or forced.
1628 '''print exception traceback if traceback printing enabled or forced.
1629 only to call in exception handler. returns true if traceback
1629 only to call in exception handler. returns true if traceback
1630 printed.'''
1630 printed.'''
1631 if self.tracebackflag or force:
1631 if self.tracebackflag or force:
1632 if exc is None:
1632 if exc is None:
1633 exc = sys.exc_info()
1633 exc = sys.exc_info()
1634 cause = getattr(exc[1], 'cause', None)
1634 cause = getattr(exc[1], 'cause', None)
1635
1635
1636 if cause is not None:
1636 if cause is not None:
1637 causetb = traceback.format_tb(cause[2])
1637 causetb = traceback.format_tb(cause[2])
1638 exctb = traceback.format_tb(exc[2])
1638 exctb = traceback.format_tb(exc[2])
1639 exconly = traceback.format_exception_only(cause[0], cause[1])
1639 exconly = traceback.format_exception_only(cause[0], cause[1])
1640
1640
1641 # exclude frame where 'exc' was chained and rethrown from exctb
1641 # exclude frame where 'exc' was chained and rethrown from exctb
1642 self.write_err('Traceback (most recent call last):\n',
1642 self.write_err('Traceback (most recent call last):\n',
1643 ''.join(exctb[:-1]),
1643 ''.join(exctb[:-1]),
1644 ''.join(causetb),
1644 ''.join(causetb),
1645 ''.join(exconly))
1645 ''.join(exconly))
1646 else:
1646 else:
1647 output = traceback.format_exception(exc[0], exc[1], exc[2])
1647 output = traceback.format_exception(exc[0], exc[1], exc[2])
1648 self.write_err(encoding.strtolocal(r''.join(output)))
1648 self.write_err(encoding.strtolocal(r''.join(output)))
1649 return self.tracebackflag or force
1649 return self.tracebackflag or force
1650
1650
1651 def geteditor(self):
1651 def geteditor(self):
1652 '''return editor to use'''
1652 '''return editor to use'''
1653 if pycompat.sysplatform == 'plan9':
1653 if pycompat.sysplatform == 'plan9':
1654 # vi is the MIPS instruction simulator on Plan 9. We
1654 # vi is the MIPS instruction simulator on Plan 9. We
1655 # instead default to E to plumb commit messages to
1655 # instead default to E to plumb commit messages to
1656 # avoid confusion.
1656 # avoid confusion.
1657 editor = 'E'
1657 editor = 'E'
1658 else:
1658 else:
1659 editor = 'vi'
1659 editor = 'vi'
1660 return (encoding.environ.get("HGEDITOR") or
1660 return (encoding.environ.get("HGEDITOR") or
1661 self.config("ui", "editor", editor))
1661 self.config("ui", "editor", editor))
1662
1662
1663 @util.propertycache
1663 @util.propertycache
1664 def _progbar(self):
1664 def _progbar(self):
1665 """setup the progbar singleton to the ui object"""
1665 """setup the progbar singleton to the ui object"""
1666 if (self.quiet or self.debugflag
1666 if (self.quiet or self.debugflag
1667 or self.configbool('progress', 'disable')
1667 or self.configbool('progress', 'disable')
1668 or not progress.shouldprint(self)):
1668 or not progress.shouldprint(self)):
1669 return None
1669 return None
1670 return getprogbar(self)
1670 return getprogbar(self)
1671
1671
1672 def _progclear(self):
1672 def _progclear(self):
1673 """clear progress bar output if any. use it before any output"""
1673 """clear progress bar output if any. use it before any output"""
1674 if not haveprogbar(): # nothing loaded yet
1674 if not haveprogbar(): # nothing loaded yet
1675 return
1675 return
1676 if self._progbar is not None and self._progbar.printed:
1676 if self._progbar is not None and self._progbar.printed:
1677 self._progbar.clear()
1677 self._progbar.clear()
1678
1678
1679 def progress(self, topic, pos, item="", unit="", total=None):
1679 def progress(self, topic, pos, item="", unit="", total=None):
1680 '''show a progress message
1680 '''show a progress message
1681
1681
1682 By default a textual progress bar will be displayed if an operation
1682 By default a textual progress bar will be displayed if an operation
1683 takes too long. 'topic' is the current operation, 'item' is a
1683 takes too long. 'topic' is the current operation, 'item' is a
1684 non-numeric marker of the current position (i.e. the currently
1684 non-numeric marker of the current position (i.e. the currently
1685 in-process file), 'pos' is the current numeric position (i.e.
1685 in-process file), 'pos' is the current numeric position (i.e.
1686 revision, bytes, etc.), unit is a corresponding unit label,
1686 revision, bytes, etc.), unit is a corresponding unit label,
1687 and total is the highest expected pos.
1687 and total is the highest expected pos.
1688
1688
1689 Multiple nested topics may be active at a time.
1689 Multiple nested topics may be active at a time.
1690
1690
1691 All topics should be marked closed by setting pos to None at
1691 All topics should be marked closed by setting pos to None at
1692 termination.
1692 termination.
1693 '''
1693 '''
1694 if getattr(self._fmsgerr, 'structured', False):
1694 if getattr(self._fmsgerr, 'structured', False):
1695 # channel for machine-readable output with metadata, just send
1695 # channel for machine-readable output with metadata, just send
1696 # raw information
1696 # raw information
1697 # TODO: consider porting some useful information (e.g. estimated
1697 # TODO: consider porting some useful information (e.g. estimated
1698 # time) from progbar. we might want to support update delay to
1698 # time) from progbar. we might want to support update delay to
1699 # reduce the cost of transferring progress messages.
1699 # reduce the cost of transferring progress messages.
1700 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1700 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1701 item=item, unit=unit, total=total)
1701 item=item, unit=unit, total=total)
1702 elif self._progbar is not None:
1702 elif self._progbar is not None:
1703 self._progbar.progress(topic, pos, item=item, unit=unit,
1703 self._progbar.progress(topic, pos, item=item, unit=unit,
1704 total=total)
1704 total=total)
1705
1706 # Looking up progress.debug in tight loops is expensive. The value
1707 # is cached on the progbar object and we can avoid the lookup in
1708 # the common case where a progbar is active.
1709 if pos is None or not self._progbar.debug:
1710 return
1711
1712 # Keep this logic in sync with above.
1705 if pos is None or not self.configbool('progress', 'debug'):
1713 if pos is None or not self.configbool('progress', 'debug'):
1706 return
1714 return
1707
1715
1708 if unit:
1716 if unit:
1709 unit = ' ' + unit
1717 unit = ' ' + unit
1710 if item:
1718 if item:
1711 item = ' ' + item
1719 item = ' ' + item
1712
1720
1713 if total:
1721 if total:
1714 pct = 100.0 * pos / total
1722 pct = 100.0 * pos / total
1715 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1723 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1716 % (topic, item, pos, total, unit, pct))
1724 % (topic, item, pos, total, unit, pct))
1717 else:
1725 else:
1718 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1726 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1719
1727
1720 def makeprogress(self, topic, unit="", total=None):
1728 def makeprogress(self, topic, unit="", total=None):
1721 '''exists only so low-level modules won't need to import scmutil'''
1729 '''exists only so low-level modules won't need to import scmutil'''
1722 return scmutil.progress(self, topic, unit, total)
1730 return scmutil.progress(self, topic, unit, total)
1723
1731
1724 def getlogger(self, name):
1732 def getlogger(self, name):
1725 """Returns a logger of the given name; or None if not registered"""
1733 """Returns a logger of the given name; or None if not registered"""
1726 return self._loggers.get(name)
1734 return self._loggers.get(name)
1727
1735
1728 def setlogger(self, name, logger):
1736 def setlogger(self, name, logger):
1729 """Install logger which can be identified later by the given name
1737 """Install logger which can be identified later by the given name
1730
1738
1731 More than one loggers can be registered. Use extension or module
1739 More than one loggers can be registered. Use extension or module
1732 name to uniquely identify the logger instance.
1740 name to uniquely identify the logger instance.
1733 """
1741 """
1734 self._loggers[name] = logger
1742 self._loggers[name] = logger
1735
1743
1736 def log(self, event, msgfmt, *msgargs, **opts):
1744 def log(self, event, msgfmt, *msgargs, **opts):
1737 '''hook for logging facility extensions
1745 '''hook for logging facility extensions
1738
1746
1739 event should be a readily-identifiable subsystem, which will
1747 event should be a readily-identifiable subsystem, which will
1740 allow filtering.
1748 allow filtering.
1741
1749
1742 msgfmt should be a newline-terminated format string to log, and
1750 msgfmt should be a newline-terminated format string to log, and
1743 *msgargs are %-formatted into it.
1751 *msgargs are %-formatted into it.
1744
1752
1745 **opts currently has no defined meanings.
1753 **opts currently has no defined meanings.
1746 '''
1754 '''
1747 if not self._loggers:
1755 if not self._loggers:
1748 return
1756 return
1749 activeloggers = [l for l in self._loggers.itervalues()
1757 activeloggers = [l for l in self._loggers.itervalues()
1750 if l.tracked(event)]
1758 if l.tracked(event)]
1751 if not activeloggers:
1759 if not activeloggers:
1752 return
1760 return
1753 msg = msgfmt % msgargs
1761 msg = msgfmt % msgargs
1754 opts = pycompat.byteskwargs(opts)
1762 opts = pycompat.byteskwargs(opts)
1755 # guard against recursion from e.g. ui.debug()
1763 # guard against recursion from e.g. ui.debug()
1756 registeredloggers = self._loggers
1764 registeredloggers = self._loggers
1757 self._loggers = {}
1765 self._loggers = {}
1758 try:
1766 try:
1759 for logger in activeloggers:
1767 for logger in activeloggers:
1760 logger.log(self, event, msg, opts)
1768 logger.log(self, event, msg, opts)
1761 finally:
1769 finally:
1762 self._loggers = registeredloggers
1770 self._loggers = registeredloggers
1763
1771
1764 def label(self, msg, label):
1772 def label(self, msg, label):
1765 '''style msg based on supplied label
1773 '''style msg based on supplied label
1766
1774
1767 If some color mode is enabled, this will add the necessary control
1775 If some color mode is enabled, this will add the necessary control
1768 characters to apply such color. In addition, 'debug' color mode adds
1776 characters to apply such color. In addition, 'debug' color mode adds
1769 markup showing which label affects a piece of text.
1777 markup showing which label affects a piece of text.
1770
1778
1771 ui.write(s, 'label') is equivalent to
1779 ui.write(s, 'label') is equivalent to
1772 ui.write(ui.label(s, 'label')).
1780 ui.write(ui.label(s, 'label')).
1773 '''
1781 '''
1774 if self._colormode is not None:
1782 if self._colormode is not None:
1775 return color.colorlabel(self, msg, label)
1783 return color.colorlabel(self, msg, label)
1776 return msg
1784 return msg
1777
1785
1778 def develwarn(self, msg, stacklevel=1, config=None):
1786 def develwarn(self, msg, stacklevel=1, config=None):
1779 """issue a developer warning message
1787 """issue a developer warning message
1780
1788
1781 Use 'stacklevel' to report the offender some layers further up in the
1789 Use 'stacklevel' to report the offender some layers further up in the
1782 stack.
1790 stack.
1783 """
1791 """
1784 if not self.configbool('devel', 'all-warnings'):
1792 if not self.configbool('devel', 'all-warnings'):
1785 if config is None or not self.configbool('devel', config):
1793 if config is None or not self.configbool('devel', config):
1786 return
1794 return
1787 msg = 'devel-warn: ' + msg
1795 msg = 'devel-warn: ' + msg
1788 stacklevel += 1 # get in develwarn
1796 stacklevel += 1 # get in develwarn
1789 if self.tracebackflag:
1797 if self.tracebackflag:
1790 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1798 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1791 self.log('develwarn', '%s at:\n%s' %
1799 self.log('develwarn', '%s at:\n%s' %
1792 (msg, ''.join(util.getstackframes(stacklevel))))
1800 (msg, ''.join(util.getstackframes(stacklevel))))
1793 else:
1801 else:
1794 curframe = inspect.currentframe()
1802 curframe = inspect.currentframe()
1795 calframe = inspect.getouterframes(curframe, 2)
1803 calframe = inspect.getouterframes(curframe, 2)
1796 fname, lineno, fmsg = calframe[stacklevel][1:4]
1804 fname, lineno, fmsg = calframe[stacklevel][1:4]
1797 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1805 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1798 self.write_err('%s at: %s:%d (%s)\n'
1806 self.write_err('%s at: %s:%d (%s)\n'
1799 % (msg, fname, lineno, fmsg))
1807 % (msg, fname, lineno, fmsg))
1800 self.log('develwarn', '%s at: %s:%d (%s)\n',
1808 self.log('develwarn', '%s at: %s:%d (%s)\n',
1801 msg, fname, lineno, fmsg)
1809 msg, fname, lineno, fmsg)
1802 curframe = calframe = None # avoid cycles
1810 curframe = calframe = None # avoid cycles
1803
1811
1804 def deprecwarn(self, msg, version, stacklevel=2):
1812 def deprecwarn(self, msg, version, stacklevel=2):
1805 """issue a deprecation warning
1813 """issue a deprecation warning
1806
1814
1807 - msg: message explaining what is deprecated and how to upgrade,
1815 - msg: message explaining what is deprecated and how to upgrade,
1808 - version: last version where the API will be supported,
1816 - version: last version where the API will be supported,
1809 """
1817 """
1810 if not (self.configbool('devel', 'all-warnings')
1818 if not (self.configbool('devel', 'all-warnings')
1811 or self.configbool('devel', 'deprec-warn')):
1819 or self.configbool('devel', 'deprec-warn')):
1812 return
1820 return
1813 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1821 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1814 " update your code.)") % version
1822 " update your code.)") % version
1815 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1823 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1816
1824
1817 def exportableenviron(self):
1825 def exportableenviron(self):
1818 """The environment variables that are safe to export, e.g. through
1826 """The environment variables that are safe to export, e.g. through
1819 hgweb.
1827 hgweb.
1820 """
1828 """
1821 return self._exportableenviron
1829 return self._exportableenviron
1822
1830
1823 @contextlib.contextmanager
1831 @contextlib.contextmanager
1824 def configoverride(self, overrides, source=""):
1832 def configoverride(self, overrides, source=""):
1825 """Context manager for temporary config overrides
1833 """Context manager for temporary config overrides
1826 `overrides` must be a dict of the following structure:
1834 `overrides` must be a dict of the following structure:
1827 {(section, name) : value}"""
1835 {(section, name) : value}"""
1828 backups = {}
1836 backups = {}
1829 try:
1837 try:
1830 for (section, name), value in overrides.items():
1838 for (section, name), value in overrides.items():
1831 backups[(section, name)] = self.backupconfig(section, name)
1839 backups[(section, name)] = self.backupconfig(section, name)
1832 self.setconfig(section, name, value, source)
1840 self.setconfig(section, name, value, source)
1833 yield
1841 yield
1834 finally:
1842 finally:
1835 for __, backup in backups.items():
1843 for __, backup in backups.items():
1836 self.restoreconfig(backup)
1844 self.restoreconfig(backup)
1837 # just restoring ui.quiet config to the previous value is not enough
1845 # just restoring ui.quiet config to the previous value is not enough
1838 # as it does not update ui.quiet class member
1846 # as it does not update ui.quiet class member
1839 if ('ui', 'quiet') in overrides:
1847 if ('ui', 'quiet') in overrides:
1840 self.fixconfig(section='ui')
1848 self.fixconfig(section='ui')
1841
1849
1842 class paths(dict):
1850 class paths(dict):
1843 """Represents a collection of paths and their configs.
1851 """Represents a collection of paths and their configs.
1844
1852
1845 Data is initially derived from ui instances and the config files they have
1853 Data is initially derived from ui instances and the config files they have
1846 loaded.
1854 loaded.
1847 """
1855 """
1848 def __init__(self, ui):
1856 def __init__(self, ui):
1849 dict.__init__(self)
1857 dict.__init__(self)
1850
1858
1851 for name, loc in ui.configitems('paths', ignoresub=True):
1859 for name, loc in ui.configitems('paths', ignoresub=True):
1852 # No location is the same as not existing.
1860 # No location is the same as not existing.
1853 if not loc:
1861 if not loc:
1854 continue
1862 continue
1855 loc, sub = ui.configsuboptions('paths', name)
1863 loc, sub = ui.configsuboptions('paths', name)
1856 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1864 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1857
1865
1858 def getpath(self, name, default=None):
1866 def getpath(self, name, default=None):
1859 """Return a ``path`` from a string, falling back to default.
1867 """Return a ``path`` from a string, falling back to default.
1860
1868
1861 ``name`` can be a named path or locations. Locations are filesystem
1869 ``name`` can be a named path or locations. Locations are filesystem
1862 paths or URIs.
1870 paths or URIs.
1863
1871
1864 Returns None if ``name`` is not a registered path, a URI, or a local
1872 Returns None if ``name`` is not a registered path, a URI, or a local
1865 path to a repo.
1873 path to a repo.
1866 """
1874 """
1867 # Only fall back to default if no path was requested.
1875 # Only fall back to default if no path was requested.
1868 if name is None:
1876 if name is None:
1869 if not default:
1877 if not default:
1870 default = ()
1878 default = ()
1871 elif not isinstance(default, (tuple, list)):
1879 elif not isinstance(default, (tuple, list)):
1872 default = (default,)
1880 default = (default,)
1873 for k in default:
1881 for k in default:
1874 try:
1882 try:
1875 return self[k]
1883 return self[k]
1876 except KeyError:
1884 except KeyError:
1877 continue
1885 continue
1878 return None
1886 return None
1879
1887
1880 # Most likely empty string.
1888 # Most likely empty string.
1881 # This may need to raise in the future.
1889 # This may need to raise in the future.
1882 if not name:
1890 if not name:
1883 return None
1891 return None
1884
1892
1885 try:
1893 try:
1886 return self[name]
1894 return self[name]
1887 except KeyError:
1895 except KeyError:
1888 # Try to resolve as a local path or URI.
1896 # Try to resolve as a local path or URI.
1889 try:
1897 try:
1890 # We don't pass sub-options in, so no need to pass ui instance.
1898 # We don't pass sub-options in, so no need to pass ui instance.
1891 return path(None, None, rawloc=name)
1899 return path(None, None, rawloc=name)
1892 except ValueError:
1900 except ValueError:
1893 raise error.RepoError(_('repository %s does not exist') %
1901 raise error.RepoError(_('repository %s does not exist') %
1894 name)
1902 name)
1895
1903
1896 _pathsuboptions = {}
1904 _pathsuboptions = {}
1897
1905
1898 def pathsuboption(option, attr):
1906 def pathsuboption(option, attr):
1899 """Decorator used to declare a path sub-option.
1907 """Decorator used to declare a path sub-option.
1900
1908
1901 Arguments are the sub-option name and the attribute it should set on
1909 Arguments are the sub-option name and the attribute it should set on
1902 ``path`` instances.
1910 ``path`` instances.
1903
1911
1904 The decorated function will receive as arguments a ``ui`` instance,
1912 The decorated function will receive as arguments a ``ui`` instance,
1905 ``path`` instance, and the string value of this option from the config.
1913 ``path`` instance, and the string value of this option from the config.
1906 The function should return the value that will be set on the ``path``
1914 The function should return the value that will be set on the ``path``
1907 instance.
1915 instance.
1908
1916
1909 This decorator can be used to perform additional verification of
1917 This decorator can be used to perform additional verification of
1910 sub-options and to change the type of sub-options.
1918 sub-options and to change the type of sub-options.
1911 """
1919 """
1912 def register(func):
1920 def register(func):
1913 _pathsuboptions[option] = (attr, func)
1921 _pathsuboptions[option] = (attr, func)
1914 return func
1922 return func
1915 return register
1923 return register
1916
1924
1917 @pathsuboption('pushurl', 'pushloc')
1925 @pathsuboption('pushurl', 'pushloc')
1918 def pushurlpathoption(ui, path, value):
1926 def pushurlpathoption(ui, path, value):
1919 u = util.url(value)
1927 u = util.url(value)
1920 # Actually require a URL.
1928 # Actually require a URL.
1921 if not u.scheme:
1929 if not u.scheme:
1922 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1930 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1923 return None
1931 return None
1924
1932
1925 # Don't support the #foo syntax in the push URL to declare branch to
1933 # Don't support the #foo syntax in the push URL to declare branch to
1926 # push.
1934 # push.
1927 if u.fragment:
1935 if u.fragment:
1928 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1936 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1929 'ignoring)\n') % path.name)
1937 'ignoring)\n') % path.name)
1930 u.fragment = None
1938 u.fragment = None
1931
1939
1932 return bytes(u)
1940 return bytes(u)
1933
1941
1934 @pathsuboption('pushrev', 'pushrev')
1942 @pathsuboption('pushrev', 'pushrev')
1935 def pushrevpathoption(ui, path, value):
1943 def pushrevpathoption(ui, path, value):
1936 return value
1944 return value
1937
1945
1938 class path(object):
1946 class path(object):
1939 """Represents an individual path and its configuration."""
1947 """Represents an individual path and its configuration."""
1940
1948
1941 def __init__(self, ui, name, rawloc=None, suboptions=None):
1949 def __init__(self, ui, name, rawloc=None, suboptions=None):
1942 """Construct a path from its config options.
1950 """Construct a path from its config options.
1943
1951
1944 ``ui`` is the ``ui`` instance the path is coming from.
1952 ``ui`` is the ``ui`` instance the path is coming from.
1945 ``name`` is the symbolic name of the path.
1953 ``name`` is the symbolic name of the path.
1946 ``rawloc`` is the raw location, as defined in the config.
1954 ``rawloc`` is the raw location, as defined in the config.
1947 ``pushloc`` is the raw locations pushes should be made to.
1955 ``pushloc`` is the raw locations pushes should be made to.
1948
1956
1949 If ``name`` is not defined, we require that the location be a) a local
1957 If ``name`` is not defined, we require that the location be a) a local
1950 filesystem path with a .hg directory or b) a URL. If not,
1958 filesystem path with a .hg directory or b) a URL. If not,
1951 ``ValueError`` is raised.
1959 ``ValueError`` is raised.
1952 """
1960 """
1953 if not rawloc:
1961 if not rawloc:
1954 raise ValueError('rawloc must be defined')
1962 raise ValueError('rawloc must be defined')
1955
1963
1956 # Locations may define branches via syntax <base>#<branch>.
1964 # Locations may define branches via syntax <base>#<branch>.
1957 u = util.url(rawloc)
1965 u = util.url(rawloc)
1958 branch = None
1966 branch = None
1959 if u.fragment:
1967 if u.fragment:
1960 branch = u.fragment
1968 branch = u.fragment
1961 u.fragment = None
1969 u.fragment = None
1962
1970
1963 self.url = u
1971 self.url = u
1964 self.branch = branch
1972 self.branch = branch
1965
1973
1966 self.name = name
1974 self.name = name
1967 self.rawloc = rawloc
1975 self.rawloc = rawloc
1968 self.loc = '%s' % u
1976 self.loc = '%s' % u
1969
1977
1970 # When given a raw location but not a symbolic name, validate the
1978 # When given a raw location but not a symbolic name, validate the
1971 # location is valid.
1979 # location is valid.
1972 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1980 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1973 raise ValueError('location is not a URL or path to a local '
1981 raise ValueError('location is not a URL or path to a local '
1974 'repo: %s' % rawloc)
1982 'repo: %s' % rawloc)
1975
1983
1976 suboptions = suboptions or {}
1984 suboptions = suboptions or {}
1977
1985
1978 # Now process the sub-options. If a sub-option is registered, its
1986 # Now process the sub-options. If a sub-option is registered, its
1979 # attribute will always be present. The value will be None if there
1987 # attribute will always be present. The value will be None if there
1980 # was no valid sub-option.
1988 # was no valid sub-option.
1981 for suboption, (attr, func) in _pathsuboptions.iteritems():
1989 for suboption, (attr, func) in _pathsuboptions.iteritems():
1982 if suboption not in suboptions:
1990 if suboption not in suboptions:
1983 setattr(self, attr, None)
1991 setattr(self, attr, None)
1984 continue
1992 continue
1985
1993
1986 value = func(ui, self, suboptions[suboption])
1994 value = func(ui, self, suboptions[suboption])
1987 setattr(self, attr, value)
1995 setattr(self, attr, value)
1988
1996
1989 def _isvalidlocalpath(self, path):
1997 def _isvalidlocalpath(self, path):
1990 """Returns True if the given path is a potentially valid repository.
1998 """Returns True if the given path is a potentially valid repository.
1991 This is its own function so that extensions can change the definition of
1999 This is its own function so that extensions can change the definition of
1992 'valid' in this case (like when pulling from a git repo into a hg
2000 'valid' in this case (like when pulling from a git repo into a hg
1993 one)."""
2001 one)."""
1994 return os.path.isdir(os.path.join(path, '.hg'))
2002 return os.path.isdir(os.path.join(path, '.hg'))
1995
2003
1996 @property
2004 @property
1997 def suboptions(self):
2005 def suboptions(self):
1998 """Return sub-options and their values for this path.
2006 """Return sub-options and their values for this path.
1999
2007
2000 This is intended to be used for presentation purposes.
2008 This is intended to be used for presentation purposes.
2001 """
2009 """
2002 d = {}
2010 d = {}
2003 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2011 for subopt, (attr, _func) in _pathsuboptions.iteritems():
2004 value = getattr(self, attr)
2012 value = getattr(self, attr)
2005 if value is not None:
2013 if value is not None:
2006 d[subopt] = value
2014 d[subopt] = value
2007 return d
2015 return d
2008
2016
2009 # we instantiate one globally shared progress bar to avoid
2017 # we instantiate one globally shared progress bar to avoid
2010 # competing progress bars when multiple UI objects get created
2018 # competing progress bars when multiple UI objects get created
2011 _progresssingleton = None
2019 _progresssingleton = None
2012
2020
2013 def getprogbar(ui):
2021 def getprogbar(ui):
2014 global _progresssingleton
2022 global _progresssingleton
2015 if _progresssingleton is None:
2023 if _progresssingleton is None:
2016 # passing 'ui' object to the singleton is fishy,
2024 # passing 'ui' object to the singleton is fishy,
2017 # this is how the extension used to work but feel free to rework it.
2025 # this is how the extension used to work but feel free to rework it.
2018 _progresssingleton = progress.progbar(ui)
2026 _progresssingleton = progress.progbar(ui)
2019 return _progresssingleton
2027 return _progresssingleton
2020
2028
2021 def haveprogbar():
2029 def haveprogbar():
2022 return _progresssingleton is not None
2030 return _progresssingleton is not None
2023
2031
2024 def _selectmsgdests(ui):
2032 def _selectmsgdests(ui):
2025 name = ui.config(b'ui', b'message-output')
2033 name = ui.config(b'ui', b'message-output')
2026 if name == b'channel':
2034 if name == b'channel':
2027 if ui.fmsg:
2035 if ui.fmsg:
2028 return ui.fmsg, ui.fmsg
2036 return ui.fmsg, ui.fmsg
2029 else:
2037 else:
2030 # fall back to ferr if channel isn't ready so that status/error
2038 # fall back to ferr if channel isn't ready so that status/error
2031 # messages can be printed
2039 # messages can be printed
2032 return ui.ferr, ui.ferr
2040 return ui.ferr, ui.ferr
2033 if name == b'stdio':
2041 if name == b'stdio':
2034 return ui.fout, ui.ferr
2042 return ui.fout, ui.ferr
2035 if name == b'stderr':
2043 if name == b'stderr':
2036 return ui.ferr, ui.ferr
2044 return ui.ferr, ui.ferr
2037 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2045 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
2038
2046
2039 def _writemsgwith(write, dest, *args, **opts):
2047 def _writemsgwith(write, dest, *args, **opts):
2040 """Write ui message with the given ui._write*() function
2048 """Write ui message with the given ui._write*() function
2041
2049
2042 The specified message type is translated to 'ui.<type>' label if the dest
2050 The specified message type is translated to 'ui.<type>' label if the dest
2043 isn't a structured channel, so that the message will be colorized.
2051 isn't a structured channel, so that the message will be colorized.
2044 """
2052 """
2045 # TODO: maybe change 'type' to a mandatory option
2053 # TODO: maybe change 'type' to a mandatory option
2046 if r'type' in opts and not getattr(dest, 'structured', False):
2054 if r'type' in opts and not getattr(dest, 'structured', False):
2047 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2055 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
2048 write(dest, *args, **opts)
2056 write(dest, *args, **opts)
General Comments 0
You need to be logged in to leave comments. Login now