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