Show More
@@ -0,0 +1,213 b'' | |||||
|
1 | # commandserver.py - communicate with Mercurial's API over a pipe | |||
|
2 | # | |||
|
3 | # Copyright Matt Mackall <mpm@selenic.com> | |||
|
4 | # | |||
|
5 | # This software may be used and distributed according to the terms of the | |||
|
6 | # GNU General Public License version 2 or any later version. | |||
|
7 | ||||
|
8 | from i18n import _ | |||
|
9 | import struct | |||
|
10 | import sys | |||
|
11 | import dispatch, encoding, util | |||
|
12 | ||||
|
13 | logfile = None | |||
|
14 | ||||
|
15 | def log(*args): | |||
|
16 | if not logfile: | |||
|
17 | return | |||
|
18 | ||||
|
19 | for a in args: | |||
|
20 | logfile.write(str(a)) | |||
|
21 | ||||
|
22 | logfile.flush() | |||
|
23 | ||||
|
24 | class channeledoutput(object): | |||
|
25 | """ | |||
|
26 | Write data from in_ to out in the following format: | |||
|
27 | ||||
|
28 | data length (unsigned int), | |||
|
29 | data | |||
|
30 | """ | |||
|
31 | def __init__(self, in_, out, channel): | |||
|
32 | self.in_ = in_ | |||
|
33 | self.out = out | |||
|
34 | self.channel = channel | |||
|
35 | ||||
|
36 | def write(self, data): | |||
|
37 | if not data: | |||
|
38 | return | |||
|
39 | self.out.write(struct.pack('>cI', self.channel, len(data))) | |||
|
40 | self.out.write(data) | |||
|
41 | self.out.flush() | |||
|
42 | ||||
|
43 | def __getattr__(self, attr): | |||
|
44 | if attr in ('isatty', 'fileno'): | |||
|
45 | raise AttributeError, attr | |||
|
46 | return getattr(self.in_, attr) | |||
|
47 | ||||
|
48 | class channeledinput(object): | |||
|
49 | """ | |||
|
50 | Read data from in_. | |||
|
51 | ||||
|
52 | Requests for input are written to out in the following format: | |||
|
53 | channel identifier - 'I' for plain input, 'L' line based (1 byte) | |||
|
54 | how many bytes to send at most (unsigned int), | |||
|
55 | ||||
|
56 | The client replies with: | |||
|
57 | data length (unsigned int), 0 meaning EOF | |||
|
58 | data | |||
|
59 | """ | |||
|
60 | ||||
|
61 | maxchunksize = 4 * 1024 | |||
|
62 | ||||
|
63 | def __init__(self, in_, out, channel): | |||
|
64 | self.in_ = in_ | |||
|
65 | self.out = out | |||
|
66 | self.channel = channel | |||
|
67 | ||||
|
68 | def read(self, size=-1): | |||
|
69 | if size < 0: | |||
|
70 | # if we need to consume all the clients input, ask for 4k chunks | |||
|
71 | # so the pipe doesn't fill up risking a deadlock | |||
|
72 | size = self.maxchunksize | |||
|
73 | s = self._read(size, self.channel) | |||
|
74 | buf = s | |||
|
75 | while s: | |||
|
76 | buf += s | |||
|
77 | s = self._read(size, self.channel) | |||
|
78 | ||||
|
79 | return buf | |||
|
80 | else: | |||
|
81 | return self._read(size, self.channel) | |||
|
82 | ||||
|
83 | def _read(self, size, channel): | |||
|
84 | if not size: | |||
|
85 | return '' | |||
|
86 | assert size > 0 | |||
|
87 | ||||
|
88 | # tell the client we need at most size bytes | |||
|
89 | self.out.write(struct.pack('>cI', channel, size)) | |||
|
90 | self.out.flush() | |||
|
91 | ||||
|
92 | length = self.in_.read(4) | |||
|
93 | length = struct.unpack('>I', length)[0] | |||
|
94 | if not length: | |||
|
95 | return '' | |||
|
96 | else: | |||
|
97 | return self.in_.read(length) | |||
|
98 | ||||
|
99 | def readline(self, size=-1): | |||
|
100 | if size < 0: | |||
|
101 | size = self.maxchunksize | |||
|
102 | s = self._read(size, 'L') | |||
|
103 | buf = s | |||
|
104 | # keep asking for more until there's either no more or | |||
|
105 | # we got a full line | |||
|
106 | while s and s[-1] != '\n': | |||
|
107 | buf += s | |||
|
108 | s = self._read(size, 'L') | |||
|
109 | ||||
|
110 | return buf | |||
|
111 | else: | |||
|
112 | return self._read(size, 'L') | |||
|
113 | ||||
|
114 | def __iter__(self): | |||
|
115 | return self | |||
|
116 | ||||
|
117 | def next(self): | |||
|
118 | l = self.readline() | |||
|
119 | if not l: | |||
|
120 | raise StopIteration | |||
|
121 | return l | |||
|
122 | ||||
|
123 | def __getattr__(self, attr): | |||
|
124 | if attr in ('isatty', 'fileno'): | |||
|
125 | raise AttributeError, attr | |||
|
126 | return getattr(self.in_, attr) | |||
|
127 | ||||
|
128 | class server(object): | |||
|
129 | """ | |||
|
130 | Listens for commands on stdin, runs them and writes the output on a channel | |||
|
131 | based stream to stdout. | |||
|
132 | """ | |||
|
133 | def __init__(self, ui, repo, mode): | |||
|
134 | self.ui = ui | |||
|
135 | ||||
|
136 | logpath = ui.config("cmdserver", "log", None) | |||
|
137 | if logpath: | |||
|
138 | global logfile | |||
|
139 | if logpath == '-': | |||
|
140 | # write log on a special 'd'ebug channel | |||
|
141 | logfile = channeledoutput(sys.stdout, sys.stdout, 'd') | |||
|
142 | else: | |||
|
143 | logfile = open(logpath, 'a') | |||
|
144 | ||||
|
145 | self.repo = repo | |||
|
146 | ||||
|
147 | if mode == 'pipe': | |||
|
148 | self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e') | |||
|
149 | self.cout = channeledoutput(sys.stdout, sys.stdout, 'o') | |||
|
150 | self.cin = channeledinput(sys.stdin, sys.stdout, 'I') | |||
|
151 | self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r') | |||
|
152 | ||||
|
153 | self.client = sys.stdin | |||
|
154 | else: | |||
|
155 | raise util.Abort(_('unknown mode %s') % mode) | |||
|
156 | ||||
|
157 | def _read(self, size): | |||
|
158 | data = self.client.read(size) | |||
|
159 | ||||
|
160 | # is the other end closed? | |||
|
161 | if not data: | |||
|
162 | raise EOFError() | |||
|
163 | ||||
|
164 | return data | |||
|
165 | ||||
|
166 | def runcommand(self): | |||
|
167 | """ reads a list of \0 terminated arguments, executes | |||
|
168 | and writes the return code to the result channel """ | |||
|
169 | ||||
|
170 | length = struct.unpack('>I', self._read(4))[0] | |||
|
171 | args = self._read(length).split('\0') | |||
|
172 | ||||
|
173 | # copy the ui so changes to it don't persist between requests | |||
|
174 | req = dispatch.request(args, self.ui.copy(), self.repo, self.cin, | |||
|
175 | self.cout, self.cerr) | |||
|
176 | ||||
|
177 | ret = dispatch.dispatch(req) or 0 # might return None | |||
|
178 | ||||
|
179 | self.cresult.write(struct.pack('>i', int(ret))) | |||
|
180 | ||||
|
181 | def getencoding(self): | |||
|
182 | """ writes the current encoding to the result channel """ | |||
|
183 | self.cresult.write(encoding.encoding) | |||
|
184 | ||||
|
185 | def serveone(self): | |||
|
186 | cmd = self.client.readline()[:-1] | |||
|
187 | if cmd: | |||
|
188 | handler = self.capabilities.get(cmd) | |||
|
189 | if handler: | |||
|
190 | handler(self) | |||
|
191 | else: | |||
|
192 | # clients are expected to check what commands are supported by | |||
|
193 | # looking at the servers capabilities | |||
|
194 | raise util.Abort(_('unknown command %s') % cmd) | |||
|
195 | ||||
|
196 | return cmd != '' | |||
|
197 | ||||
|
198 | capabilities = {'runcommand' : runcommand, | |||
|
199 | 'getencoding' : getencoding} | |||
|
200 | ||||
|
201 | def serve(self): | |||
|
202 | self.cout.write('capabilities: %s' % ' '.join(self.capabilities.keys())) | |||
|
203 | self.cout.write('encoding: %s' % encoding.encoding) | |||
|
204 | ||||
|
205 | try: | |||
|
206 | while self.serveone(): | |||
|
207 | pass | |||
|
208 | except EOFError: | |||
|
209 | # we'll get here if the client disconnected while we were reading | |||
|
210 | # its request | |||
|
211 | return 1 | |||
|
212 | ||||
|
213 | return 0 |
@@ -11,7 +11,8 b' from i18n import _, gettext' | |||||
11 | import os, re, difflib, time, tempfile, errno |
|
11 | import os, re, difflib, time, tempfile, errno | |
12 | import hg, scmutil, util, revlog, extensions, copies, error, bookmarks |
|
12 | import hg, scmutil, util, revlog, extensions, copies, error, bookmarks | |
13 | import patch, help, url, encoding, templatekw, discovery |
|
13 | import patch, help, url, encoding, templatekw, discovery | |
14 |
import archival, changegroup, cmdutil, |
|
14 | import archival, changegroup, cmdutil, hbisect | |
|
15 | import sshserver, hgweb, hgweb.server, commandserver | |||
15 | import merge as mergemod |
|
16 | import merge as mergemod | |
16 | import minirst, revset, fileset |
|
17 | import minirst, revset, fileset | |
17 | import dagparser, context, simplemerge |
|
18 | import dagparser, context, simplemerge | |
@@ -4418,6 +4419,7 b' def root(ui, repo):' | |||||
4418 | _('FILE')), |
|
4419 | _('FILE')), | |
4419 | ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')), |
|
4420 | ('', 'pid-file', '', _('name of file to write process ID to'), _('FILE')), | |
4420 | ('', 'stdio', None, _('for remote clients')), |
|
4421 | ('', 'stdio', None, _('for remote clients')), | |
|
4422 | ('', 'cmdserver', '', _('for remote clients'), _('MODE')), | |||
4421 | ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')), |
|
4423 | ('t', 'templates', '', _('web templates to use'), _('TEMPLATE')), | |
4422 | ('', 'style', '', _('template style to use'), _('STYLE')), |
|
4424 | ('', 'style', '', _('template style to use'), _('STYLE')), | |
4423 | ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), |
|
4425 | ('6', 'ipv6', None, _('use IPv6 in addition to IPv4')), | |
@@ -4448,13 +4450,24 b' def serve(ui, repo, **opts):' | |||||
4448 | Returns 0 on success. |
|
4450 | Returns 0 on success. | |
4449 | """ |
|
4451 | """ | |
4450 |
|
4452 | |||
4451 | if opts["stdio"]: |
|
4453 | if opts["stdio"] and opts["cmdserver"]: | |
|
4454 | raise util.Abort(_("cannot use --stdio with --cmdserver")) | |||
|
4455 | ||||
|
4456 | def checkrepo(): | |||
4452 | if repo is None: |
|
4457 | if repo is None: | |
4453 | raise error.RepoError(_("There is no Mercurial repository here" |
|
4458 | raise error.RepoError(_("There is no Mercurial repository here" | |
4454 | " (.hg not found)")) |
|
4459 | " (.hg not found)")) | |
|
4460 | ||||
|
4461 | if opts["stdio"]: | |||
|
4462 | checkrepo() | |||
4455 | s = sshserver.sshserver(ui, repo) |
|
4463 | s = sshserver.sshserver(ui, repo) | |
4456 | s.serve_forever() |
|
4464 | s.serve_forever() | |
4457 |
|
4465 | |||
|
4466 | if opts["cmdserver"]: | |||
|
4467 | checkrepo() | |||
|
4468 | s = commandserver.server(ui, repo, opts["cmdserver"]) | |||
|
4469 | return s.serve() | |||
|
4470 | ||||
4458 | # this way we can check if something was given in the command-line |
|
4471 | # this way we can check if something was given in the command-line | |
4459 | if opts.get('port'): |
|
4472 | if opts.get('port'): | |
4460 | opts['port'] = util.getport(opts.get('port')) |
|
4473 | opts['port'] = util.getport(opts.get('port')) |
@@ -137,6 +137,7 b' Show the options for the "serve" command' | |||||
137 | --accesslog |
|
137 | --accesslog | |
138 | --address |
|
138 | --address | |
139 | --certificate |
|
139 | --certificate | |
|
140 | --cmdserver | |||
140 | --config |
|
141 | --config | |
141 | --cwd |
|
142 | --cwd | |
142 | --daemon |
|
143 | --daemon | |
@@ -199,7 +200,7 b' Show all commands + options' | |||||
199 | pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure |
|
200 | pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure | |
200 | push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure |
|
201 | push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure | |
201 | remove: after, force, include, exclude |
|
202 | remove: after, force, include, exclude | |
202 | serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, templates, style, ipv6, certificate |
|
203 | serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate | |
203 | status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos |
|
204 | status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos | |
204 | summary: remote |
|
205 | summary: remote | |
205 | update: clean, check, date, rev |
|
206 | update: clean, check, date, rev |
@@ -79,7 +79,7 b' verify 7e7d56fe4833 (encoding fallback i' | |||||
79 | > |
|
79 | > | |
80 | > myui = ui.ui() |
|
80 | > myui = ui.ui() | |
81 | > repo = hg.repository(myui, 'a') |
|
81 | > repo = hg.repository(myui, 'a') | |
82 | > commands.serve(myui, repo, stdio=True) |
|
82 | > commands.serve(myui, repo, stdio=True, cmdserver=False) | |
83 | > EOF |
|
83 | > EOF | |
84 | $ echo baz >> b/foo |
|
84 | $ echo baz >> b/foo | |
85 | $ hg -R b ci -m baz |
|
85 | $ hg -R b ci -m baz |
General Comments 0
You need to be logged in to leave comments.
Login now