##// END OF EJS Templates
cmdserver: take repo.baseui as our ui...
Idan Kamara -
r14882:bb2cffe8 stable
parent child Browse files
Show More
@@ -1,234 +1,236 b''
1 # commandserver.py - communicate with Mercurial's API over a pipe
1 # commandserver.py - communicate with Mercurial's API over a pipe
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from i18n import _
8 from i18n import _
9 import struct
9 import struct
10 import sys, os
10 import sys, os
11 import dispatch, encoding, util
11 import dispatch, encoding, util
12
12
13 logfile = None
13 logfile = None
14
14
15 def log(*args):
15 def log(*args):
16 if not logfile:
16 if not logfile:
17 return
17 return
18
18
19 for a in args:
19 for a in args:
20 logfile.write(str(a))
20 logfile.write(str(a))
21
21
22 logfile.flush()
22 logfile.flush()
23
23
24 class channeledoutput(object):
24 class channeledoutput(object):
25 """
25 """
26 Write data from in_ to out in the following format:
26 Write data from in_ to out in the following format:
27
27
28 data length (unsigned int),
28 data length (unsigned int),
29 data
29 data
30 """
30 """
31 def __init__(self, in_, out, channel):
31 def __init__(self, in_, out, channel):
32 self.in_ = in_
32 self.in_ = in_
33 self.out = out
33 self.out = out
34 self.channel = channel
34 self.channel = channel
35
35
36 def write(self, data):
36 def write(self, data):
37 if not data:
37 if not data:
38 return
38 return
39 self.out.write(struct.pack('>cI', self.channel, len(data)))
39 self.out.write(struct.pack('>cI', self.channel, len(data)))
40 self.out.write(data)
40 self.out.write(data)
41 self.out.flush()
41 self.out.flush()
42
42
43 def __getattr__(self, attr):
43 def __getattr__(self, attr):
44 if attr in ('isatty', 'fileno'):
44 if attr in ('isatty', 'fileno'):
45 raise AttributeError, attr
45 raise AttributeError, attr
46 return getattr(self.in_, attr)
46 return getattr(self.in_, attr)
47
47
48 class channeledinput(object):
48 class channeledinput(object):
49 """
49 """
50 Read data from in_.
50 Read data from in_.
51
51
52 Requests for input are written to out in the following format:
52 Requests for input are written to out in the following format:
53 channel identifier - 'I' for plain input, 'L' line based (1 byte)
53 channel identifier - 'I' for plain input, 'L' line based (1 byte)
54 how many bytes to send at most (unsigned int),
54 how many bytes to send at most (unsigned int),
55
55
56 The client replies with:
56 The client replies with:
57 data length (unsigned int), 0 meaning EOF
57 data length (unsigned int), 0 meaning EOF
58 data
58 data
59 """
59 """
60
60
61 maxchunksize = 4 * 1024
61 maxchunksize = 4 * 1024
62
62
63 def __init__(self, in_, out, channel):
63 def __init__(self, in_, out, channel):
64 self.in_ = in_
64 self.in_ = in_
65 self.out = out
65 self.out = out
66 self.channel = channel
66 self.channel = channel
67
67
68 def read(self, size=-1):
68 def read(self, size=-1):
69 if size < 0:
69 if size < 0:
70 # if we need to consume all the clients input, ask for 4k chunks
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
71 # so the pipe doesn't fill up risking a deadlock
72 size = self.maxchunksize
72 size = self.maxchunksize
73 s = self._read(size, self.channel)
73 s = self._read(size, self.channel)
74 buf = s
74 buf = s
75 while s:
75 while s:
76 s = self._read(size, self.channel)
76 s = self._read(size, self.channel)
77 buf += s
77 buf += s
78
78
79 return buf
79 return buf
80 else:
80 else:
81 return self._read(size, self.channel)
81 return self._read(size, self.channel)
82
82
83 def _read(self, size, channel):
83 def _read(self, size, channel):
84 if not size:
84 if not size:
85 return ''
85 return ''
86 assert size > 0
86 assert size > 0
87
87
88 # tell the client we need at most size bytes
88 # tell the client we need at most size bytes
89 self.out.write(struct.pack('>cI', channel, size))
89 self.out.write(struct.pack('>cI', channel, size))
90 self.out.flush()
90 self.out.flush()
91
91
92 length = self.in_.read(4)
92 length = self.in_.read(4)
93 length = struct.unpack('>I', length)[0]
93 length = struct.unpack('>I', length)[0]
94 if not length:
94 if not length:
95 return ''
95 return ''
96 else:
96 else:
97 return self.in_.read(length)
97 return self.in_.read(length)
98
98
99 def readline(self, size=-1):
99 def readline(self, size=-1):
100 if size < 0:
100 if size < 0:
101 size = self.maxchunksize
101 size = self.maxchunksize
102 s = self._read(size, 'L')
102 s = self._read(size, 'L')
103 buf = s
103 buf = s
104 # keep asking for more until there's either no more or
104 # keep asking for more until there's either no more or
105 # we got a full line
105 # we got a full line
106 while s and s[-1] != '\n':
106 while s and s[-1] != '\n':
107 s = self._read(size, 'L')
107 s = self._read(size, 'L')
108 buf += s
108 buf += s
109
109
110 return buf
110 return buf
111 else:
111 else:
112 return self._read(size, 'L')
112 return self._read(size, 'L')
113
113
114 def __iter__(self):
114 def __iter__(self):
115 return self
115 return self
116
116
117 def next(self):
117 def next(self):
118 l = self.readline()
118 l = self.readline()
119 if not l:
119 if not l:
120 raise StopIteration
120 raise StopIteration
121 return l
121 return l
122
122
123 def __getattr__(self, attr):
123 def __getattr__(self, attr):
124 if attr in ('isatty', 'fileno'):
124 if attr in ('isatty', 'fileno'):
125 raise AttributeError, attr
125 raise AttributeError, attr
126 return getattr(self.in_, attr)
126 return getattr(self.in_, attr)
127
127
128 class server(object):
128 class server(object):
129 """
129 """
130 Listens for commands on stdin, runs them and writes the output on a channel
130 Listens for commands on stdin, runs them and writes the output on a channel
131 based stream to stdout.
131 based stream to stdout.
132 """
132 """
133 def __init__(self, ui, repo, mode):
133 def __init__(self, ui, repo, mode):
134 self.cwd = os.getcwd()
134 self.cwd = os.getcwd()
135 self.ui = ui
136
135
137 logpath = ui.config("cmdserver", "log", None)
136 logpath = ui.config("cmdserver", "log", None)
138 if logpath:
137 if logpath:
139 global logfile
138 global logfile
140 if logpath == '-':
139 if logpath == '-':
141 # write log on a special 'd'ebug channel
140 # write log on a special 'd'ebug channel
142 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
143 else:
142 else:
144 logfile = open(logpath, 'a')
143 logfile = open(logpath, 'a')
145
144
145 # the ui here is really the repo ui so take its baseui so we don't end up
146 # with its local configuration
147 self.ui = repo.baseui
146 self.repo = repo
148 self.repo = repo
147 self.repoui = repo.ui
149 self.repoui = repo.ui
148
150
149 if mode == 'pipe':
151 if mode == 'pipe':
150 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
152 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
151 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
153 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
152 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
154 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
153 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
155 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
154
156
155 self.client = sys.stdin
157 self.client = sys.stdin
156 else:
158 else:
157 raise util.Abort(_('unknown mode %s') % mode)
159 raise util.Abort(_('unknown mode %s') % mode)
158
160
159 def _read(self, size):
161 def _read(self, size):
160 if not size:
162 if not size:
161 return ''
163 return ''
162
164
163 data = self.client.read(size)
165 data = self.client.read(size)
164
166
165 # is the other end closed?
167 # is the other end closed?
166 if not data:
168 if not data:
167 raise EOFError()
169 raise EOFError()
168
170
169 return data
171 return data
170
172
171 def runcommand(self):
173 def runcommand(self):
172 """ reads a list of \0 terminated arguments, executes
174 """ reads a list of \0 terminated arguments, executes
173 and writes the return code to the result channel """
175 and writes the return code to the result channel """
174
176
175 length = struct.unpack('>I', self._read(4))[0]
177 length = struct.unpack('>I', self._read(4))[0]
176 if not length:
178 if not length:
177 args = []
179 args = []
178 else:
180 else:
179 args = self._read(length).split('\0')
181 args = self._read(length).split('\0')
180
182
181 # copy the uis so changes (e.g. --config or --verbose) don't
183 # copy the uis so changes (e.g. --config or --verbose) don't
182 # persist between requests
184 # persist between requests
183 copiedui = self.ui.copy()
185 copiedui = self.ui.copy()
184 self.repo.baseui = copiedui
186 self.repo.baseui = copiedui
185 self.repo.ui = self.repo.dirstate._ui = self.repoui.copy()
187 self.repo.ui = self.repo.dirstate._ui = self.repoui.copy()
186
188
187 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
189 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
188 self.cout, self.cerr)
190 self.cout, self.cerr)
189
191
190 ret = dispatch.dispatch(req) or 0 # might return None
192 ret = dispatch.dispatch(req) or 0 # might return None
191
193
192 # restore old cwd
194 # restore old cwd
193 if '--cwd' in args:
195 if '--cwd' in args:
194 os.chdir(self.cwd)
196 os.chdir(self.cwd)
195
197
196 self.cresult.write(struct.pack('>i', int(ret)))
198 self.cresult.write(struct.pack('>i', int(ret)))
197
199
198 def getencoding(self):
200 def getencoding(self):
199 """ writes the current encoding to the result channel """
201 """ writes the current encoding to the result channel """
200 self.cresult.write(encoding.encoding)
202 self.cresult.write(encoding.encoding)
201
203
202 def serveone(self):
204 def serveone(self):
203 cmd = self.client.readline()[:-1]
205 cmd = self.client.readline()[:-1]
204 if cmd:
206 if cmd:
205 handler = self.capabilities.get(cmd)
207 handler = self.capabilities.get(cmd)
206 if handler:
208 if handler:
207 handler(self)
209 handler(self)
208 else:
210 else:
209 # clients are expected to check what commands are supported by
211 # clients are expected to check what commands are supported by
210 # looking at the servers capabilities
212 # looking at the servers capabilities
211 raise util.Abort(_('unknown command %s') % cmd)
213 raise util.Abort(_('unknown command %s') % cmd)
212
214
213 return cmd != ''
215 return cmd != ''
214
216
215 capabilities = {'runcommand' : runcommand,
217 capabilities = {'runcommand' : runcommand,
216 'getencoding' : getencoding}
218 'getencoding' : getencoding}
217
219
218 def serve(self):
220 def serve(self):
219 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
221 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
220 hellomsg += '\n'
222 hellomsg += '\n'
221 hellomsg += 'encoding: ' + encoding.encoding
223 hellomsg += 'encoding: ' + encoding.encoding
222
224
223 # write the hello msg in -one- chunk
225 # write the hello msg in -one- chunk
224 self.cout.write(hellomsg)
226 self.cout.write(hellomsg)
225
227
226 try:
228 try:
227 while self.serveone():
229 while self.serveone():
228 pass
230 pass
229 except EOFError:
231 except EOFError:
230 # we'll get here if the client disconnected while we were reading
232 # we'll get here if the client disconnected while we were reading
231 # its request
233 # its request
232 return 1
234 return 1
233
235
234 return 0
236 return 0
@@ -1,142 +1,160 b''
1 import sys, os, struct, subprocess, cStringIO, re
1 import sys, os, struct, subprocess, cStringIO, re, shutil
2
2
3 def connect(path=None):
3 def connect(path=None):
4 cmdline = ['hg', 'serve', '--cmdserver', 'pipe']
4 cmdline = ['hg', 'serve', '--cmdserver', 'pipe']
5 if path:
5 if path:
6 cmdline += ['-R', path]
6 cmdline += ['-R', path]
7
7
8 server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
8 server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
9 stdout=subprocess.PIPE)
9 stdout=subprocess.PIPE)
10
10
11 return server
11 return server
12
12
13 def writeblock(server, data):
13 def writeblock(server, data):
14 server.stdin.write(struct.pack('>I', len(data)))
14 server.stdin.write(struct.pack('>I', len(data)))
15 server.stdin.write(data)
15 server.stdin.write(data)
16 server.stdin.flush()
16 server.stdin.flush()
17
17
18 def readchannel(server):
18 def readchannel(server):
19 data = server.stdout.read(5)
19 data = server.stdout.read(5)
20 if not data:
20 if not data:
21 raise EOFError()
21 raise EOFError()
22 channel, length = struct.unpack('>cI', data)
22 channel, length = struct.unpack('>cI', data)
23 if channel in 'IL':
23 if channel in 'IL':
24 return channel, length
24 return channel, length
25 else:
25 else:
26 return channel, server.stdout.read(length)
26 return channel, server.stdout.read(length)
27
27
28 def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None):
28 def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None):
29 server.stdin.write('runcommand\n')
29 server.stdin.write('runcommand\n')
30 writeblock(server, '\0'.join(args))
30 writeblock(server, '\0'.join(args))
31
31
32 if not input:
32 if not input:
33 input = cStringIO.StringIO()
33 input = cStringIO.StringIO()
34
34
35 while True:
35 while True:
36 ch, data = readchannel(server)
36 ch, data = readchannel(server)
37 if ch == 'o':
37 if ch == 'o':
38 output.write(data)
38 output.write(data)
39 output.flush()
39 output.flush()
40 elif ch == 'e':
40 elif ch == 'e':
41 error.write(data)
41 error.write(data)
42 error.flush()
42 error.flush()
43 elif ch == 'I':
43 elif ch == 'I':
44 writeblock(server, input.read(data))
44 writeblock(server, input.read(data))
45 elif ch == 'L':
45 elif ch == 'L':
46 writeblock(server, input.readline(data))
46 writeblock(server, input.readline(data))
47 elif ch == 'r':
47 elif ch == 'r':
48 return struct.unpack('>i', data)[0]
48 return struct.unpack('>i', data)[0]
49 else:
49 else:
50 print "unexpected channel %c: %r" % (ch, data)
50 print "unexpected channel %c: %r" % (ch, data)
51 if ch.isupper():
51 if ch.isupper():
52 return
52 return
53
53
54 def check(func, repopath=None):
54 def check(func, repopath=None):
55 server = connect(repopath)
55 server = connect(repopath)
56 try:
56 try:
57 return func(server)
57 return func(server)
58 finally:
58 finally:
59 server.stdin.close()
59 server.stdin.close()
60 server.wait()
60 server.wait()
61
61
62 def unknowncommand(server):
62 def unknowncommand(server):
63 server.stdin.write('unknowncommand\n')
63 server.stdin.write('unknowncommand\n')
64
64
65 def hellomessage(server):
65 def hellomessage(server):
66 ch, data = readchannel(server)
66 ch, data = readchannel(server)
67 # escaping python tests output not supported
67 # escaping python tests output not supported
68 print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***', data))
68 print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***', data))
69
69
70 # run an arbitrary command to make sure the next thing the server sends
70 # run an arbitrary command to make sure the next thing the server sends
71 # isn't part of the hello message
71 # isn't part of the hello message
72 runcommand(server, ['id'])
72 runcommand(server, ['id'])
73
73
74 def checkruncommand(server):
74 def checkruncommand(server):
75 # hello block
75 # hello block
76 readchannel(server)
76 readchannel(server)
77
77
78 # no args
78 # no args
79 runcommand(server, [])
79 runcommand(server, [])
80
80
81 # global options
81 # global options
82 runcommand(server, ['id', '--quiet'])
82 runcommand(server, ['id', '--quiet'])
83
83
84 # make sure global options don't stick through requests
84 # make sure global options don't stick through requests
85 runcommand(server, ['id'])
85 runcommand(server, ['id'])
86
86
87 # --config
87 # --config
88 runcommand(server, ['id', '--config', 'ui.quiet=True'])
88 runcommand(server, ['id', '--config', 'ui.quiet=True'])
89
89
90 # make sure --config doesn't stick
90 # make sure --config doesn't stick
91 runcommand(server, ['id'])
91 runcommand(server, ['id'])
92
92
93 def inputeof(server):
93 def inputeof(server):
94 readchannel(server)
94 readchannel(server)
95 server.stdin.write('runcommand\n')
95 server.stdin.write('runcommand\n')
96 # close stdin while server is waiting for input
96 # close stdin while server is waiting for input
97 server.stdin.close()
97 server.stdin.close()
98
98
99 # server exits with 1 if the pipe closed while reading the command
99 # server exits with 1 if the pipe closed while reading the command
100 print 'server exit code =', server.wait()
100 print 'server exit code =', server.wait()
101
101
102 def serverinput(server):
102 def serverinput(server):
103 readchannel(server)
103 readchannel(server)
104
104
105 patch = """
105 patch = """
106 # HG changeset patch
106 # HG changeset patch
107 # User test
107 # User test
108 # Date 0 0
108 # Date 0 0
109 # Node ID c103a3dec114d882c98382d684d8af798d09d857
109 # Node ID c103a3dec114d882c98382d684d8af798d09d857
110 # Parent 0000000000000000000000000000000000000000
110 # Parent 0000000000000000000000000000000000000000
111 1
111 1
112
112
113 diff -r 000000000000 -r c103a3dec114 a
113 diff -r 000000000000 -r c103a3dec114 a
114 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
114 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
115 +++ b/a Thu Jan 01 00:00:00 1970 +0000
115 +++ b/a Thu Jan 01 00:00:00 1970 +0000
116 @@ -0,0 +1,1 @@
116 @@ -0,0 +1,1 @@
117 +1
117 +1
118 """
118 """
119
119
120 runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
120 runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
121 runcommand(server, ['log'])
121 runcommand(server, ['log'])
122
122
123 def cwd(server):
123 def cwd(server):
124 """ check that --cwd doesn't persist between requests """
124 """ check that --cwd doesn't persist between requests """
125 readchannel(server)
125 readchannel(server)
126 os.mkdir('foo')
126 os.mkdir('foo')
127 f = open('foo/bar', 'w')
127 f = open('foo/bar', 'w')
128 f.write('a')
128 f.write('a')
129 f.close()
129 f.close()
130 runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
130 runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
131 runcommand(server, ['st', 'foo/bar'])
131 runcommand(server, ['st', 'foo/bar'])
132 os.remove('foo/bar')
132 os.remove('foo/bar')
133
133
134 def localhgrc(server):
135 """ check that local configs for the cached repo aren't inherited when -R
136 is used """
137 readchannel(server)
138
139 # the cached repo local hgrc contains ui.foo=bar, so showconfig should show it
140 runcommand(server, ['showconfig'])
141
142 # but not for this repo
143 runcommand(server, ['init', 'foo'])
144 runcommand(server, ['-R', 'foo', 'showconfig'])
145 shutil.rmtree('foo')
146
134 if __name__ == '__main__':
147 if __name__ == '__main__':
135 os.system('hg init')
148 os.system('hg init')
136
149
137 check(hellomessage)
150 check(hellomessage)
138 check(unknowncommand)
151 check(unknowncommand)
139 check(checkruncommand)
152 check(checkruncommand)
140 check(inputeof)
153 check(inputeof)
141 check(serverinput)
154 check(serverinput)
142 check(cwd)
155 check(cwd)
156
157 hgrc = open('.hg/hgrc', 'a')
158 hgrc.write('[ui]\nfoo=bar\n')
159 hgrc.close()
160 check(localhgrc)
@@ -1,40 +1,51 b''
1 o, 'capabilities: getencoding runcommand\nencoding: ***'
1 o, 'capabilities: getencoding runcommand\nencoding: ***'
2 000000000000 tip
2 000000000000 tip
3 abort: unknown command unknowncommand
3 abort: unknown command unknowncommand
4 Mercurial Distributed SCM
4 Mercurial Distributed SCM
5
5
6 basic commands:
6 basic commands:
7
7
8 add add the specified files on the next commit
8 add add the specified files on the next commit
9 annotate show changeset information by line for each file
9 annotate show changeset information by line for each file
10 clone make a copy of an existing repository
10 clone make a copy of an existing repository
11 commit commit the specified files or all outstanding changes
11 commit commit the specified files or all outstanding changes
12 diff diff repository (or selected files)
12 diff diff repository (or selected files)
13 export dump the header and diffs for one or more changesets
13 export dump the header and diffs for one or more changesets
14 forget forget the specified files on the next commit
14 forget forget the specified files on the next commit
15 init create a new repository in the given directory
15 init create a new repository in the given directory
16 log show revision history of entire repository or files
16 log show revision history of entire repository or files
17 merge merge working directory with another revision
17 merge merge working directory with another revision
18 pull pull changes from the specified source
18 pull pull changes from the specified source
19 push push changes to the specified destination
19 push push changes to the specified destination
20 remove remove the specified files on the next commit
20 remove remove the specified files on the next commit
21 serve start stand-alone webserver
21 serve start stand-alone webserver
22 status show changed files in the working directory
22 status show changed files in the working directory
23 summary summarize working directory state
23 summary summarize working directory state
24 update update working directory (or switch revisions)
24 update update working directory (or switch revisions)
25
25
26 use "hg help" for the full list of commands or "hg -v" for details
26 use "hg help" for the full list of commands or "hg -v" for details
27 000000000000
27 000000000000
28 000000000000 tip
28 000000000000 tip
29 000000000000
29 000000000000
30 000000000000 tip
30 000000000000 tip
31 server exit code = 1
31 server exit code = 1
32 applying patch from stdin
32 applying patch from stdin
33 changeset: 0:eff892de26ec
33 changeset: 0:eff892de26ec
34 tag: tip
34 tag: tip
35 user: test
35 user: test
36 date: Thu Jan 01 00:00:00 1970 +0000
36 date: Thu Jan 01 00:00:00 1970 +0000
37 summary: 1
37 summary: 1
38
38
39 ? bar
39 ? bar
40 ? foo/bar
40 ? foo/bar
41 bundle.mainreporoot=$TESTTMP
42 defaults.backout=-d "0 0"
43 defaults.commit=-d "0 0"
44 defaults.tag=-d "0 0"
45 ui.slash=True
46 ui.foo=bar
47 bundle.mainreporoot=$TESTTMP/foo
48 defaults.backout=-d "0 0"
49 defaults.commit=-d "0 0"
50 defaults.tag=-d "0 0"
51 ui.slash=True
General Comments 0
You need to be logged in to leave comments. Login now