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