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