Differences between revisions 2 and 28 (spanning 26 versions)
Revision 2 as of 2011-06-12 11:06:55
Size: 2208
Editor: IdanKamara
Comment:
Revision 28 as of 2011-06-23 18:31:53
Size: 7749
Editor: mpm
Comment:
Deletions are marked like this. Additions are marked like this.
Line 12: Line 12:
Data sent from the server is channel based, meaning a (channel [character], length [unsigned int]) pair is sent before the actual data. For example: Data sent from the server is channel based, meaning a (channel, length) pair is sent before the actual data. The channel is a single character, while the length is an unsigned int (4 bytes). In the examples below, the length field is in plain text.
Line 20: Line 20:
that is 1234 bytes sent on channel 'o', with the data following.

When starting the server, it will send a new-line separated list of capabilities (on the 'o' channel), in this format:

{{{
capabilities:\n
capability1\n
capability2\n
...
}}}

At the most basic level, the server will support the 'runcommand' capability.
that is 1234 bytes sent on channel 'o', with 1234 bytes of data following.

When starting the server, it will send a hello message on the 'o' channel. The message is sent as one chunk. In this format:

{{{
capabilities: capability1 capability2 ... capabilityN\n
encoding: UTF-8
}}}

The first line is a space separated list of capabilities. At the most basic level, the server will support the 'runcommand' capability.
On the next line, the servers encoding.

Clients should ignore unknown fields in the hello message, in case a new version of the server decides to update it with some important information.

More on channels below.
Line 35: Line 38:
Strings sent from the server are all local strings. Strings are encoded by default in Mercurial's local encoding. At the moment the encoding cannot be changed after server startup. To set it at startup, use HGENCODING. To query the server's encoding, see the 'getencoding' command.

Clients wanting to use Unicode should specify a UTF-8 encoding, but be aware that some responses will mix UTF-8 metadata and raw file contents. See EncodingStrategy for more information.
Line 39: Line 44:
There are currently 5 channels:
 * o - Output channel. Everything that Mercurial writes to stdout when running from the command line is written on this channel.
 * e - Error channel. When running commands, it correlates to stderr.
 * i - Input channel. The length field here can either be 0, telling the client to send all input, or some positive number telling the client to send at most <length> bytes.
 * l - Line based input channel. The client should send a single line of input (trimmed if length is not 0). This channel is used when Mercurial is iterating over stdin.
Channels are divided into 2, required and optional. Required channels identifiers are uppercase. They cannot be ignored. If a client encounters an unexpected required channel, it should abort.

Optional channels identifiers are lowercase, and their data can be ignored.

Optional:
 * 'o'utput channel: most of the communication happens on this channel. When running commands, output Mercurial writes to stdout is written to this channel.
 * 'e'rror channel: when running commands, this correlates to stderr.
 * 'r'esult channel: the server uses this channel to tell the client that a command finished by writing its return value (command specific).
 * 'd'ebug channel: used when the server is started with logging to '-'.

Required:
 * 'I'nput channel: the length field here tells the client how many bytes to send.
 * 'L'ine based input channel: the client should send a single line of input (trimmed to length). This channel is used when Mercurial interacts with the user or when iterating over stdin.
Line 52: Line 65:
length = 0 sent by the client is interpreted as EOF by the server.

* d - Debug channel. Used when the server is started with logging to '-'.

=== Capabilities ===
length = 0 sent by the client is interpreted as EOF by the server. The server will not ask for more than 4kb per request as to not fill up the pipe.

=== Commands ===
Line 65: Line 76:
 * runcommand - Run the command specified by a list of \0-terminated strings. An unsigned int indicating the length of the arguments should be sent before the list. Example: The server aborts upon unknown commands. Clients are expected to check what commands are supported by the server by consulting the capabilities.

==== runcommand ====

Run the command specified by a list of \0-terminated strings. An unsigned int indicating the length of the arguments should be sent before the list. Example:
Line 77: Line 92:
The server responds with input/output generated by Mercurial on the matching channels. When the command returns, the server writes the return code (signed integer)
of the command to the 'r'esult channel.

==== getencoding ====

Returns the servers encoding on the result channel.

client:

{{{
getencoding\n
}}}

server responds with:
{{{
r
5
ascii
}}}

=== Examples ===

==== runcommand ====

Complete example of a client running 'hg summary', right after starting the server:

(text in the server column is <channel>: <length>, where length is really 4 byte unsigned ints, not plain text like below)

||server||client||notes||
|| || connected, waiting for hello message || ||
||'''o''': 24<<BR>>capabilities: runcommand getencoding || || ||
||'''o''': 15<<BR>>encoding: UTF-8 || || ||
|| || || server is waiting for a command ||
|| || runcommand\n<<BR>>7<<BR>>summary || client talks to server on stdin ||
|| starts running command || || ||
||'''o''': 27<<BR>>parent: 14571:17c0cb1045e5 || || ||
||'''o''': 3<<BR>>tip || || ||
||'''o''': 1<<BR>>\n || || ||
||'''o''': 53<<BR>> paper, coal: display diffstat on the changeset page\n || || ||
||'''o''': 16<<BR>>branch: default\n || || ||
||'''o''': 16<<BR>>commit: (clean)\n || || ||
||'''o''': 18<<BR>>update: (current)\n || || ||
||'''r''': 4<<BR>>0 || || server finished running command, writes ret on the 'r' channel to the client ||
|| || closes server stdin || client disconnects ||
|| server exits || || client waits for server to exit ||

And another one with activity on the input channels too by running 'import -':

(starting after client read the hello message)

||server||client||notes||
|| || || server is waiting for a command ||
|| || getencoding\n || ||
||'''r''': 5<<BR>>UTF-8 || || server responds with the encoding, then waits for the next command ||
|| || runcommand\n<<BR>>8<<BR>>import\0<<BR>>- || ||
|| starts running command || || ||
||'''o''': 26<<BR>>applying patch from stdin\n || || ||
||'''l''': 4096 || || server tells client to send it a line ||
|| || 21<<BR>># HG changeset patch\n || client responds with <length><line> ||
||'''l''': 4096 || || server processes line, asks for another one ||
|| || || ...this goes on until the client has no more input ||
||'''l''': 4096 || || ||
|| || 0 || it responds with length=0 ||
||'''r''': 4<<BR>>0 || || server finished running command, writes ret on the 'r' channel to the client ||
|| || closes server stdin || client disconnects ||
|| server exits || || client waits for server to exit ||

== Example client ==

This is a minimal Python example to illustrate how to establish a connection and execute a command.

{{{#!highlight python
import sys, struct, subprocess

# connect to the server
server = subprocess.Popen(['hg', 'serve', '--cmdserver', 'pipe'],
                          stdin=subprocess.PIPE, stdout=subprocess.PIPE)

def readchannel(server):
    channel, length = struct.unpack('>cI', server.stdout.read(5))
    if channel in 'IL': # input
        return channel, length
    return channel, server.stdout.read(length)

def writeblock(data):
    server.stdin.write(struct.pack('>l', len(data)))
    server.stdin.write(data)
    server.stdin.flush()

# read the hello block
hello = readchannel(server)
print "hello block:", repr(hello)

# write the command
server.stdin.write('runcommand\n')
writeblock('\0'.join(sys.arg[1:]))

# receive the response
while True:
    channel, val = readchannel(server)
    if channel == 'o':
        print "output:", repr(val)
    elif channel == 'e':
        print "error:", repr(val)
    elif channel == 'r':
        print "exit code:", struct.unpack(">l", val)[0]
        break
    elif channel == 'L':
        print "(line read request)"
        writeblock(sys.stdin.readline(val))
    elif channel == 'I':
        print "(block read request)"
        writeblock(sys.stdin.read(val))
    else:
        print "unexpected channel:", channel, val
        if channel == channel.upper(): # required?
            break

# shut down the server
server.stdin.close()
}}}

Command Server

A server that allows communication with Mercurial's API over a pipe.

1. Protocol

All communication with the server is done on stdin/stdout. The byte order used by the server is big-endian.

Data sent from the server is channel based, meaning a (channel, length) pair is sent before the actual data. The channel is a single character, while the length is an unsigned int (4 bytes). In the examples below, the length field is in plain text.

o
1234
<data: 1234 bytes>

that is 1234 bytes sent on channel 'o', with 1234 bytes of data following.

When starting the server, it will send a hello message on the 'o' channel. The message is sent as one chunk. In this format:

capabilities: capability1 capability2 ... capabilityN\n
encoding: UTF-8

The first line is a space separated list of capabilities. At the most basic level, the server will support the 'runcommand' capability. On the next line, the servers encoding.

Clients should ignore unknown fields in the hello message, in case a new version of the server decides to update it with some important information.

More on channels below.

1.1. Encoding

Strings are encoded by default in Mercurial's local encoding. At the moment the encoding cannot be changed after server startup. To set it at startup, use HGENCODING. To query the server's encoding, see the 'getencoding' command.

Clients wanting to use Unicode should specify a UTF-8 encoding, but be aware that some responses will mix UTF-8 metadata and raw file contents. See EncodingStrategy for more information.

1.2. Channels

Channels are divided into 2, required and optional. Required channels identifiers are uppercase. They cannot be ignored. If a client encounters an unexpected required channel, it should abort.

Optional channels identifiers are lowercase, and their data can be ignored.

Optional:

  • 'o'utput channel: most of the communication happens on this channel. When running commands, output Mercurial writes to stdout is written to this channel.
  • 'e'rror channel: when running commands, this correlates to stderr.
  • 'r'esult channel: the server uses this channel to tell the client that a command finished by writing its return value (command specific).
  • 'd'ebug channel: used when the server is started with logging to '-'.

Required:

  • 'I'nput channel: the length field here tells the client how many bytes to send.
  • 'L'ine based input channel: the client should send a single line of input (trimmed to length). This channel is used when Mercurial interacts with the user or when iterating over stdin.

Input should be sent on stdin in the following format:

length
data

length = 0 sent by the client is interpreted as EOF by the server. The server will not ask for more than 4kb per request as to not fill up the pipe.

1.3. Commands

The server is running on an endless loop (until stdin is closed) waiting for commands. A command request looks like this:

commandname\n
<command specific request>

The server aborts upon unknown commands. Clients are expected to check what commands are supported by the server by consulting the capabilities.

1.3.1. runcommand

Run the command specified by a list of \0-terminated strings. An unsigned int indicating the length of the arguments should be sent before the list. Example:

runcommand\n
8
log\0
-l\0
5

Which corresponds to running 'hg log -l 5'.

The server responds with input/output generated by Mercurial on the matching channels. When the command returns, the server writes the return code (signed integer) of the command to the 'r'esult channel.

1.3.2. getencoding

Returns the servers encoding on the result channel.

client:

getencoding\n

server responds with:

r
5
ascii

1.4. Examples

1.4.1. runcommand

Complete example of a client running 'hg summary', right after starting the server:

(text in the server column is <channel>: <length>, where length is really 4 byte unsigned ints, not plain text like below)

server

client

notes

connected, waiting for hello message

o: 24
capabilities: runcommand getencoding

o: 15
encoding: UTF-8

server is waiting for a command

runcommand\n
7
summary

client talks to server on stdin

starts running command

o: 27
parent: 14571:17c0cb1045e5

o: 3
tip

o: 1
\n

o: 53
paper, coal: display diffstat on the changeset page\n

o: 16
branch: default\n

o: 16
commit: (clean)\n

o: 18
update: (current)\n

r: 4
0

server finished running command, writes ret on the 'r' channel to the client

closes server stdin

client disconnects

server exits

client waits for server to exit

And another one with activity on the input channels too by running 'import -':

(starting after client read the hello message)

server

client

notes

server is waiting for a command

getencoding\n

r: 5
UTF-8

server responds with the encoding, then waits for the next command

runcommand\n
8
import\0
-

starts running command

o: 26
applying patch from stdin\n

l: 4096

server tells client to send it a line

21
# HG changeset patch\n

client responds with <length><line>

l: 4096

server processes line, asks for another one

...this goes on until the client has no more input

l: 4096

0

it responds with length=0

r: 4
0

server finished running command, writes ret on the 'r' channel to the client

closes server stdin

client disconnects

server exits

client waits for server to exit

2. Example client

This is a minimal Python example to illustrate how to establish a connection and execute a command.

   1 import sys, struct, subprocess
   2 
   3 # connect to the server
   4 server = subprocess.Popen(['hg', 'serve', '--cmdserver', 'pipe'],
   5                           stdin=subprocess.PIPE, stdout=subprocess.PIPE)
   6 
   7 def readchannel(server):
   8     channel, length = struct.unpack('>cI', server.stdout.read(5))
   9     if channel in 'IL': # input
  10         return channel, length
  11     return channel, server.stdout.read(length)
  12 
  13 def writeblock(data):
  14     server.stdin.write(struct.pack('>l', len(data)))
  15     server.stdin.write(data)
  16     server.stdin.flush()
  17 
  18 # read the hello block
  19 hello = readchannel(server)
  20 print "hello block:", repr(hello)
  21 
  22 # write the command
  23 server.stdin.write('runcommand\n')
  24 writeblock('\0'.join(sys.arg[1:]))
  25 
  26 # receive the response
  27 while True:
  28     channel, val = readchannel(server)
  29     if channel == 'o':
  30         print "output:", repr(val)
  31     elif channel == 'e':
  32         print "error:", repr(val)
  33     elif channel == 'r':
  34         print "exit code:", struct.unpack(">l", val)[0]
  35         break
  36     elif channel == 'L':
  37         print "(line read request)"
  38         writeblock(sys.stdin.readline(val))
  39     elif channel == 'I':
  40         print "(block read request)"
  41         writeblock(sys.stdin.read(val))
  42     else:
  43         print "unexpected channel:", channel, val
  44         if channel == channel.upper(): # required?
  45             break
  46 
  47 # shut down the server
  48 server.stdin.close()


CategoryDeveloper

CommandServer (last edited 2022-12-23 22:42:51 by gavenkoa)