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