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