##// END OF EJS Templates
cmdserver: repo.invalidate() on every runcommand...
Idan Kamara -
r14939:b4c06b97 default
parent child Browse files
Show More
@@ -1,236 +1,237
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 135
136 136 logpath = ui.config("cmdserver", "log", None)
137 137 if logpath:
138 138 global logfile
139 139 if logpath == '-':
140 140 # write log on a special 'd'ebug channel
141 141 logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
142 142 else:
143 143 logfile = open(logpath, 'a')
144 144
145 145 # the ui here is really the repo ui so take its baseui so we don't end up
146 146 # with its local configuration
147 147 self.ui = repo.baseui
148 148 self.repo = repo
149 149 self.repoui = repo.ui
150 150
151 151 if mode == 'pipe':
152 152 self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
153 153 self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
154 154 self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
155 155 self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
156 156
157 157 self.client = sys.stdin
158 158 else:
159 159 raise util.Abort(_('unknown mode %s') % mode)
160 160
161 161 def _read(self, size):
162 162 if not size:
163 163 return ''
164 164
165 165 data = self.client.read(size)
166 166
167 167 # is the other end closed?
168 168 if not data:
169 169 raise EOFError()
170 170
171 171 return data
172 172
173 173 def runcommand(self):
174 174 """ reads a list of \0 terminated arguments, executes
175 175 and writes the return code to the result channel """
176 176
177 177 length = struct.unpack('>I', self._read(4))[0]
178 178 if not length:
179 179 args = []
180 180 else:
181 181 args = self._read(length).split('\0')
182 182
183 183 # copy the uis so changes (e.g. --config or --verbose) don't
184 184 # persist between requests
185 185 copiedui = self.ui.copy()
186 186 self.repo.baseui = copiedui
187 187 self.repo.ui = self.repo.dirstate._ui = self.repoui.copy()
188 self.repo.invalidate()
188 189
189 190 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
190 191 self.cout, self.cerr)
191 192
192 193 ret = dispatch.dispatch(req) or 0 # might return None
193 194
194 195 # restore old cwd
195 196 if '--cwd' in args:
196 197 os.chdir(self.cwd)
197 198
198 199 self.cresult.write(struct.pack('>i', int(ret)))
199 200
200 201 def getencoding(self):
201 202 """ writes the current encoding to the result channel """
202 203 self.cresult.write(encoding.encoding)
203 204
204 205 def serveone(self):
205 206 cmd = self.client.readline()[:-1]
206 207 if cmd:
207 208 handler = self.capabilities.get(cmd)
208 209 if handler:
209 210 handler(self)
210 211 else:
211 212 # clients are expected to check what commands are supported by
212 213 # looking at the servers capabilities
213 214 raise util.Abort(_('unknown command %s') % cmd)
214 215
215 216 return cmd != ''
216 217
217 218 capabilities = {'runcommand' : runcommand,
218 219 'getencoding' : getencoding}
219 220
220 221 def serve(self):
221 222 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
222 223 hellomsg += '\n'
223 224 hellomsg += 'encoding: ' + encoding.encoding
224 225
225 226 # write the hello msg in -one- chunk
226 227 self.cout.write(hellomsg)
227 228
228 229 try:
229 230 while self.serveone():
230 231 pass
231 232 except EOFError:
232 233 # we'll get here if the client disconnected while we were reading
233 234 # its request
234 235 return 1
235 236
236 237 return 0
@@ -1,171 +1,198
1 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 134 def localhgrc(server):
135 135 """ check that local configs for the cached repo aren't inherited when -R
136 136 is used """
137 137 readchannel(server)
138 138
139 139 # the cached repo local hgrc contains ui.foo=bar, so showconfig should show it
140 140 runcommand(server, ['showconfig'])
141 141
142 142 # but not for this repo
143 143 runcommand(server, ['init', 'foo'])
144 144 runcommand(server, ['-R', 'foo', 'showconfig'])
145 145 shutil.rmtree('foo')
146 146
147 147 def hook(**args):
148 148 print 'hook talking'
149 149 print 'now try to read something: %r' % sys.stdin.read()
150 150
151 151 def hookoutput(server):
152 152 readchannel(server)
153 153 runcommand(server, ['--config',
154 154 'hooks.pre-identify=python:test-commandserver.hook', 'id'],
155 155 input=cStringIO.StringIO('some input'))
156 156
157 def outsidechanges(server):
158 readchannel(server)
159 os.system('echo a >> a && hg ci -Am2')
160 runcommand(server, ['tip'])
161
162 def bookmarks(server):
163 readchannel(server)
164 runcommand(server, ['bookmarks'])
165
166 # changes .hg/bookmarks
167 os.system('hg bookmark -i bm1')
168 os.system('hg bookmark -i bm2')
169 runcommand(server, ['bookmarks'])
170
171 # changes .hg/bookmarks.current
172 os.system('hg upd bm1 -q')
173 runcommand(server, ['bookmarks'])
174
175 def tagscache(server):
176 readchannel(server)
177 runcommand(server, ['id', '-t', '-r', '0'])
178 os.system('hg tag -r 0 foo')
179 runcommand(server, ['id', '-t', '-r', '0'])
180
157 181 if __name__ == '__main__':
158 182 os.system('hg init')
159 183
160 184 check(hellomessage)
161 185 check(unknowncommand)
162 186 check(checkruncommand)
163 187 check(inputeof)
164 188 check(serverinput)
165 189 check(cwd)
166 190
167 191 hgrc = open('.hg/hgrc', 'a')
168 192 hgrc.write('[ui]\nfoo=bar\n')
169 193 hgrc.close()
170 194 check(localhgrc)
171 195 check(hookoutput)
196 check(outsidechanges)
197 check(bookmarks)
198 check(tagscache)
@@ -1,54 +1,67
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 41 bundle.mainreporoot=$TESTTMP
42 42 defaults.backout=-d "0 0"
43 43 defaults.commit=-d "0 0"
44 44 defaults.tag=-d "0 0"
45 45 ui.slash=True
46 46 ui.foo=bar
47 47 bundle.mainreporoot=$TESTTMP/foo
48 48 defaults.backout=-d "0 0"
49 49 defaults.commit=-d "0 0"
50 50 defaults.tag=-d "0 0"
51 51 ui.slash=True
52 52 hook talking
53 53 now try to read something: 'some input'
54 54 eff892de26ec tip
55 changeset: 1:d3a0a68be6de
56 tag: tip
57 user: test
58 date: Thu Jan 01 00:00:00 1970 +0000
59 summary: 2
60
61 no bookmarks set
62 bm1 1:d3a0a68be6de
63 bm2 1:d3a0a68be6de
64 * bm1 1:d3a0a68be6de
65 bm2 1:d3a0a68be6de
66
67 foo
General Comments 0
You need to be logged in to leave comments. Login now