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