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