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