##// END OF EJS Templates
cmdserver: invalidate the dirstate when running commands (issue3271)...
Idan Kamara -
r16114:acfca07a stable
parent child Browse files
Show More
@@ -1,237 +1,238 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 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 188 self.repo.invalidate()
189 self.repo.invalidatedirstate()
189 190
190 191 req = dispatch.request(args[:], copiedui, self.repo, self.cin,
191 192 self.cout, self.cerr)
192 193
193 194 ret = dispatch.dispatch(req) or 0 # might return None
194 195
195 196 # restore old cwd
196 197 if '--cwd' in args:
197 198 os.chdir(self.cwd)
198 199
199 200 self.cresult.write(struct.pack('>i', int(ret)))
200 201
201 202 def getencoding(self):
202 203 """ writes the current encoding to the result channel """
203 204 self.cresult.write(encoding.encoding)
204 205
205 206 def serveone(self):
206 207 cmd = self.client.readline()[:-1]
207 208 if cmd:
208 209 handler = self.capabilities.get(cmd)
209 210 if handler:
210 211 handler(self)
211 212 else:
212 213 # clients are expected to check what commands are supported by
213 214 # looking at the servers capabilities
214 215 raise util.Abort(_('unknown command %s') % cmd)
215 216
216 217 return cmd != ''
217 218
218 219 capabilities = {'runcommand' : runcommand,
219 220 'getencoding' : getencoding}
220 221
221 222 def serve(self):
222 223 hellomsg = 'capabilities: ' + ' '.join(self.capabilities.keys())
223 224 hellomsg += '\n'
224 225 hellomsg += 'encoding: ' + encoding.encoding
225 226
226 227 # write the hello msg in -one- chunk
227 228 self.cout.write(hellomsg)
228 229
229 230 try:
230 231 while self.serveone():
231 232 pass
232 233 except EOFError:
233 234 # we'll get here if the client disconnected while we were reading
234 235 # its request
235 236 return 1
236 237
237 238 return 0
@@ -1,212 +1,214 b''
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 print ' runcommand', ' '.join(args)
30 30 server.stdin.write('runcommand\n')
31 31 writeblock(server, '\0'.join(args))
32 32
33 33 if not input:
34 34 input = cStringIO.StringIO()
35 35
36 36 while True:
37 37 ch, data = readchannel(server)
38 38 if ch == 'o':
39 39 output.write(data)
40 40 output.flush()
41 41 elif ch == 'e':
42 42 error.write(data)
43 43 error.flush()
44 44 elif ch == 'I':
45 45 writeblock(server, input.read(data))
46 46 elif ch == 'L':
47 47 writeblock(server, input.readline(data))
48 48 elif ch == 'r':
49 49 return struct.unpack('>i', data)[0]
50 50 else:
51 51 print "unexpected channel %c: %r" % (ch, data)
52 52 if ch.isupper():
53 53 return
54 54
55 55 def check(func, repopath=None):
56 56 print
57 57 print 'testing %s:' % func.__name__
58 58 print
59 59 server = connect(repopath)
60 60 try:
61 61 return func(server)
62 62 finally:
63 63 server.stdin.close()
64 64 server.wait()
65 65
66 66 def unknowncommand(server):
67 67 server.stdin.write('unknowncommand\n')
68 68
69 69 def hellomessage(server):
70 70 ch, data = readchannel(server)
71 71 # escaping python tests output not supported
72 72 print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***', data))
73 73
74 74 # run an arbitrary command to make sure the next thing the server sends
75 75 # isn't part of the hello message
76 76 runcommand(server, ['id'])
77 77
78 78 def checkruncommand(server):
79 79 # hello block
80 80 readchannel(server)
81 81
82 82 # no args
83 83 runcommand(server, [])
84 84
85 85 # global options
86 86 runcommand(server, ['id', '--quiet'])
87 87
88 88 # make sure global options don't stick through requests
89 89 runcommand(server, ['id'])
90 90
91 91 # --config
92 92 runcommand(server, ['id', '--config', 'ui.quiet=True'])
93 93
94 94 # make sure --config doesn't stick
95 95 runcommand(server, ['id'])
96 96
97 97 def inputeof(server):
98 98 readchannel(server)
99 99 server.stdin.write('runcommand\n')
100 100 # close stdin while server is waiting for input
101 101 server.stdin.close()
102 102
103 103 # server exits with 1 if the pipe closed while reading the command
104 104 print 'server exit code =', server.wait()
105 105
106 106 def serverinput(server):
107 107 readchannel(server)
108 108
109 109 patch = """
110 110 # HG changeset patch
111 111 # User test
112 112 # Date 0 0
113 113 # Node ID c103a3dec114d882c98382d684d8af798d09d857
114 114 # Parent 0000000000000000000000000000000000000000
115 115 1
116 116
117 117 diff -r 000000000000 -r c103a3dec114 a
118 118 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
119 119 +++ b/a Thu Jan 01 00:00:00 1970 +0000
120 120 @@ -0,0 +1,1 @@
121 121 +1
122 122 """
123 123
124 124 runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
125 125 runcommand(server, ['log'])
126 126
127 127 def cwd(server):
128 128 """ check that --cwd doesn't persist between requests """
129 129 readchannel(server)
130 130 os.mkdir('foo')
131 131 f = open('foo/bar', 'wb')
132 132 f.write('a')
133 133 f.close()
134 134 runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
135 135 runcommand(server, ['st', 'foo/bar'])
136 136 os.remove('foo/bar')
137 137
138 138 def localhgrc(server):
139 139 """ check that local configs for the cached repo aren't inherited when -R
140 140 is used """
141 141 readchannel(server)
142 142
143 143 # the cached repo local hgrc contains ui.foo=bar, so showconfig should show it
144 144 runcommand(server, ['showconfig'])
145 145
146 146 # but not for this repo
147 147 runcommand(server, ['init', 'foo'])
148 148 runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
149 149 shutil.rmtree('foo')
150 150
151 151 def hook(**args):
152 152 print 'hook talking'
153 153 print 'now try to read something: %r' % sys.stdin.read()
154 154
155 155 def hookoutput(server):
156 156 readchannel(server)
157 157 runcommand(server, ['--config',
158 158 'hooks.pre-identify=python:test-commandserver.hook', 'id'],
159 159 input=cStringIO.StringIO('some input'))
160 160
161 161 def outsidechanges(server):
162 162 readchannel(server)
163 163 f = open('a', 'ab')
164 164 f.write('a\n')
165 165 f.close()
166 runcommand(server, ['status'])
166 167 os.system('hg ci -Am2')
167 168 runcommand(server, ['tip'])
169 runcommand(server, ['status'])
168 170
169 171 def bookmarks(server):
170 172 readchannel(server)
171 173 runcommand(server, ['bookmarks'])
172 174
173 175 # changes .hg/bookmarks
174 176 os.system('hg bookmark -i bm1')
175 177 os.system('hg bookmark -i bm2')
176 178 runcommand(server, ['bookmarks'])
177 179
178 180 # changes .hg/bookmarks.current
179 181 os.system('hg upd bm1 -q')
180 182 runcommand(server, ['bookmarks'])
181 183
182 184 def tagscache(server):
183 185 readchannel(server)
184 186 runcommand(server, ['id', '-t', '-r', '0'])
185 187 os.system('hg tag -r 0 foo')
186 188 runcommand(server, ['id', '-t', '-r', '0'])
187 189
188 190 def setphase(server):
189 191 readchannel(server)
190 192 runcommand(server, ['phase', '-r', '.'])
191 193 os.system('hg phase -r . -p')
192 194 runcommand(server, ['phase', '-r', '.'])
193 195
194 196 if __name__ == '__main__':
195 197 os.system('hg init')
196 198
197 199 check(hellomessage)
198 200 check(unknowncommand)
199 201 check(checkruncommand)
200 202 check(inputeof)
201 203 check(serverinput)
202 204 check(cwd)
203 205
204 206 hgrc = open('.hg/hgrc', 'a')
205 207 hgrc.write('[ui]\nfoo=bar\n')
206 208 hgrc.close()
207 209 check(localhgrc)
208 210 check(hookoutput)
209 211 check(outsidechanges)
210 212 check(bookmarks)
211 213 check(tagscache)
212 214 check(setphase)
@@ -1,127 +1,130 b''
1 1
2 2 testing hellomessage:
3 3
4 4 o, 'capabilities: getencoding runcommand\nencoding: ***'
5 5 runcommand id
6 6 000000000000 tip
7 7 abort: unknown command unknowncommand
8 8
9 9 testing unknowncommand:
10 10
11 11
12 12 testing checkruncommand:
13 13
14 14 runcommand
15 15 Mercurial Distributed SCM
16 16
17 17 basic commands:
18 18
19 19 add add the specified files on the next commit
20 20 annotate show changeset information by line for each file
21 21 clone make a copy of an existing repository
22 22 commit commit the specified files or all outstanding changes
23 23 diff diff repository (or selected files)
24 24 export dump the header and diffs for one or more changesets
25 25 forget forget the specified files on the next commit
26 26 init create a new repository in the given directory
27 27 log show revision history of entire repository or files
28 28 merge merge working directory with another revision
29 29 phase set or show the current phase name
30 30 pull pull changes from the specified source
31 31 push push changes to the specified destination
32 32 remove remove the specified files on the next commit
33 33 serve start stand-alone webserver
34 34 status show changed files in the working directory
35 35 summary summarize working directory state
36 36 update update working directory (or switch revisions)
37 37
38 38 use "hg help" for the full list of commands or "hg -v" for details
39 39 runcommand id --quiet
40 40 000000000000
41 41 runcommand id
42 42 000000000000 tip
43 43 runcommand id --config ui.quiet=True
44 44 000000000000
45 45 runcommand id
46 46 000000000000 tip
47 47
48 48 testing inputeof:
49 49
50 50 server exit code = 1
51 51
52 52 testing serverinput:
53 53
54 54 runcommand import -
55 55 applying patch from stdin
56 56 runcommand log
57 57 changeset: 0:eff892de26ec
58 58 tag: tip
59 59 user: test
60 60 date: Thu Jan 01 00:00:00 1970 +0000
61 61 summary: 1
62 62
63 63
64 64 testing cwd:
65 65
66 66 runcommand --cwd foo st bar
67 67 ? bar
68 68 runcommand st foo/bar
69 69 ? foo/bar
70 70
71 71 testing localhgrc:
72 72
73 73 runcommand showconfig
74 74 bundle.mainreporoot=$TESTTMP
75 75 defaults.backout=-d "0 0"
76 76 defaults.commit=-d "0 0"
77 77 defaults.tag=-d "0 0"
78 78 ui.slash=True
79 79 ui.foo=bar
80 80 runcommand init foo
81 81 runcommand -R foo showconfig ui defaults
82 82 defaults.backout=-d "0 0"
83 83 defaults.commit=-d "0 0"
84 84 defaults.tag=-d "0 0"
85 85 ui.slash=True
86 86
87 87 testing hookoutput:
88 88
89 89 runcommand --config hooks.pre-identify=python:test-commandserver.hook id
90 90 hook talking
91 91 now try to read something: 'some input'
92 92 eff892de26ec tip
93 93
94 94 testing outsidechanges:
95 95
96 runcommand status
97 M a
96 98 runcommand tip
97 99 changeset: 1:d3a0a68be6de
98 100 tag: tip
99 101 user: test
100 102 date: Thu Jan 01 00:00:00 1970 +0000
101 103 summary: 2
102 104
105 runcommand status
103 106
104 107 testing bookmarks:
105 108
106 109 runcommand bookmarks
107 110 no bookmarks set
108 111 runcommand bookmarks
109 112 bm1 1:d3a0a68be6de
110 113 bm2 1:d3a0a68be6de
111 114 runcommand bookmarks
112 115 * bm1 1:d3a0a68be6de
113 116 bm2 1:d3a0a68be6de
114 117
115 118 testing tagscache:
116 119
117 120 runcommand id -t -r 0
118 121
119 122 runcommand id -t -r 0
120 123 foo
121 124
122 125 testing setphase:
123 126
124 127 runcommand phase -r .
125 128 2: draft
126 129 runcommand phase -r .
127 130 2: public
General Comments 0
You need to be logged in to leave comments. Login now