mercurial/debugcommands.py
changeset 36528 72e487851a53
parent 36527 44dc34b8d17b
child 36530 bde0bd50f368
equal deleted inserted replaced
36527:44dc34b8d17b 36528:72e487851a53
    15 import os
    15 import os
    16 import random
    16 import random
    17 import socket
    17 import socket
    18 import ssl
    18 import ssl
    19 import string
    19 import string
       
    20 import subprocess
    20 import sys
    21 import sys
    21 import tempfile
    22 import tempfile
    22 import time
    23 import time
    23 
    24 
    24 from .i18n import _
    25 from .i18n import _
    63     revsetlang,
    64     revsetlang,
    64     scmutil,
    65     scmutil,
    65     setdiscovery,
    66     setdiscovery,
    66     simplemerge,
    67     simplemerge,
    67     smartset,
    68     smartset,
       
    69     sshpeer,
    68     sslutil,
    70     sslutil,
    69     streamclone,
    71     streamclone,
    70     templater,
    72     templater,
    71     treediscovery,
    73     treediscovery,
    72     upgrade,
    74     upgrade,
  2527     res1 = repo.debugwireargs(*vals, **args)
  2529     res1 = repo.debugwireargs(*vals, **args)
  2528     res2 = repo.debugwireargs(*vals, **args)
  2530     res2 = repo.debugwireargs(*vals, **args)
  2529     ui.write("%s\n" % res1)
  2531     ui.write("%s\n" % res1)
  2530     if res1 != res2:
  2532     if res1 != res2:
  2531         ui.warn("%s\n" % res2)
  2533         ui.warn("%s\n" % res2)
       
  2534 
       
  2535 def _parsewirelangblocks(fh):
       
  2536     activeaction = None
       
  2537     blocklines = []
       
  2538 
       
  2539     for line in fh:
       
  2540         line = line.rstrip()
       
  2541         if not line:
       
  2542             continue
       
  2543 
       
  2544         if line.startswith(b'#'):
       
  2545             continue
       
  2546 
       
  2547         if not line.startswith(' '):
       
  2548             # New block. Flush previous one.
       
  2549             if activeaction:
       
  2550                 yield activeaction, blocklines
       
  2551 
       
  2552             activeaction = line
       
  2553             blocklines = []
       
  2554             continue
       
  2555 
       
  2556         # Else we start with an indent.
       
  2557 
       
  2558         if not activeaction:
       
  2559             raise error.Abort(_('indented line outside of block'))
       
  2560 
       
  2561         blocklines.append(line)
       
  2562 
       
  2563     # Flush last block.
       
  2564     if activeaction:
       
  2565         yield activeaction, blocklines
       
  2566 
       
  2567 @command('debugwireproto',
       
  2568     [
       
  2569         ('', 'localssh', False, _('start an SSH server for this repo')),
       
  2570         ('', 'peer', '', _('construct a specific version of the peer')),
       
  2571     ] + cmdutil.remoteopts,
       
  2572     _('[REPO]'),
       
  2573     optionalrepo=True)
       
  2574 def debugwireproto(ui, repo, **opts):
       
  2575     """send wire protocol commands to a server
       
  2576 
       
  2577     This command can be used to issue wire protocol commands to remote
       
  2578     peers and to debug the raw data being exchanged.
       
  2579 
       
  2580     ``--localssh`` will start an SSH server against the current repository
       
  2581     and connect to that. By default, the connection will perform a handshake
       
  2582     and establish an appropriate peer instance.
       
  2583 
       
  2584     ``--peer`` can be used to bypass the handshake protocol and construct a
       
  2585     peer instance using the specified class type. Valid values are ``raw``,
       
  2586     ``ssh1``, and ``ssh2``. ``raw`` instances only allow sending raw data
       
  2587     payloads and don't support higher-level command actions.
       
  2588 
       
  2589     Commands are issued via a mini language which is specified via stdin.
       
  2590     The language consists of individual actions to perform. An action is
       
  2591     defined by a block. A block is defined as a line with no leading
       
  2592     space followed by 0 or more lines with leading space. Blocks are
       
  2593     effectively a high-level command with additional metadata.
       
  2594 
       
  2595     Lines beginning with ``#`` are ignored.
       
  2596 
       
  2597     The following sections denote available actions.
       
  2598 
       
  2599     raw
       
  2600     ---
       
  2601 
       
  2602     Send raw data to the server.
       
  2603 
       
  2604     The block payload contains the raw data to send as one atomic send
       
  2605     operation. The data may not actually be delivered in a single system
       
  2606     call: it depends on the abilities of the transport being used.
       
  2607 
       
  2608     Each line in the block is de-indented and concatenated. Then, that
       
  2609     value is evaluated as a Python b'' literal. This allows the use of
       
  2610     backslash escaping, etc.
       
  2611 
       
  2612     raw+
       
  2613     ----
       
  2614 
       
  2615     Behaves like ``raw`` except flushes output afterwards.
       
  2616 
       
  2617     close
       
  2618     -----
       
  2619 
       
  2620     Close the connection to the server.
       
  2621 
       
  2622     flush
       
  2623     -----
       
  2624 
       
  2625     Flush data written to the server.
       
  2626 
       
  2627     readavailable
       
  2628     -------------
       
  2629 
       
  2630     Read all available data from the server.
       
  2631 
       
  2632     If the connection to the server encompasses multiple pipes, we poll both
       
  2633     pipes and read available data.
       
  2634 
       
  2635     readline
       
  2636     --------
       
  2637 
       
  2638     Read a line of output from the server. If there are multiple output
       
  2639     pipes, reads only the main pipe.
       
  2640     """
       
  2641     opts = pycompat.byteskwargs(opts)
       
  2642 
       
  2643     if opts['localssh'] and not repo:
       
  2644         raise error.Abort(_('--localssh requires a repository'))
       
  2645 
       
  2646     if opts['peer'] and opts['peer'] not in ('raw', 'ssh1', 'ssh2'):
       
  2647         raise error.Abort(_('invalid value for --peer'),
       
  2648                           hint=_('valid values are "raw", "ssh1", and "ssh2"'))
       
  2649 
       
  2650     if ui.interactive():
       
  2651         ui.write(_('(waiting for commands on stdin)\n'))
       
  2652 
       
  2653     blocks = list(_parsewirelangblocks(ui.fin))
       
  2654 
       
  2655     proc = None
       
  2656 
       
  2657     if opts['localssh']:
       
  2658         # We start the SSH server in its own process so there is process
       
  2659         # separation. This prevents a whole class of potential bugs around
       
  2660         # shared state from interfering with server operation.
       
  2661         args = util.hgcmd() + [
       
  2662             '-R', repo.root,
       
  2663             'debugserve', '--sshstdio',
       
  2664         ]
       
  2665         proc = subprocess.Popen(args, stdin=subprocess.PIPE,
       
  2666                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
       
  2667                                 bufsize=0)
       
  2668 
       
  2669         stdin = proc.stdin
       
  2670         stdout = proc.stdout
       
  2671         stderr = proc.stderr
       
  2672 
       
  2673         # We turn the pipes into observers so we can log I/O.
       
  2674         if ui.verbose or opts['peer'] == 'raw':
       
  2675             stdin = util.makeloggingfileobject(ui, proc.stdin, b'i',
       
  2676                                                logdata=True)
       
  2677             stdout = util.makeloggingfileobject(ui, proc.stdout, b'o',
       
  2678                                                 logdata=True)
       
  2679             stderr = util.makeloggingfileobject(ui, proc.stderr, b'e',
       
  2680                                                 logdata=True)
       
  2681 
       
  2682         # --localssh also implies the peer connection settings.
       
  2683 
       
  2684         url = 'ssh://localserver'
       
  2685 
       
  2686         if opts['peer'] == 'ssh1':
       
  2687             ui.write(_('creating ssh peer for wire protocol version 1\n'))
       
  2688             peer = sshpeer.sshv1peer(ui, url, proc, stdin, stdout, stderr,
       
  2689                                      None)
       
  2690         elif opts['peer'] == 'ssh2':
       
  2691             ui.write(_('creating ssh peer for wire protocol version 2\n'))
       
  2692             peer = sshpeer.sshv2peer(ui, url, proc, stdin, stdout, stderr,
       
  2693                                      None)
       
  2694         elif opts['peer'] == 'raw':
       
  2695             ui.write(_('using raw connection to peer\n'))
       
  2696             peer = None
       
  2697         else:
       
  2698             ui.write(_('creating ssh peer from handshake results\n'))
       
  2699             peer = sshpeer.makepeer(ui, url, proc, stdin, stdout, stderr)
       
  2700 
       
  2701     else:
       
  2702         raise error.Abort(_('only --localssh is currently supported'))
       
  2703 
       
  2704     # Now perform actions based on the parsed wire language instructions.
       
  2705     for action, lines in blocks:
       
  2706         if action in ('raw', 'raw+'):
       
  2707             # Concatenate the data together.
       
  2708             data = ''.join(l.lstrip() for l in lines)
       
  2709             data = util.unescapestr(data)
       
  2710             stdin.write(data)
       
  2711 
       
  2712             if action == 'raw+':
       
  2713                 stdin.flush()
       
  2714         elif action == 'flush':
       
  2715             stdin.flush()
       
  2716         elif action == 'close':
       
  2717             peer.close()
       
  2718         elif action == 'readavailable':
       
  2719             fds = util.poll([stdout.fileno(), stderr.fileno()])
       
  2720 
       
  2721             if stdout.fileno() in fds:
       
  2722                 util.readpipe(stdout)
       
  2723             if stderr.fileno() in fds:
       
  2724                 util.readpipe(stderr)
       
  2725         elif action == 'readline':
       
  2726             stdout.readline()
       
  2727         else:
       
  2728             raise error.Abort(_('unknown action: %s') % action)
       
  2729 
       
  2730     if peer:
       
  2731         peer.close()
       
  2732 
       
  2733     if proc:
       
  2734         proc.kill()