##// END OF EJS Templates
commandserver: send raw progress information to message channel...
Yuya Nishihara -
r40630:234c2d8c default
parent child Browse files
Show More
@@ -1,591 +1,592 b''
1 # commandserver.py - communicate with Mercurial's API over a pipe
1 # commandserver.py - communicate with Mercurial's API over a pipe
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright 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 errno
10 import errno
11 import gc
11 import gc
12 import os
12 import os
13 import random
13 import random
14 import signal
14 import signal
15 import socket
15 import socket
16 import struct
16 import struct
17 import traceback
17 import traceback
18
18
19 try:
19 try:
20 import selectors
20 import selectors
21 selectors.BaseSelector
21 selectors.BaseSelector
22 except ImportError:
22 except ImportError:
23 from .thirdparty import selectors2 as selectors
23 from .thirdparty import selectors2 as selectors
24
24
25 from .i18n import _
25 from .i18n import _
26 from . import (
26 from . import (
27 encoding,
27 encoding,
28 error,
28 error,
29 pycompat,
29 pycompat,
30 util,
30 util,
31 )
31 )
32 from .utils import (
32 from .utils import (
33 cborutil,
33 cborutil,
34 procutil,
34 procutil,
35 )
35 )
36
36
37 logfile = None
37 logfile = None
38
38
39 def log(*args):
39 def log(*args):
40 if not logfile:
40 if not logfile:
41 return
41 return
42
42
43 for a in args:
43 for a in args:
44 logfile.write(str(a))
44 logfile.write(str(a))
45
45
46 logfile.flush()
46 logfile.flush()
47
47
48 class channeledoutput(object):
48 class channeledoutput(object):
49 """
49 """
50 Write data to out in the following format:
50 Write data to out in the following format:
51
51
52 data length (unsigned int),
52 data length (unsigned int),
53 data
53 data
54 """
54 """
55 def __init__(self, out, channel):
55 def __init__(self, out, channel):
56 self.out = out
56 self.out = out
57 self.channel = channel
57 self.channel = channel
58
58
59 @property
59 @property
60 def name(self):
60 def name(self):
61 return '<%c-channel>' % self.channel
61 return '<%c-channel>' % self.channel
62
62
63 def write(self, data):
63 def write(self, data):
64 if not data:
64 if not data:
65 return
65 return
66 # single write() to guarantee the same atomicity as the underlying file
66 # single write() to guarantee the same atomicity as the underlying file
67 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
67 self.out.write(struct.pack('>cI', self.channel, len(data)) + data)
68 self.out.flush()
68 self.out.flush()
69
69
70 def __getattr__(self, attr):
70 def __getattr__(self, attr):
71 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
71 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
72 raise AttributeError(attr)
72 raise AttributeError(attr)
73 return getattr(self.out, attr)
73 return getattr(self.out, attr)
74
74
75 class channeledmessage(object):
75 class channeledmessage(object):
76 """
76 """
77 Write encoded message and metadata to out in the following format:
77 Write encoded message and metadata to out in the following format:
78
78
79 data length (unsigned int),
79 data length (unsigned int),
80 encoded message and metadata, as a flat key-value dict.
80 encoded message and metadata, as a flat key-value dict.
81
81
82 Each message should have 'type' attribute. Messages of unknown type
82 Each message should have 'type' attribute. Messages of unknown type
83 should be ignored.
83 should be ignored.
84 """
84 """
85
85
86 # teach ui that write() can take **opts
86 # teach ui that write() can take **opts
87 structured = True
87 structured = True
88
88
89 def __init__(self, out, channel, encodename, encodefn):
89 def __init__(self, out, channel, encodename, encodefn):
90 self._cout = channeledoutput(out, channel)
90 self._cout = channeledoutput(out, channel)
91 self.encoding = encodename
91 self.encoding = encodename
92 self._encodefn = encodefn
92 self._encodefn = encodefn
93
93
94 def write(self, data, **opts):
94 def write(self, data, **opts):
95 opts = pycompat.byteskwargs(opts)
95 opts = pycompat.byteskwargs(opts)
96 opts[b'data'] = data
96 if data is not None:
97 opts[b'data'] = data
97 self._cout.write(self._encodefn(opts))
98 self._cout.write(self._encodefn(opts))
98
99
99 def __getattr__(self, attr):
100 def __getattr__(self, attr):
100 return getattr(self._cout, attr)
101 return getattr(self._cout, attr)
101
102
102 class channeledinput(object):
103 class channeledinput(object):
103 """
104 """
104 Read data from in_.
105 Read data from in_.
105
106
106 Requests for input are written to out in the following format:
107 Requests for input are written to out in the following format:
107 channel identifier - 'I' for plain input, 'L' line based (1 byte)
108 channel identifier - 'I' for plain input, 'L' line based (1 byte)
108 how many bytes to send at most (unsigned int),
109 how many bytes to send at most (unsigned int),
109
110
110 The client replies with:
111 The client replies with:
111 data length (unsigned int), 0 meaning EOF
112 data length (unsigned int), 0 meaning EOF
112 data
113 data
113 """
114 """
114
115
115 maxchunksize = 4 * 1024
116 maxchunksize = 4 * 1024
116
117
117 def __init__(self, in_, out, channel):
118 def __init__(self, in_, out, channel):
118 self.in_ = in_
119 self.in_ = in_
119 self.out = out
120 self.out = out
120 self.channel = channel
121 self.channel = channel
121
122
122 @property
123 @property
123 def name(self):
124 def name(self):
124 return '<%c-channel>' % self.channel
125 return '<%c-channel>' % self.channel
125
126
126 def read(self, size=-1):
127 def read(self, size=-1):
127 if size < 0:
128 if size < 0:
128 # if we need to consume all the clients input, ask for 4k chunks
129 # if we need to consume all the clients input, ask for 4k chunks
129 # so the pipe doesn't fill up risking a deadlock
130 # so the pipe doesn't fill up risking a deadlock
130 size = self.maxchunksize
131 size = self.maxchunksize
131 s = self._read(size, self.channel)
132 s = self._read(size, self.channel)
132 buf = s
133 buf = s
133 while s:
134 while s:
134 s = self._read(size, self.channel)
135 s = self._read(size, self.channel)
135 buf += s
136 buf += s
136
137
137 return buf
138 return buf
138 else:
139 else:
139 return self._read(size, self.channel)
140 return self._read(size, self.channel)
140
141
141 def _read(self, size, channel):
142 def _read(self, size, channel):
142 if not size:
143 if not size:
143 return ''
144 return ''
144 assert size > 0
145 assert size > 0
145
146
146 # tell the client we need at most size bytes
147 # tell the client we need at most size bytes
147 self.out.write(struct.pack('>cI', channel, size))
148 self.out.write(struct.pack('>cI', channel, size))
148 self.out.flush()
149 self.out.flush()
149
150
150 length = self.in_.read(4)
151 length = self.in_.read(4)
151 length = struct.unpack('>I', length)[0]
152 length = struct.unpack('>I', length)[0]
152 if not length:
153 if not length:
153 return ''
154 return ''
154 else:
155 else:
155 return self.in_.read(length)
156 return self.in_.read(length)
156
157
157 def readline(self, size=-1):
158 def readline(self, size=-1):
158 if size < 0:
159 if size < 0:
159 size = self.maxchunksize
160 size = self.maxchunksize
160 s = self._read(size, 'L')
161 s = self._read(size, 'L')
161 buf = s
162 buf = s
162 # keep asking for more until there's either no more or
163 # keep asking for more until there's either no more or
163 # we got a full line
164 # we got a full line
164 while s and s[-1] != '\n':
165 while s and s[-1] != '\n':
165 s = self._read(size, 'L')
166 s = self._read(size, 'L')
166 buf += s
167 buf += s
167
168
168 return buf
169 return buf
169 else:
170 else:
170 return self._read(size, 'L')
171 return self._read(size, 'L')
171
172
172 def __iter__(self):
173 def __iter__(self):
173 return self
174 return self
174
175
175 def next(self):
176 def next(self):
176 l = self.readline()
177 l = self.readline()
177 if not l:
178 if not l:
178 raise StopIteration
179 raise StopIteration
179 return l
180 return l
180
181
181 __next__ = next
182 __next__ = next
182
183
183 def __getattr__(self, attr):
184 def __getattr__(self, attr):
184 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
185 if attr in (r'isatty', r'fileno', r'tell', r'seek'):
185 raise AttributeError(attr)
186 raise AttributeError(attr)
186 return getattr(self.in_, attr)
187 return getattr(self.in_, attr)
187
188
188 _messageencoders = {
189 _messageencoders = {
189 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
190 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
190 }
191 }
191
192
192 def _selectmessageencoder(ui):
193 def _selectmessageencoder(ui):
193 # experimental config: cmdserver.message-encodings
194 # experimental config: cmdserver.message-encodings
194 encnames = ui.configlist(b'cmdserver', b'message-encodings')
195 encnames = ui.configlist(b'cmdserver', b'message-encodings')
195 for n in encnames:
196 for n in encnames:
196 f = _messageencoders.get(n)
197 f = _messageencoders.get(n)
197 if f:
198 if f:
198 return n, f
199 return n, f
199 raise error.Abort(b'no supported message encodings: %s'
200 raise error.Abort(b'no supported message encodings: %s'
200 % b' '.join(encnames))
201 % b' '.join(encnames))
201
202
202 class server(object):
203 class server(object):
203 """
204 """
204 Listens for commands on fin, runs them and writes the output on a channel
205 Listens for commands on fin, runs them and writes the output on a channel
205 based stream to fout.
206 based stream to fout.
206 """
207 """
207 def __init__(self, ui, repo, fin, fout):
208 def __init__(self, ui, repo, fin, fout):
208 self.cwd = encoding.getcwd()
209 self.cwd = encoding.getcwd()
209
210
210 # developer config: cmdserver.log
211 # developer config: cmdserver.log
211 logpath = ui.config("cmdserver", "log")
212 logpath = ui.config("cmdserver", "log")
212 if logpath:
213 if logpath:
213 global logfile
214 global logfile
214 if logpath == '-':
215 if logpath == '-':
215 # write log on a special 'd' (debug) channel
216 # write log on a special 'd' (debug) channel
216 logfile = channeledoutput(fout, 'd')
217 logfile = channeledoutput(fout, 'd')
217 else:
218 else:
218 logfile = open(logpath, 'a')
219 logfile = open(logpath, 'a')
219
220
220 if repo:
221 if repo:
221 # the ui here is really the repo ui so take its baseui so we don't
222 # the ui here is really the repo ui so take its baseui so we don't
222 # end up with its local configuration
223 # end up with its local configuration
223 self.ui = repo.baseui
224 self.ui = repo.baseui
224 self.repo = repo
225 self.repo = repo
225 self.repoui = repo.ui
226 self.repoui = repo.ui
226 else:
227 else:
227 self.ui = ui
228 self.ui = ui
228 self.repo = self.repoui = None
229 self.repo = self.repoui = None
229
230
230 self.cerr = channeledoutput(fout, 'e')
231 self.cerr = channeledoutput(fout, 'e')
231 self.cout = channeledoutput(fout, 'o')
232 self.cout = channeledoutput(fout, 'o')
232 self.cin = channeledinput(fin, fout, 'I')
233 self.cin = channeledinput(fin, fout, 'I')
233 self.cresult = channeledoutput(fout, 'r')
234 self.cresult = channeledoutput(fout, 'r')
234
235
235 # TODO: add this to help/config.txt when stabilized
236 # TODO: add this to help/config.txt when stabilized
236 # ``channel``
237 # ``channel``
237 # Use separate channel for structured output. (Command-server only)
238 # Use separate channel for structured output. (Command-server only)
238 self.cmsg = None
239 self.cmsg = None
239 if ui.config(b'ui', b'message-output') == b'channel':
240 if ui.config(b'ui', b'message-output') == b'channel':
240 encname, encfn = _selectmessageencoder(ui)
241 encname, encfn = _selectmessageencoder(ui)
241 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
242 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
242
243
243 self.client = fin
244 self.client = fin
244
245
245 def cleanup(self):
246 def cleanup(self):
246 """release and restore resources taken during server session"""
247 """release and restore resources taken during server session"""
247
248
248 def _read(self, size):
249 def _read(self, size):
249 if not size:
250 if not size:
250 return ''
251 return ''
251
252
252 data = self.client.read(size)
253 data = self.client.read(size)
253
254
254 # is the other end closed?
255 # is the other end closed?
255 if not data:
256 if not data:
256 raise EOFError
257 raise EOFError
257
258
258 return data
259 return data
259
260
260 def _readstr(self):
261 def _readstr(self):
261 """read a string from the channel
262 """read a string from the channel
262
263
263 format:
264 format:
264 data length (uint32), data
265 data length (uint32), data
265 """
266 """
266 length = struct.unpack('>I', self._read(4))[0]
267 length = struct.unpack('>I', self._read(4))[0]
267 if not length:
268 if not length:
268 return ''
269 return ''
269 return self._read(length)
270 return self._read(length)
270
271
271 def _readlist(self):
272 def _readlist(self):
272 """read a list of NULL separated strings from the channel"""
273 """read a list of NULL separated strings from the channel"""
273 s = self._readstr()
274 s = self._readstr()
274 if s:
275 if s:
275 return s.split('\0')
276 return s.split('\0')
276 else:
277 else:
277 return []
278 return []
278
279
279 def runcommand(self):
280 def runcommand(self):
280 """ reads a list of \0 terminated arguments, executes
281 """ reads a list of \0 terminated arguments, executes
281 and writes the return code to the result channel """
282 and writes the return code to the result channel """
282 from . import dispatch # avoid cycle
283 from . import dispatch # avoid cycle
283
284
284 args = self._readlist()
285 args = self._readlist()
285
286
286 # copy the uis so changes (e.g. --config or --verbose) don't
287 # copy the uis so changes (e.g. --config or --verbose) don't
287 # persist between requests
288 # persist between requests
288 copiedui = self.ui.copy()
289 copiedui = self.ui.copy()
289 uis = [copiedui]
290 uis = [copiedui]
290 if self.repo:
291 if self.repo:
291 self.repo.baseui = copiedui
292 self.repo.baseui = copiedui
292 # clone ui without using ui.copy because this is protected
293 # clone ui without using ui.copy because this is protected
293 repoui = self.repoui.__class__(self.repoui)
294 repoui = self.repoui.__class__(self.repoui)
294 repoui.copy = copiedui.copy # redo copy protection
295 repoui.copy = copiedui.copy # redo copy protection
295 uis.append(repoui)
296 uis.append(repoui)
296 self.repo.ui = self.repo.dirstate._ui = repoui
297 self.repo.ui = self.repo.dirstate._ui = repoui
297 self.repo.invalidateall()
298 self.repo.invalidateall()
298
299
299 for ui in uis:
300 for ui in uis:
300 ui.resetstate()
301 ui.resetstate()
301 # any kind of interaction must use server channels, but chg may
302 # any kind of interaction must use server channels, but chg may
302 # replace channels by fully functional tty files. so nontty is
303 # replace channels by fully functional tty files. so nontty is
303 # enforced only if cin is a channel.
304 # enforced only if cin is a channel.
304 if not util.safehasattr(self.cin, 'fileno'):
305 if not util.safehasattr(self.cin, 'fileno'):
305 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
306 ui.setconfig('ui', 'nontty', 'true', 'commandserver')
306
307
307 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
308 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
308 self.cout, self.cerr, self.cmsg)
309 self.cout, self.cerr, self.cmsg)
309
310
310 try:
311 try:
311 ret = dispatch.dispatch(req) & 255
312 ret = dispatch.dispatch(req) & 255
312 self.cresult.write(struct.pack('>i', int(ret)))
313 self.cresult.write(struct.pack('>i', int(ret)))
313 finally:
314 finally:
314 # restore old cwd
315 # restore old cwd
315 if '--cwd' in args:
316 if '--cwd' in args:
316 os.chdir(self.cwd)
317 os.chdir(self.cwd)
317
318
318 def getencoding(self):
319 def getencoding(self):
319 """ writes the current encoding to the result channel """
320 """ writes the current encoding to the result channel """
320 self.cresult.write(encoding.encoding)
321 self.cresult.write(encoding.encoding)
321
322
322 def serveone(self):
323 def serveone(self):
323 cmd = self.client.readline()[:-1]
324 cmd = self.client.readline()[:-1]
324 if cmd:
325 if cmd:
325 handler = self.capabilities.get(cmd)
326 handler = self.capabilities.get(cmd)
326 if handler:
327 if handler:
327 handler(self)
328 handler(self)
328 else:
329 else:
329 # clients are expected to check what commands are supported by
330 # clients are expected to check what commands are supported by
330 # looking at the servers capabilities
331 # looking at the servers capabilities
331 raise error.Abort(_('unknown command %s') % cmd)
332 raise error.Abort(_('unknown command %s') % cmd)
332
333
333 return cmd != ''
334 return cmd != ''
334
335
335 capabilities = {'runcommand': runcommand,
336 capabilities = {'runcommand': runcommand,
336 'getencoding': getencoding}
337 'getencoding': getencoding}
337
338
338 def serve(self):
339 def serve(self):
339 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
340 hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
340 hellomsg += '\n'
341 hellomsg += '\n'
341 hellomsg += 'encoding: ' + encoding.encoding
342 hellomsg += 'encoding: ' + encoding.encoding
342 hellomsg += '\n'
343 hellomsg += '\n'
343 if self.cmsg:
344 if self.cmsg:
344 hellomsg += 'message-encoding: %s\n' % self.cmsg.encoding
345 hellomsg += 'message-encoding: %s\n' % self.cmsg.encoding
345 hellomsg += 'pid: %d' % procutil.getpid()
346 hellomsg += 'pid: %d' % procutil.getpid()
346 if util.safehasattr(os, 'getpgid'):
347 if util.safehasattr(os, 'getpgid'):
347 hellomsg += '\n'
348 hellomsg += '\n'
348 hellomsg += 'pgid: %d' % os.getpgid(0)
349 hellomsg += 'pgid: %d' % os.getpgid(0)
349
350
350 # write the hello msg in -one- chunk
351 # write the hello msg in -one- chunk
351 self.cout.write(hellomsg)
352 self.cout.write(hellomsg)
352
353
353 try:
354 try:
354 while self.serveone():
355 while self.serveone():
355 pass
356 pass
356 except EOFError:
357 except EOFError:
357 # we'll get here if the client disconnected while we were reading
358 # we'll get here if the client disconnected while we were reading
358 # its request
359 # its request
359 return 1
360 return 1
360
361
361 return 0
362 return 0
362
363
363 class pipeservice(object):
364 class pipeservice(object):
364 def __init__(self, ui, repo, opts):
365 def __init__(self, ui, repo, opts):
365 self.ui = ui
366 self.ui = ui
366 self.repo = repo
367 self.repo = repo
367
368
368 def init(self):
369 def init(self):
369 pass
370 pass
370
371
371 def run(self):
372 def run(self):
372 ui = self.ui
373 ui = self.ui
373 # redirect stdio to null device so that broken extensions or in-process
374 # redirect stdio to null device so that broken extensions or in-process
374 # hooks will never cause corruption of channel protocol.
375 # hooks will never cause corruption of channel protocol.
375 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
376 with procutil.protectedstdio(ui.fin, ui.fout) as (fin, fout):
376 sv = server(ui, self.repo, fin, fout)
377 sv = server(ui, self.repo, fin, fout)
377 try:
378 try:
378 return sv.serve()
379 return sv.serve()
379 finally:
380 finally:
380 sv.cleanup()
381 sv.cleanup()
381
382
382 def _initworkerprocess():
383 def _initworkerprocess():
383 # use a different process group from the master process, in order to:
384 # use a different process group from the master process, in order to:
384 # 1. make the current process group no longer "orphaned" (because the
385 # 1. make the current process group no longer "orphaned" (because the
385 # parent of this process is in a different process group while
386 # parent of this process is in a different process group while
386 # remains in a same session)
387 # remains in a same session)
387 # according to POSIX 2.2.2.52, orphaned process group will ignore
388 # according to POSIX 2.2.2.52, orphaned process group will ignore
388 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
389 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
389 # cause trouble for things like ncurses.
390 # cause trouble for things like ncurses.
390 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
391 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
391 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
392 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
392 # processes like ssh will be killed properly, without affecting
393 # processes like ssh will be killed properly, without affecting
393 # unrelated processes.
394 # unrelated processes.
394 os.setpgid(0, 0)
395 os.setpgid(0, 0)
395 # change random state otherwise forked request handlers would have a
396 # change random state otherwise forked request handlers would have a
396 # same state inherited from parent.
397 # same state inherited from parent.
397 random.seed()
398 random.seed()
398
399
399 def _serverequest(ui, repo, conn, createcmdserver):
400 def _serverequest(ui, repo, conn, createcmdserver):
400 fin = conn.makefile(r'rb')
401 fin = conn.makefile(r'rb')
401 fout = conn.makefile(r'wb')
402 fout = conn.makefile(r'wb')
402 sv = None
403 sv = None
403 try:
404 try:
404 sv = createcmdserver(repo, conn, fin, fout)
405 sv = createcmdserver(repo, conn, fin, fout)
405 try:
406 try:
406 sv.serve()
407 sv.serve()
407 # handle exceptions that may be raised by command server. most of
408 # handle exceptions that may be raised by command server. most of
408 # known exceptions are caught by dispatch.
409 # known exceptions are caught by dispatch.
409 except error.Abort as inst:
410 except error.Abort as inst:
410 ui.error(_('abort: %s\n') % inst)
411 ui.error(_('abort: %s\n') % inst)
411 except IOError as inst:
412 except IOError as inst:
412 if inst.errno != errno.EPIPE:
413 if inst.errno != errno.EPIPE:
413 raise
414 raise
414 except KeyboardInterrupt:
415 except KeyboardInterrupt:
415 pass
416 pass
416 finally:
417 finally:
417 sv.cleanup()
418 sv.cleanup()
418 except: # re-raises
419 except: # re-raises
419 # also write traceback to error channel. otherwise client cannot
420 # also write traceback to error channel. otherwise client cannot
420 # see it because it is written to server's stderr by default.
421 # see it because it is written to server's stderr by default.
421 if sv:
422 if sv:
422 cerr = sv.cerr
423 cerr = sv.cerr
423 else:
424 else:
424 cerr = channeledoutput(fout, 'e')
425 cerr = channeledoutput(fout, 'e')
425 cerr.write(encoding.strtolocal(traceback.format_exc()))
426 cerr.write(encoding.strtolocal(traceback.format_exc()))
426 raise
427 raise
427 finally:
428 finally:
428 fin.close()
429 fin.close()
429 try:
430 try:
430 fout.close() # implicit flush() may cause another EPIPE
431 fout.close() # implicit flush() may cause another EPIPE
431 except IOError as inst:
432 except IOError as inst:
432 if inst.errno != errno.EPIPE:
433 if inst.errno != errno.EPIPE:
433 raise
434 raise
434
435
435 class unixservicehandler(object):
436 class unixservicehandler(object):
436 """Set of pluggable operations for unix-mode services
437 """Set of pluggable operations for unix-mode services
437
438
438 Almost all methods except for createcmdserver() are called in the main
439 Almost all methods except for createcmdserver() are called in the main
439 process. You can't pass mutable resource back from createcmdserver().
440 process. You can't pass mutable resource back from createcmdserver().
440 """
441 """
441
442
442 pollinterval = None
443 pollinterval = None
443
444
444 def __init__(self, ui):
445 def __init__(self, ui):
445 self.ui = ui
446 self.ui = ui
446
447
447 def bindsocket(self, sock, address):
448 def bindsocket(self, sock, address):
448 util.bindunixsocket(sock, address)
449 util.bindunixsocket(sock, address)
449 sock.listen(socket.SOMAXCONN)
450 sock.listen(socket.SOMAXCONN)
450 self.ui.status(_('listening at %s\n') % address)
451 self.ui.status(_('listening at %s\n') % address)
451 self.ui.flush() # avoid buffering of status message
452 self.ui.flush() # avoid buffering of status message
452
453
453 def unlinksocket(self, address):
454 def unlinksocket(self, address):
454 os.unlink(address)
455 os.unlink(address)
455
456
456 def shouldexit(self):
457 def shouldexit(self):
457 """True if server should shut down; checked per pollinterval"""
458 """True if server should shut down; checked per pollinterval"""
458 return False
459 return False
459
460
460 def newconnection(self):
461 def newconnection(self):
461 """Called when main process notices new connection"""
462 """Called when main process notices new connection"""
462
463
463 def createcmdserver(self, repo, conn, fin, fout):
464 def createcmdserver(self, repo, conn, fin, fout):
464 """Create new command server instance; called in the process that
465 """Create new command server instance; called in the process that
465 serves for the current connection"""
466 serves for the current connection"""
466 return server(self.ui, repo, fin, fout)
467 return server(self.ui, repo, fin, fout)
467
468
468 class unixforkingservice(object):
469 class unixforkingservice(object):
469 """
470 """
470 Listens on unix domain socket and forks server per connection
471 Listens on unix domain socket and forks server per connection
471 """
472 """
472
473
473 def __init__(self, ui, repo, opts, handler=None):
474 def __init__(self, ui, repo, opts, handler=None):
474 self.ui = ui
475 self.ui = ui
475 self.repo = repo
476 self.repo = repo
476 self.address = opts['address']
477 self.address = opts['address']
477 if not util.safehasattr(socket, 'AF_UNIX'):
478 if not util.safehasattr(socket, 'AF_UNIX'):
478 raise error.Abort(_('unsupported platform'))
479 raise error.Abort(_('unsupported platform'))
479 if not self.address:
480 if not self.address:
480 raise error.Abort(_('no socket path specified with --address'))
481 raise error.Abort(_('no socket path specified with --address'))
481 self._servicehandler = handler or unixservicehandler(ui)
482 self._servicehandler = handler or unixservicehandler(ui)
482 self._sock = None
483 self._sock = None
483 self._oldsigchldhandler = None
484 self._oldsigchldhandler = None
484 self._workerpids = set() # updated by signal handler; do not iterate
485 self._workerpids = set() # updated by signal handler; do not iterate
485 self._socketunlinked = None
486 self._socketunlinked = None
486
487
487 def init(self):
488 def init(self):
488 self._sock = socket.socket(socket.AF_UNIX)
489 self._sock = socket.socket(socket.AF_UNIX)
489 self._servicehandler.bindsocket(self._sock, self.address)
490 self._servicehandler.bindsocket(self._sock, self.address)
490 if util.safehasattr(procutil, 'unblocksignal'):
491 if util.safehasattr(procutil, 'unblocksignal'):
491 procutil.unblocksignal(signal.SIGCHLD)
492 procutil.unblocksignal(signal.SIGCHLD)
492 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
493 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
493 self._oldsigchldhandler = o
494 self._oldsigchldhandler = o
494 self._socketunlinked = False
495 self._socketunlinked = False
495
496
496 def _unlinksocket(self):
497 def _unlinksocket(self):
497 if not self._socketunlinked:
498 if not self._socketunlinked:
498 self._servicehandler.unlinksocket(self.address)
499 self._servicehandler.unlinksocket(self.address)
499 self._socketunlinked = True
500 self._socketunlinked = True
500
501
501 def _cleanup(self):
502 def _cleanup(self):
502 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
503 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
503 self._sock.close()
504 self._sock.close()
504 self._unlinksocket()
505 self._unlinksocket()
505 # don't kill child processes as they have active clients, just wait
506 # don't kill child processes as they have active clients, just wait
506 self._reapworkers(0)
507 self._reapworkers(0)
507
508
508 def run(self):
509 def run(self):
509 try:
510 try:
510 self._mainloop()
511 self._mainloop()
511 finally:
512 finally:
512 self._cleanup()
513 self._cleanup()
513
514
514 def _mainloop(self):
515 def _mainloop(self):
515 exiting = False
516 exiting = False
516 h = self._servicehandler
517 h = self._servicehandler
517 selector = selectors.DefaultSelector()
518 selector = selectors.DefaultSelector()
518 selector.register(self._sock, selectors.EVENT_READ)
519 selector.register(self._sock, selectors.EVENT_READ)
519 while True:
520 while True:
520 if not exiting and h.shouldexit():
521 if not exiting and h.shouldexit():
521 # clients can no longer connect() to the domain socket, so
522 # clients can no longer connect() to the domain socket, so
522 # we stop queuing new requests.
523 # we stop queuing new requests.
523 # for requests that are queued (connect()-ed, but haven't been
524 # for requests that are queued (connect()-ed, but haven't been
524 # accept()-ed), handle them before exit. otherwise, clients
525 # accept()-ed), handle them before exit. otherwise, clients
525 # waiting for recv() will receive ECONNRESET.
526 # waiting for recv() will receive ECONNRESET.
526 self._unlinksocket()
527 self._unlinksocket()
527 exiting = True
528 exiting = True
528 ready = selector.select(timeout=h.pollinterval)
529 ready = selector.select(timeout=h.pollinterval)
529 if not ready:
530 if not ready:
530 # only exit if we completed all queued requests
531 # only exit if we completed all queued requests
531 if exiting:
532 if exiting:
532 break
533 break
533 continue
534 continue
534 try:
535 try:
535 conn, _addr = self._sock.accept()
536 conn, _addr = self._sock.accept()
536 except socket.error as inst:
537 except socket.error as inst:
537 if inst.args[0] == errno.EINTR:
538 if inst.args[0] == errno.EINTR:
538 continue
539 continue
539 raise
540 raise
540
541
541 pid = os.fork()
542 pid = os.fork()
542 if pid:
543 if pid:
543 try:
544 try:
544 self.ui.debug('forked worker process (pid=%d)\n' % pid)
545 self.ui.debug('forked worker process (pid=%d)\n' % pid)
545 self._workerpids.add(pid)
546 self._workerpids.add(pid)
546 h.newconnection()
547 h.newconnection()
547 finally:
548 finally:
548 conn.close() # release handle in parent process
549 conn.close() # release handle in parent process
549 else:
550 else:
550 try:
551 try:
551 selector.close()
552 selector.close()
552 self._sock.close()
553 self._sock.close()
553 self._runworker(conn)
554 self._runworker(conn)
554 conn.close()
555 conn.close()
555 os._exit(0)
556 os._exit(0)
556 except: # never return, hence no re-raises
557 except: # never return, hence no re-raises
557 try:
558 try:
558 self.ui.traceback(force=True)
559 self.ui.traceback(force=True)
559 finally:
560 finally:
560 os._exit(255)
561 os._exit(255)
561 selector.close()
562 selector.close()
562
563
563 def _sigchldhandler(self, signal, frame):
564 def _sigchldhandler(self, signal, frame):
564 self._reapworkers(os.WNOHANG)
565 self._reapworkers(os.WNOHANG)
565
566
566 def _reapworkers(self, options):
567 def _reapworkers(self, options):
567 while self._workerpids:
568 while self._workerpids:
568 try:
569 try:
569 pid, _status = os.waitpid(-1, options)
570 pid, _status = os.waitpid(-1, options)
570 except OSError as inst:
571 except OSError as inst:
571 if inst.errno == errno.EINTR:
572 if inst.errno == errno.EINTR:
572 continue
573 continue
573 if inst.errno != errno.ECHILD:
574 if inst.errno != errno.ECHILD:
574 raise
575 raise
575 # no child processes at all (reaped by other waitpid()?)
576 # no child processes at all (reaped by other waitpid()?)
576 self._workerpids.clear()
577 self._workerpids.clear()
577 return
578 return
578 if pid == 0:
579 if pid == 0:
579 # no waitable child processes
580 # no waitable child processes
580 return
581 return
581 self.ui.debug('worker process exited (pid=%d)\n' % pid)
582 self.ui.debug('worker process exited (pid=%d)\n' % pid)
582 self._workerpids.discard(pid)
583 self._workerpids.discard(pid)
583
584
584 def _runworker(self, conn):
585 def _runworker(self, conn):
585 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
586 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
586 _initworkerprocess()
587 _initworkerprocess()
587 h = self._servicehandler
588 h = self._servicehandler
588 try:
589 try:
589 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
590 _serverequest(self.ui, self.repo, conn, h.createcmdserver)
590 finally:
591 finally:
591 gc.collect() # trigger __del__ since worker process uses os._exit
592 gc.collect() # trigger __del__ since worker process uses os._exit
@@ -1,1996 +1,2004 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', password=True)
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 getattr(self._fmsgerr, 'structured', False):
1679 # channel for machine-readable output with metadata, just send
1680 # raw information
1681 # TODO: consider porting some useful information (e.g. estimated
1682 # time) from progbar. we might want to support update delay to
1683 # reduce the cost of transferring progress messages.
1684 self._fmsgerr.write(None, type=b'progress', topic=topic, pos=pos,
1685 item=item, unit=unit, total=total)
1686 elif self._progbar is not None:
1679 self._progbar.progress(topic, pos, item=item, unit=unit,
1687 self._progbar.progress(topic, pos, item=item, unit=unit,
1680 total=total)
1688 total=total)
1681 if pos is None or not self.configbool('progress', 'debug'):
1689 if pos is None or not self.configbool('progress', 'debug'):
1682 return
1690 return
1683
1691
1684 if unit:
1692 if unit:
1685 unit = ' ' + unit
1693 unit = ' ' + unit
1686 if item:
1694 if item:
1687 item = ' ' + item
1695 item = ' ' + item
1688
1696
1689 if total:
1697 if total:
1690 pct = 100.0 * pos / total
1698 pct = 100.0 * pos / total
1691 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1699 self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
1692 % (topic, item, pos, total, unit, pct))
1700 % (topic, item, pos, total, unit, pct))
1693 else:
1701 else:
1694 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1702 self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
1695
1703
1696 def makeprogress(self, topic, unit="", total=None):
1704 def makeprogress(self, topic, unit="", total=None):
1697 '''exists only so low-level modules won't need to import scmutil'''
1705 '''exists only so low-level modules won't need to import scmutil'''
1698 return scmutil.progress(self, topic, unit, total)
1706 return scmutil.progress(self, topic, unit, total)
1699
1707
1700 def log(self, service, *msg, **opts):
1708 def log(self, service, *msg, **opts):
1701 '''hook for logging facility extensions
1709 '''hook for logging facility extensions
1702
1710
1703 service should be a readily-identifiable subsystem, which will
1711 service should be a readily-identifiable subsystem, which will
1704 allow filtering.
1712 allow filtering.
1705
1713
1706 *msg should be a newline-terminated format string to log, and
1714 *msg should be a newline-terminated format string to log, and
1707 then any values to %-format into that format string.
1715 then any values to %-format into that format string.
1708
1716
1709 **opts currently has no defined meanings.
1717 **opts currently has no defined meanings.
1710 '''
1718 '''
1711
1719
1712 def label(self, msg, label):
1720 def label(self, msg, label):
1713 '''style msg based on supplied label
1721 '''style msg based on supplied label
1714
1722
1715 If some color mode is enabled, this will add the necessary control
1723 If some color mode is enabled, this will add the necessary control
1716 characters to apply such color. In addition, 'debug' color mode adds
1724 characters to apply such color. In addition, 'debug' color mode adds
1717 markup showing which label affects a piece of text.
1725 markup showing which label affects a piece of text.
1718
1726
1719 ui.write(s, 'label') is equivalent to
1727 ui.write(s, 'label') is equivalent to
1720 ui.write(ui.label(s, 'label')).
1728 ui.write(ui.label(s, 'label')).
1721 '''
1729 '''
1722 if self._colormode is not None:
1730 if self._colormode is not None:
1723 return color.colorlabel(self, msg, label)
1731 return color.colorlabel(self, msg, label)
1724 return msg
1732 return msg
1725
1733
1726 def develwarn(self, msg, stacklevel=1, config=None):
1734 def develwarn(self, msg, stacklevel=1, config=None):
1727 """issue a developer warning message
1735 """issue a developer warning message
1728
1736
1729 Use 'stacklevel' to report the offender some layers further up in the
1737 Use 'stacklevel' to report the offender some layers further up in the
1730 stack.
1738 stack.
1731 """
1739 """
1732 if not self.configbool('devel', 'all-warnings'):
1740 if not self.configbool('devel', 'all-warnings'):
1733 if config is None or not self.configbool('devel', config):
1741 if config is None or not self.configbool('devel', config):
1734 return
1742 return
1735 msg = 'devel-warn: ' + msg
1743 msg = 'devel-warn: ' + msg
1736 stacklevel += 1 # get in develwarn
1744 stacklevel += 1 # get in develwarn
1737 if self.tracebackflag:
1745 if self.tracebackflag:
1738 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1746 util.debugstacktrace(msg, stacklevel, self._ferr, self._fout)
1739 self.log('develwarn', '%s at:\n%s' %
1747 self.log('develwarn', '%s at:\n%s' %
1740 (msg, ''.join(util.getstackframes(stacklevel))))
1748 (msg, ''.join(util.getstackframes(stacklevel))))
1741 else:
1749 else:
1742 curframe = inspect.currentframe()
1750 curframe = inspect.currentframe()
1743 calframe = inspect.getouterframes(curframe, 2)
1751 calframe = inspect.getouterframes(curframe, 2)
1744 fname, lineno, fmsg = calframe[stacklevel][1:4]
1752 fname, lineno, fmsg = calframe[stacklevel][1:4]
1745 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1753 fname, fmsg = pycompat.sysbytes(fname), pycompat.sysbytes(fmsg)
1746 self.write_err('%s at: %s:%d (%s)\n'
1754 self.write_err('%s at: %s:%d (%s)\n'
1747 % (msg, fname, lineno, fmsg))
1755 % (msg, fname, lineno, fmsg))
1748 self.log('develwarn', '%s at: %s:%d (%s)\n',
1756 self.log('develwarn', '%s at: %s:%d (%s)\n',
1749 msg, fname, lineno, fmsg)
1757 msg, fname, lineno, fmsg)
1750 curframe = calframe = None # avoid cycles
1758 curframe = calframe = None # avoid cycles
1751
1759
1752 def deprecwarn(self, msg, version, stacklevel=2):
1760 def deprecwarn(self, msg, version, stacklevel=2):
1753 """issue a deprecation warning
1761 """issue a deprecation warning
1754
1762
1755 - msg: message explaining what is deprecated and how to upgrade,
1763 - msg: message explaining what is deprecated and how to upgrade,
1756 - version: last version where the API will be supported,
1764 - version: last version where the API will be supported,
1757 """
1765 """
1758 if not (self.configbool('devel', 'all-warnings')
1766 if not (self.configbool('devel', 'all-warnings')
1759 or self.configbool('devel', 'deprec-warn')):
1767 or self.configbool('devel', 'deprec-warn')):
1760 return
1768 return
1761 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1769 msg += ("\n(compatibility will be dropped after Mercurial-%s,"
1762 " update your code.)") % version
1770 " update your code.)") % version
1763 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1771 self.develwarn(msg, stacklevel=stacklevel, config='deprec-warn')
1764
1772
1765 def exportableenviron(self):
1773 def exportableenviron(self):
1766 """The environment variables that are safe to export, e.g. through
1774 """The environment variables that are safe to export, e.g. through
1767 hgweb.
1775 hgweb.
1768 """
1776 """
1769 return self._exportableenviron
1777 return self._exportableenviron
1770
1778
1771 @contextlib.contextmanager
1779 @contextlib.contextmanager
1772 def configoverride(self, overrides, source=""):
1780 def configoverride(self, overrides, source=""):
1773 """Context manager for temporary config overrides
1781 """Context manager for temporary config overrides
1774 `overrides` must be a dict of the following structure:
1782 `overrides` must be a dict of the following structure:
1775 {(section, name) : value}"""
1783 {(section, name) : value}"""
1776 backups = {}
1784 backups = {}
1777 try:
1785 try:
1778 for (section, name), value in overrides.items():
1786 for (section, name), value in overrides.items():
1779 backups[(section, name)] = self.backupconfig(section, name)
1787 backups[(section, name)] = self.backupconfig(section, name)
1780 self.setconfig(section, name, value, source)
1788 self.setconfig(section, name, value, source)
1781 yield
1789 yield
1782 finally:
1790 finally:
1783 for __, backup in backups.items():
1791 for __, backup in backups.items():
1784 self.restoreconfig(backup)
1792 self.restoreconfig(backup)
1785 # just restoring ui.quiet config to the previous value is not enough
1793 # just restoring ui.quiet config to the previous value is not enough
1786 # as it does not update ui.quiet class member
1794 # as it does not update ui.quiet class member
1787 if ('ui', 'quiet') in overrides:
1795 if ('ui', 'quiet') in overrides:
1788 self.fixconfig(section='ui')
1796 self.fixconfig(section='ui')
1789
1797
1790 class paths(dict):
1798 class paths(dict):
1791 """Represents a collection of paths and their configs.
1799 """Represents a collection of paths and their configs.
1792
1800
1793 Data is initially derived from ui instances and the config files they have
1801 Data is initially derived from ui instances and the config files they have
1794 loaded.
1802 loaded.
1795 """
1803 """
1796 def __init__(self, ui):
1804 def __init__(self, ui):
1797 dict.__init__(self)
1805 dict.__init__(self)
1798
1806
1799 for name, loc in ui.configitems('paths', ignoresub=True):
1807 for name, loc in ui.configitems('paths', ignoresub=True):
1800 # No location is the same as not existing.
1808 # No location is the same as not existing.
1801 if not loc:
1809 if not loc:
1802 continue
1810 continue
1803 loc, sub = ui.configsuboptions('paths', name)
1811 loc, sub = ui.configsuboptions('paths', name)
1804 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1812 self[name] = path(ui, name, rawloc=loc, suboptions=sub)
1805
1813
1806 def getpath(self, name, default=None):
1814 def getpath(self, name, default=None):
1807 """Return a ``path`` from a string, falling back to default.
1815 """Return a ``path`` from a string, falling back to default.
1808
1816
1809 ``name`` can be a named path or locations. Locations are filesystem
1817 ``name`` can be a named path or locations. Locations are filesystem
1810 paths or URIs.
1818 paths or URIs.
1811
1819
1812 Returns None if ``name`` is not a registered path, a URI, or a local
1820 Returns None if ``name`` is not a registered path, a URI, or a local
1813 path to a repo.
1821 path to a repo.
1814 """
1822 """
1815 # Only fall back to default if no path was requested.
1823 # Only fall back to default if no path was requested.
1816 if name is None:
1824 if name is None:
1817 if not default:
1825 if not default:
1818 default = ()
1826 default = ()
1819 elif not isinstance(default, (tuple, list)):
1827 elif not isinstance(default, (tuple, list)):
1820 default = (default,)
1828 default = (default,)
1821 for k in default:
1829 for k in default:
1822 try:
1830 try:
1823 return self[k]
1831 return self[k]
1824 except KeyError:
1832 except KeyError:
1825 continue
1833 continue
1826 return None
1834 return None
1827
1835
1828 # Most likely empty string.
1836 # Most likely empty string.
1829 # This may need to raise in the future.
1837 # This may need to raise in the future.
1830 if not name:
1838 if not name:
1831 return None
1839 return None
1832
1840
1833 try:
1841 try:
1834 return self[name]
1842 return self[name]
1835 except KeyError:
1843 except KeyError:
1836 # Try to resolve as a local path or URI.
1844 # Try to resolve as a local path or URI.
1837 try:
1845 try:
1838 # We don't pass sub-options in, so no need to pass ui instance.
1846 # We don't pass sub-options in, so no need to pass ui instance.
1839 return path(None, None, rawloc=name)
1847 return path(None, None, rawloc=name)
1840 except ValueError:
1848 except ValueError:
1841 raise error.RepoError(_('repository %s does not exist') %
1849 raise error.RepoError(_('repository %s does not exist') %
1842 name)
1850 name)
1843
1851
1844 _pathsuboptions = {}
1852 _pathsuboptions = {}
1845
1853
1846 def pathsuboption(option, attr):
1854 def pathsuboption(option, attr):
1847 """Decorator used to declare a path sub-option.
1855 """Decorator used to declare a path sub-option.
1848
1856
1849 Arguments are the sub-option name and the attribute it should set on
1857 Arguments are the sub-option name and the attribute it should set on
1850 ``path`` instances.
1858 ``path`` instances.
1851
1859
1852 The decorated function will receive as arguments a ``ui`` instance,
1860 The decorated function will receive as arguments a ``ui`` instance,
1853 ``path`` instance, and the string value of this option from the config.
1861 ``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``
1862 The function should return the value that will be set on the ``path``
1855 instance.
1863 instance.
1856
1864
1857 This decorator can be used to perform additional verification of
1865 This decorator can be used to perform additional verification of
1858 sub-options and to change the type of sub-options.
1866 sub-options and to change the type of sub-options.
1859 """
1867 """
1860 def register(func):
1868 def register(func):
1861 _pathsuboptions[option] = (attr, func)
1869 _pathsuboptions[option] = (attr, func)
1862 return func
1870 return func
1863 return register
1871 return register
1864
1872
1865 @pathsuboption('pushurl', 'pushloc')
1873 @pathsuboption('pushurl', 'pushloc')
1866 def pushurlpathoption(ui, path, value):
1874 def pushurlpathoption(ui, path, value):
1867 u = util.url(value)
1875 u = util.url(value)
1868 # Actually require a URL.
1876 # Actually require a URL.
1869 if not u.scheme:
1877 if not u.scheme:
1870 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1878 ui.warn(_('(paths.%s:pushurl not a URL; ignoring)\n') % path.name)
1871 return None
1879 return None
1872
1880
1873 # Don't support the #foo syntax in the push URL to declare branch to
1881 # Don't support the #foo syntax in the push URL to declare branch to
1874 # push.
1882 # push.
1875 if u.fragment:
1883 if u.fragment:
1876 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1884 ui.warn(_('("#fragment" in paths.%s:pushurl not supported; '
1877 'ignoring)\n') % path.name)
1885 'ignoring)\n') % path.name)
1878 u.fragment = None
1886 u.fragment = None
1879
1887
1880 return bytes(u)
1888 return bytes(u)
1881
1889
1882 @pathsuboption('pushrev', 'pushrev')
1890 @pathsuboption('pushrev', 'pushrev')
1883 def pushrevpathoption(ui, path, value):
1891 def pushrevpathoption(ui, path, value):
1884 return value
1892 return value
1885
1893
1886 class path(object):
1894 class path(object):
1887 """Represents an individual path and its configuration."""
1895 """Represents an individual path and its configuration."""
1888
1896
1889 def __init__(self, ui, name, rawloc=None, suboptions=None):
1897 def __init__(self, ui, name, rawloc=None, suboptions=None):
1890 """Construct a path from its config options.
1898 """Construct a path from its config options.
1891
1899
1892 ``ui`` is the ``ui`` instance the path is coming from.
1900 ``ui`` is the ``ui`` instance the path is coming from.
1893 ``name`` is the symbolic name of the path.
1901 ``name`` is the symbolic name of the path.
1894 ``rawloc`` is the raw location, as defined in the config.
1902 ``rawloc`` is the raw location, as defined in the config.
1895 ``pushloc`` is the raw locations pushes should be made to.
1903 ``pushloc`` is the raw locations pushes should be made to.
1896
1904
1897 If ``name`` is not defined, we require that the location be a) a local
1905 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,
1906 filesystem path with a .hg directory or b) a URL. If not,
1899 ``ValueError`` is raised.
1907 ``ValueError`` is raised.
1900 """
1908 """
1901 if not rawloc:
1909 if not rawloc:
1902 raise ValueError('rawloc must be defined')
1910 raise ValueError('rawloc must be defined')
1903
1911
1904 # Locations may define branches via syntax <base>#<branch>.
1912 # Locations may define branches via syntax <base>#<branch>.
1905 u = util.url(rawloc)
1913 u = util.url(rawloc)
1906 branch = None
1914 branch = None
1907 if u.fragment:
1915 if u.fragment:
1908 branch = u.fragment
1916 branch = u.fragment
1909 u.fragment = None
1917 u.fragment = None
1910
1918
1911 self.url = u
1919 self.url = u
1912 self.branch = branch
1920 self.branch = branch
1913
1921
1914 self.name = name
1922 self.name = name
1915 self.rawloc = rawloc
1923 self.rawloc = rawloc
1916 self.loc = '%s' % u
1924 self.loc = '%s' % u
1917
1925
1918 # When given a raw location but not a symbolic name, validate the
1926 # When given a raw location but not a symbolic name, validate the
1919 # location is valid.
1927 # location is valid.
1920 if not name and not u.scheme and not self._isvalidlocalpath(self.loc):
1928 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 '
1929 raise ValueError('location is not a URL or path to a local '
1922 'repo: %s' % rawloc)
1930 'repo: %s' % rawloc)
1923
1931
1924 suboptions = suboptions or {}
1932 suboptions = suboptions or {}
1925
1933
1926 # Now process the sub-options. If a sub-option is registered, its
1934 # 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
1935 # attribute will always be present. The value will be None if there
1928 # was no valid sub-option.
1936 # was no valid sub-option.
1929 for suboption, (attr, func) in _pathsuboptions.iteritems():
1937 for suboption, (attr, func) in _pathsuboptions.iteritems():
1930 if suboption not in suboptions:
1938 if suboption not in suboptions:
1931 setattr(self, attr, None)
1939 setattr(self, attr, None)
1932 continue
1940 continue
1933
1941
1934 value = func(ui, self, suboptions[suboption])
1942 value = func(ui, self, suboptions[suboption])
1935 setattr(self, attr, value)
1943 setattr(self, attr, value)
1936
1944
1937 def _isvalidlocalpath(self, path):
1945 def _isvalidlocalpath(self, path):
1938 """Returns True if the given path is a potentially valid repository.
1946 """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
1947 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
1948 'valid' in this case (like when pulling from a git repo into a hg
1941 one)."""
1949 one)."""
1942 return os.path.isdir(os.path.join(path, '.hg'))
1950 return os.path.isdir(os.path.join(path, '.hg'))
1943
1951
1944 @property
1952 @property
1945 def suboptions(self):
1953 def suboptions(self):
1946 """Return sub-options and their values for this path.
1954 """Return sub-options and their values for this path.
1947
1955
1948 This is intended to be used for presentation purposes.
1956 This is intended to be used for presentation purposes.
1949 """
1957 """
1950 d = {}
1958 d = {}
1951 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1959 for subopt, (attr, _func) in _pathsuboptions.iteritems():
1952 value = getattr(self, attr)
1960 value = getattr(self, attr)
1953 if value is not None:
1961 if value is not None:
1954 d[subopt] = value
1962 d[subopt] = value
1955 return d
1963 return d
1956
1964
1957 # we instantiate one globally shared progress bar to avoid
1965 # we instantiate one globally shared progress bar to avoid
1958 # competing progress bars when multiple UI objects get created
1966 # competing progress bars when multiple UI objects get created
1959 _progresssingleton = None
1967 _progresssingleton = None
1960
1968
1961 def getprogbar(ui):
1969 def getprogbar(ui):
1962 global _progresssingleton
1970 global _progresssingleton
1963 if _progresssingleton is None:
1971 if _progresssingleton is None:
1964 # passing 'ui' object to the singleton is fishy,
1972 # passing 'ui' object to the singleton is fishy,
1965 # this is how the extension used to work but feel free to rework it.
1973 # this is how the extension used to work but feel free to rework it.
1966 _progresssingleton = progress.progbar(ui)
1974 _progresssingleton = progress.progbar(ui)
1967 return _progresssingleton
1975 return _progresssingleton
1968
1976
1969 def haveprogbar():
1977 def haveprogbar():
1970 return _progresssingleton is not None
1978 return _progresssingleton is not None
1971
1979
1972 def _selectmsgdests(ui):
1980 def _selectmsgdests(ui):
1973 name = ui.config(b'ui', b'message-output')
1981 name = ui.config(b'ui', b'message-output')
1974 if name == b'channel':
1982 if name == b'channel':
1975 if ui.fmsg:
1983 if ui.fmsg:
1976 return ui.fmsg, ui.fmsg
1984 return ui.fmsg, ui.fmsg
1977 else:
1985 else:
1978 # fall back to ferr if channel isn't ready so that status/error
1986 # fall back to ferr if channel isn't ready so that status/error
1979 # messages can be printed
1987 # messages can be printed
1980 return ui.ferr, ui.ferr
1988 return ui.ferr, ui.ferr
1981 if name == b'stdio':
1989 if name == b'stdio':
1982 return ui.fout, ui.ferr
1990 return ui.fout, ui.ferr
1983 if name == b'stderr':
1991 if name == b'stderr':
1984 return ui.ferr, ui.ferr
1992 return ui.ferr, ui.ferr
1985 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
1993 raise error.Abort(b'invalid ui.message-output destination: %s' % name)
1986
1994
1987 def _writemsgwith(write, dest, *args, **opts):
1995 def _writemsgwith(write, dest, *args, **opts):
1988 """Write ui message with the given ui._write*() function
1996 """Write ui message with the given ui._write*() function
1989
1997
1990 The specified message type is translated to 'ui.<type>' label if the dest
1998 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.
1999 isn't a structured channel, so that the message will be colorized.
1992 """
2000 """
1993 # TODO: maybe change 'type' to a mandatory option
2001 # TODO: maybe change 'type' to a mandatory option
1994 if r'type' in opts and not getattr(dest, 'structured', False):
2002 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')
2003 opts[r'label'] = opts.get(r'label', '') + ' ui.%s' % opts.pop(r'type')
1996 write(dest, *args, **opts)
2004 write(dest, *args, **opts)
@@ -1,1092 +1,1096 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: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
753 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
754 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
755 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
754 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
756 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
757 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
755 message: '\xa2DdataOchecking files\nDtypeFstatus'
758 message: '\xa2DdataOchecking files\nDtypeFstatus'
759 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
756 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
760 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
757
761
758 >>> from hgclient import checkwith, readchannel, runcommand, stringio
762 >>> from hgclient import checkwith, readchannel, runcommand, stringio
759 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
763 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
760 ... b'--config', b'cmdserver.message-encodings=cbor',
764 ... b'--config', b'cmdserver.message-encodings=cbor',
761 ... b'--config', b'extensions.dbgui=dbgui.py'])
765 ... b'--config', b'extensions.dbgui=dbgui.py'])
762 ... def prompt(server):
766 ... def prompt(server):
763 ... readchannel(server)
767 ... readchannel(server)
764 ... interactive = [b'--config', b'ui.interactive=True']
768 ... interactive = [b'--config', b'ui.interactive=True']
765 ... runcommand(server, [b'debuggetpass'] + interactive,
769 ... runcommand(server, [b'debuggetpass'] + interactive,
766 ... input=stringio(b'1234\n'))
770 ... input=stringio(b'1234\n'))
767 ... runcommand(server, [b'debugprompt'] + interactive,
771 ... runcommand(server, [b'debugprompt'] + interactive,
768 ... input=stringio(b'5678\n'))
772 ... input=stringio(b'5678\n'))
769 ... runcommand(server, [b'debugpromptchoice'] + interactive,
773 ... runcommand(server, [b'debugpromptchoice'] + interactive,
770 ... input=stringio(b'n\n'))
774 ... input=stringio(b'n\n'))
771 *** runcommand debuggetpass --config ui.interactive=True
775 *** runcommand debuggetpass --config ui.interactive=True
772 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
776 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
773 1234
777 1234
774 *** runcommand debugprompt --config ui.interactive=True
778 *** runcommand debugprompt --config ui.interactive=True
775 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
779 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
776 5678
780 5678
777 *** runcommand debugpromptchoice --config ui.interactive=True
781 *** runcommand debugpromptchoice --config ui.interactive=True
778 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
782 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
779 1
783 1
780
784
781 bad message encoding:
785 bad message encoding:
782
786
783 $ hg serve --cmdserver pipe --config ui.message-output=channel
787 $ hg serve --cmdserver pipe --config ui.message-output=channel
784 abort: no supported message encodings:
788 abort: no supported message encodings:
785 [255]
789 [255]
786 $ hg serve --cmdserver pipe --config ui.message-output=channel \
790 $ hg serve --cmdserver pipe --config ui.message-output=channel \
787 > --config cmdserver.message-encodings='foo bar'
791 > --config cmdserver.message-encodings='foo bar'
788 abort: no supported message encodings: foo bar
792 abort: no supported message encodings: foo bar
789 [255]
793 [255]
790
794
791 unix domain socket:
795 unix domain socket:
792
796
793 $ cd repo
797 $ cd repo
794 $ hg update -q
798 $ hg update -q
795
799
796 #if unix-socket unix-permissions
800 #if unix-socket unix-permissions
797
801
798 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
802 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
799 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
803 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
800 >>> def hellomessage(conn):
804 >>> def hellomessage(conn):
801 ... ch, data = readchannel(conn)
805 ... ch, data = readchannel(conn)
802 ... bprint(b'%c, %r' % (ch, data))
806 ... bprint(b'%c, %r' % (ch, data))
803 ... runcommand(conn, [b'id'])
807 ... runcommand(conn, [b'id'])
804 >>> check(hellomessage, server.connect)
808 >>> check(hellomessage, server.connect)
805 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
809 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
806 *** runcommand id
810 *** runcommand id
807 eff892de26ec tip bm1/bm2/bm3
811 eff892de26ec tip bm1/bm2/bm3
808 >>> def unknowncommand(conn):
812 >>> def unknowncommand(conn):
809 ... readchannel(conn)
813 ... readchannel(conn)
810 ... conn.stdin.write(b'unknowncommand\n')
814 ... conn.stdin.write(b'unknowncommand\n')
811 >>> check(unknowncommand, server.connect) # error sent to server.log
815 >>> check(unknowncommand, server.connect) # error sent to server.log
812 >>> def serverinput(conn):
816 >>> def serverinput(conn):
813 ... readchannel(conn)
817 ... readchannel(conn)
814 ... patch = b"""
818 ... patch = b"""
815 ... # HG changeset patch
819 ... # HG changeset patch
816 ... # User test
820 ... # User test
817 ... # Date 0 0
821 ... # Date 0 0
818 ... 2
822 ... 2
819 ...
823 ...
820 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
824 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
821 ... --- a/a
825 ... --- a/a
822 ... +++ b/a
826 ... +++ b/a
823 ... @@ -1,1 +1,2 @@
827 ... @@ -1,1 +1,2 @@
824 ... 1
828 ... 1
825 ... +2
829 ... +2
826 ... """
830 ... """
827 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
831 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
828 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
832 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
829 >>> check(serverinput, server.connect)
833 >>> check(serverinput, server.connect)
830 *** runcommand import -
834 *** runcommand import -
831 applying patch from stdin
835 applying patch from stdin
832 *** runcommand log -rtip -q
836 *** runcommand log -rtip -q
833 2:1ed24be7e7a0
837 2:1ed24be7e7a0
834 >>> server.shutdown()
838 >>> server.shutdown()
835
839
836 $ cat .hg/server.log
840 $ cat .hg/server.log
837 listening at .hg/server.sock
841 listening at .hg/server.sock
838 abort: unknown command unknowncommand
842 abort: unknown command unknowncommand
839 killed!
843 killed!
840 $ rm .hg/server.log
844 $ rm .hg/server.log
841
845
842 if server crashed before hello, traceback will be sent to 'e' channel as
846 if server crashed before hello, traceback will be sent to 'e' channel as
843 last ditch:
847 last ditch:
844
848
845 $ cat <<EOF >> .hg/hgrc
849 $ cat <<EOF >> .hg/hgrc
846 > [cmdserver]
850 > [cmdserver]
847 > log = inexistent/path.log
851 > log = inexistent/path.log
848 > EOF
852 > EOF
849 >>> from hgclient import bprint, check, readchannel, unixserver
853 >>> from hgclient import bprint, check, readchannel, unixserver
850 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
854 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
851 >>> def earlycrash(conn):
855 >>> def earlycrash(conn):
852 ... while True:
856 ... while True:
853 ... try:
857 ... try:
854 ... ch, data = readchannel(conn)
858 ... ch, data = readchannel(conn)
855 ... for l in data.splitlines(True):
859 ... for l in data.splitlines(True):
856 ... if not l.startswith(b' '):
860 ... if not l.startswith(b' '):
857 ... bprint(b'%c, %r' % (ch, l))
861 ... bprint(b'%c, %r' % (ch, l))
858 ... except EOFError:
862 ... except EOFError:
859 ... break
863 ... break
860 >>> check(earlycrash, server.connect)
864 >>> check(earlycrash, server.connect)
861 e, 'Traceback (most recent call last):\n'
865 e, 'Traceback (most recent call last):\n'
862 e, "(IOError|FileNotFoundError): .*" (re)
866 e, "(IOError|FileNotFoundError): .*" (re)
863 >>> server.shutdown()
867 >>> server.shutdown()
864
868
865 $ cat .hg/server.log | grep -v '^ '
869 $ cat .hg/server.log | grep -v '^ '
866 listening at .hg/server.sock
870 listening at .hg/server.sock
867 Traceback (most recent call last):
871 Traceback (most recent call last):
868 (IOError|FileNotFoundError): .* (re)
872 (IOError|FileNotFoundError): .* (re)
869 killed!
873 killed!
870 #endif
874 #endif
871 #if no-unix-socket
875 #if no-unix-socket
872
876
873 $ hg serve --cmdserver unix -a .hg/server.sock
877 $ hg serve --cmdserver unix -a .hg/server.sock
874 abort: unsupported platform
878 abort: unsupported platform
875 [255]
879 [255]
876
880
877 #endif
881 #endif
878
882
879 $ cd ..
883 $ cd ..
880
884
881 Test that accessing to invalid changelog cache is avoided at
885 Test that accessing to invalid changelog cache is avoided at
882 subsequent operations even if repo object is reused even after failure
886 subsequent operations even if repo object is reused even after failure
883 of transaction (see 0a7610758c42 also)
887 of transaction (see 0a7610758c42 also)
884
888
885 "hg log" after failure of transaction is needed to detect invalid
889 "hg log" after failure of transaction is needed to detect invalid
886 cache in repoview: this can't detect by "hg verify" only.
890 cache in repoview: this can't detect by "hg verify" only.
887
891
888 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
892 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
889 4) are tested, because '00changelog.i' are differently changed in each
893 4) are tested, because '00changelog.i' are differently changed in each
890 cases.
894 cases.
891
895
892 $ cat > $TESTTMP/failafterfinalize.py <<EOF
896 $ cat > $TESTTMP/failafterfinalize.py <<EOF
893 > # extension to abort transaction after finalization forcibly
897 > # extension to abort transaction after finalization forcibly
894 > from mercurial import commands, error, extensions, lock as lockmod
898 > from mercurial import commands, error, extensions, lock as lockmod
895 > from mercurial import registrar
899 > from mercurial import registrar
896 > cmdtable = {}
900 > cmdtable = {}
897 > command = registrar.command(cmdtable)
901 > command = registrar.command(cmdtable)
898 > configtable = {}
902 > configtable = {}
899 > configitem = registrar.configitem(configtable)
903 > configitem = registrar.configitem(configtable)
900 > configitem(b'failafterfinalize', b'fail',
904 > configitem(b'failafterfinalize', b'fail',
901 > default=None,
905 > default=None,
902 > )
906 > )
903 > def fail(tr):
907 > def fail(tr):
904 > raise error.Abort(b'fail after finalization')
908 > raise error.Abort(b'fail after finalization')
905 > def reposetup(ui, repo):
909 > def reposetup(ui, repo):
906 > class failrepo(repo.__class__):
910 > class failrepo(repo.__class__):
907 > def commitctx(self, ctx, error=False):
911 > def commitctx(self, ctx, error=False):
908 > if self.ui.configbool(b'failafterfinalize', b'fail'):
912 > if self.ui.configbool(b'failafterfinalize', b'fail'):
909 > # 'sorted()' by ASCII code on category names causes
913 > # 'sorted()' by ASCII code on category names causes
910 > # invoking 'fail' after finalization of changelog
914 > # invoking 'fail' after finalization of changelog
911 > # using "'cl-%i' % id(self)" as category name
915 > # using "'cl-%i' % id(self)" as category name
912 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
916 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
913 > return super(failrepo, self).commitctx(ctx, error)
917 > return super(failrepo, self).commitctx(ctx, error)
914 > repo.__class__ = failrepo
918 > repo.__class__ = failrepo
915 > EOF
919 > EOF
916
920
917 $ hg init repo3
921 $ hg init repo3
918 $ cd repo3
922 $ cd repo3
919
923
920 $ cat <<EOF >> $HGRCPATH
924 $ cat <<EOF >> $HGRCPATH
921 > [ui]
925 > [ui]
922 > logtemplate = {rev} {desc|firstline} ({files})\n
926 > logtemplate = {rev} {desc|firstline} ({files})\n
923 >
927 >
924 > [extensions]
928 > [extensions]
925 > failafterfinalize = $TESTTMP/failafterfinalize.py
929 > failafterfinalize = $TESTTMP/failafterfinalize.py
926 > EOF
930 > EOF
927
931
928 - test failure with "empty changelog"
932 - test failure with "empty changelog"
929
933
930 $ echo foo > foo
934 $ echo foo > foo
931 $ hg add foo
935 $ hg add foo
932
936
933 (failure before finalization)
937 (failure before finalization)
934
938
935 >>> from hgclient import check, readchannel, runcommand
939 >>> from hgclient import check, readchannel, runcommand
936 >>> @check
940 >>> @check
937 ... def abort(server):
941 ... def abort(server):
938 ... readchannel(server)
942 ... readchannel(server)
939 ... runcommand(server, [b'commit',
943 ... runcommand(server, [b'commit',
940 ... b'--config', b'hooks.pretxncommit=false',
944 ... b'--config', b'hooks.pretxncommit=false',
941 ... b'-mfoo'])
945 ... b'-mfoo'])
942 ... runcommand(server, [b'log'])
946 ... runcommand(server, [b'log'])
943 ... runcommand(server, [b'verify', b'-q'])
947 ... runcommand(server, [b'verify', b'-q'])
944 *** runcommand commit --config hooks.pretxncommit=false -mfoo
948 *** runcommand commit --config hooks.pretxncommit=false -mfoo
945 transaction abort!
949 transaction abort!
946 rollback completed
950 rollback completed
947 abort: pretxncommit hook exited with status 1
951 abort: pretxncommit hook exited with status 1
948 [255]
952 [255]
949 *** runcommand log
953 *** runcommand log
950 *** runcommand verify -q
954 *** runcommand verify -q
951
955
952 (failure after finalization)
956 (failure after finalization)
953
957
954 >>> from hgclient import check, readchannel, runcommand
958 >>> from hgclient import check, readchannel, runcommand
955 >>> @check
959 >>> @check
956 ... def abort(server):
960 ... def abort(server):
957 ... readchannel(server)
961 ... readchannel(server)
958 ... runcommand(server, [b'commit',
962 ... runcommand(server, [b'commit',
959 ... b'--config', b'failafterfinalize.fail=true',
963 ... b'--config', b'failafterfinalize.fail=true',
960 ... b'-mfoo'])
964 ... b'-mfoo'])
961 ... runcommand(server, [b'log'])
965 ... runcommand(server, [b'log'])
962 ... runcommand(server, [b'verify', b'-q'])
966 ... runcommand(server, [b'verify', b'-q'])
963 *** runcommand commit --config failafterfinalize.fail=true -mfoo
967 *** runcommand commit --config failafterfinalize.fail=true -mfoo
964 transaction abort!
968 transaction abort!
965 rollback completed
969 rollback completed
966 abort: fail after finalization
970 abort: fail after finalization
967 [255]
971 [255]
968 *** runcommand log
972 *** runcommand log
969 *** runcommand verify -q
973 *** runcommand verify -q
970
974
971 - test failure with "not-empty changelog"
975 - test failure with "not-empty changelog"
972
976
973 $ echo bar > bar
977 $ echo bar > bar
974 $ hg add bar
978 $ hg add bar
975 $ hg commit -mbar bar
979 $ hg commit -mbar bar
976
980
977 (failure before finalization)
981 (failure before finalization)
978
982
979 >>> from hgclient import check, readchannel, runcommand
983 >>> from hgclient import check, readchannel, runcommand
980 >>> @check
984 >>> @check
981 ... def abort(server):
985 ... def abort(server):
982 ... readchannel(server)
986 ... readchannel(server)
983 ... runcommand(server, [b'commit',
987 ... runcommand(server, [b'commit',
984 ... b'--config', b'hooks.pretxncommit=false',
988 ... b'--config', b'hooks.pretxncommit=false',
985 ... b'-mfoo', b'foo'])
989 ... b'-mfoo', b'foo'])
986 ... runcommand(server, [b'log'])
990 ... runcommand(server, [b'log'])
987 ... runcommand(server, [b'verify', b'-q'])
991 ... runcommand(server, [b'verify', b'-q'])
988 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
992 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
989 transaction abort!
993 transaction abort!
990 rollback completed
994 rollback completed
991 abort: pretxncommit hook exited with status 1
995 abort: pretxncommit hook exited with status 1
992 [255]
996 [255]
993 *** runcommand log
997 *** runcommand log
994 0 bar (bar)
998 0 bar (bar)
995 *** runcommand verify -q
999 *** runcommand verify -q
996
1000
997 (failure after finalization)
1001 (failure after finalization)
998
1002
999 >>> from hgclient import check, readchannel, runcommand
1003 >>> from hgclient import check, readchannel, runcommand
1000 >>> @check
1004 >>> @check
1001 ... def abort(server):
1005 ... def abort(server):
1002 ... readchannel(server)
1006 ... readchannel(server)
1003 ... runcommand(server, [b'commit',
1007 ... runcommand(server, [b'commit',
1004 ... b'--config', b'failafterfinalize.fail=true',
1008 ... b'--config', b'failafterfinalize.fail=true',
1005 ... b'-mfoo', b'foo'])
1009 ... b'-mfoo', b'foo'])
1006 ... runcommand(server, [b'log'])
1010 ... runcommand(server, [b'log'])
1007 ... runcommand(server, [b'verify', b'-q'])
1011 ... runcommand(server, [b'verify', b'-q'])
1008 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1012 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1009 transaction abort!
1013 transaction abort!
1010 rollback completed
1014 rollback completed
1011 abort: fail after finalization
1015 abort: fail after finalization
1012 [255]
1016 [255]
1013 *** runcommand log
1017 *** runcommand log
1014 0 bar (bar)
1018 0 bar (bar)
1015 *** runcommand verify -q
1019 *** runcommand verify -q
1016
1020
1017 $ cd ..
1021 $ cd ..
1018
1022
1019 Test symlink traversal over cached audited paths:
1023 Test symlink traversal over cached audited paths:
1020 -------------------------------------------------
1024 -------------------------------------------------
1021
1025
1022 #if symlink
1026 #if symlink
1023
1027
1024 set up symlink hell
1028 set up symlink hell
1025
1029
1026 $ mkdir merge-symlink-out
1030 $ mkdir merge-symlink-out
1027 $ hg init merge-symlink
1031 $ hg init merge-symlink
1028 $ cd merge-symlink
1032 $ cd merge-symlink
1029 $ touch base
1033 $ touch base
1030 $ hg commit -qAm base
1034 $ hg commit -qAm base
1031 $ ln -s ../merge-symlink-out a
1035 $ ln -s ../merge-symlink-out a
1032 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1036 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1033 $ hg up -q 0
1037 $ hg up -q 0
1034 $ mkdir a
1038 $ mkdir a
1035 $ touch a/poisoned
1039 $ touch a/poisoned
1036 $ hg commit -qAm 'file a/poisoned'
1040 $ hg commit -qAm 'file a/poisoned'
1037 $ hg log -G -T '{rev}: {desc}\n'
1041 $ hg log -G -T '{rev}: {desc}\n'
1038 @ 2: file a/poisoned
1042 @ 2: file a/poisoned
1039 |
1043 |
1040 | o 1: symlink a -> ../merge-symlink-out
1044 | o 1: symlink a -> ../merge-symlink-out
1041 |/
1045 |/
1042 o 0: base
1046 o 0: base
1043
1047
1044
1048
1045 try trivial merge after update: cache of audited paths should be discarded,
1049 try trivial merge after update: cache of audited paths should be discarded,
1046 and the merge should fail (issue5628)
1050 and the merge should fail (issue5628)
1047
1051
1048 $ hg up -q null
1052 $ hg up -q null
1049 >>> from hgclient import check, readchannel, runcommand
1053 >>> from hgclient import check, readchannel, runcommand
1050 >>> @check
1054 >>> @check
1051 ... def merge(server):
1055 ... def merge(server):
1052 ... readchannel(server)
1056 ... readchannel(server)
1053 ... # audit a/poisoned as a good path
1057 ... # audit a/poisoned as a good path
1054 ... runcommand(server, [b'up', b'-qC', b'2'])
1058 ... runcommand(server, [b'up', b'-qC', b'2'])
1055 ... runcommand(server, [b'up', b'-qC', b'1'])
1059 ... runcommand(server, [b'up', b'-qC', b'1'])
1056 ... # here a is a symlink, so a/poisoned is bad
1060 ... # here a is a symlink, so a/poisoned is bad
1057 ... runcommand(server, [b'merge', b'2'])
1061 ... runcommand(server, [b'merge', b'2'])
1058 *** runcommand up -qC 2
1062 *** runcommand up -qC 2
1059 *** runcommand up -qC 1
1063 *** runcommand up -qC 1
1060 *** runcommand merge 2
1064 *** runcommand merge 2
1061 abort: path 'a/poisoned' traverses symbolic link 'a'
1065 abort: path 'a/poisoned' traverses symbolic link 'a'
1062 [255]
1066 [255]
1063 $ ls ../merge-symlink-out
1067 $ ls ../merge-symlink-out
1064
1068
1065 cache of repo.auditor should be discarded, so matcher would never traverse
1069 cache of repo.auditor should be discarded, so matcher would never traverse
1066 symlinks:
1070 symlinks:
1067
1071
1068 $ hg up -qC 0
1072 $ hg up -qC 0
1069 $ touch ../merge-symlink-out/poisoned
1073 $ touch ../merge-symlink-out/poisoned
1070 >>> from hgclient import check, readchannel, runcommand
1074 >>> from hgclient import check, readchannel, runcommand
1071 >>> @check
1075 >>> @check
1072 ... def files(server):
1076 ... def files(server):
1073 ... readchannel(server)
1077 ... readchannel(server)
1074 ... runcommand(server, [b'up', b'-qC', b'2'])
1078 ... runcommand(server, [b'up', b'-qC', b'2'])
1075 ... # audit a/poisoned as a good path
1079 ... # audit a/poisoned as a good path
1076 ... runcommand(server, [b'files', b'a/poisoned'])
1080 ... runcommand(server, [b'files', b'a/poisoned'])
1077 ... runcommand(server, [b'up', b'-qC', b'0'])
1081 ... runcommand(server, [b'up', b'-qC', b'0'])
1078 ... runcommand(server, [b'up', b'-qC', b'1'])
1082 ... runcommand(server, [b'up', b'-qC', b'1'])
1079 ... # here 'a' is a symlink, so a/poisoned should be warned
1083 ... # here 'a' is a symlink, so a/poisoned should be warned
1080 ... runcommand(server, [b'files', b'a/poisoned'])
1084 ... runcommand(server, [b'files', b'a/poisoned'])
1081 *** runcommand up -qC 2
1085 *** runcommand up -qC 2
1082 *** runcommand files a/poisoned
1086 *** runcommand files a/poisoned
1083 a/poisoned
1087 a/poisoned
1084 *** runcommand up -qC 0
1088 *** runcommand up -qC 0
1085 *** runcommand up -qC 1
1089 *** runcommand up -qC 1
1086 *** runcommand files a/poisoned
1090 *** runcommand files a/poisoned
1087 abort: path 'a/poisoned' traverses symbolic link 'a'
1091 abort: path 'a/poisoned' traverses symbolic link 'a'
1088 [255]
1092 [255]
1089
1093
1090 $ cd ..
1094 $ cd ..
1091
1095
1092 #endif
1096 #endif
General Comments 0
You need to be logged in to leave comments. Login now