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