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