##// END OF EJS Templates
py3: fix EOL detection in commandserver.channeledinput...
Yuya Nishihara -
r44857:98c14f01 stable
parent child Browse files
Show More
@@ -1,727 +1,727 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 __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import gc
11 import gc
12 import os
12 import os
13 import random
13 import random
14 import signal
14 import signal
15 import socket
15 import socket
16 import struct
16 import struct
17 import traceback
17 import traceback
18
18
19 try:
19 try:
20 import selectors
20 import selectors
21
21
22 selectors.BaseSelector
22 selectors.BaseSelector
23 except ImportError:
23 except ImportError:
24 from .thirdparty import selectors2 as selectors
24 from .thirdparty import selectors2 as selectors
25
25
26 from .i18n import _
26 from .i18n import _
27 from .pycompat import getattr
27 from .pycompat import getattr
28 from . import (
28 from . import (
29 encoding,
29 encoding,
30 error,
30 error,
31 loggingutil,
31 loggingutil,
32 pycompat,
32 pycompat,
33 repocache,
33 repocache,
34 util,
34 util,
35 vfs as vfsmod,
35 vfs as vfsmod,
36 )
36 )
37 from .utils import (
37 from .utils import (
38 cborutil,
38 cborutil,
39 procutil,
39 procutil,
40 )
40 )
41
41
42
42
43 class channeledoutput(object):
43 class channeledoutput(object):
44 """
44 """
45 Write data to out in the following format:
45 Write data to out in the following format:
46
46
47 data length (unsigned int),
47 data length (unsigned int),
48 data
48 data
49 """
49 """
50
50
51 def __init__(self, out, channel):
51 def __init__(self, out, channel):
52 self.out = out
52 self.out = out
53 self.channel = channel
53 self.channel = channel
54
54
55 @property
55 @property
56 def name(self):
56 def name(self):
57 return b'<%c-channel>' % self.channel
57 return b'<%c-channel>' % self.channel
58
58
59 def write(self, data):
59 def write(self, data):
60 if not data:
60 if not data:
61 return
61 return
62 # single write() to guarantee the same atomicity as the underlying file
62 # single write() to guarantee the same atomicity as the underlying file
63 self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data)
63 self.out.write(struct.pack(b'>cI', self.channel, len(data)) + data)
64 self.out.flush()
64 self.out.flush()
65
65
66 def __getattr__(self, attr):
66 def __getattr__(self, attr):
67 if attr in ('isatty', 'fileno', 'tell', 'seek'):
67 if attr in ('isatty', 'fileno', 'tell', 'seek'):
68 raise AttributeError(attr)
68 raise AttributeError(attr)
69 return getattr(self.out, attr)
69 return getattr(self.out, attr)
70
70
71
71
72 class channeledmessage(object):
72 class channeledmessage(object):
73 """
73 """
74 Write encoded message and metadata to out in the following format:
74 Write encoded message and metadata to out in the following format:
75
75
76 data length (unsigned int),
76 data length (unsigned int),
77 encoded message and metadata, as a flat key-value dict.
77 encoded message and metadata, as a flat key-value dict.
78
78
79 Each message should have 'type' attribute. Messages of unknown type
79 Each message should have 'type' attribute. Messages of unknown type
80 should be ignored.
80 should be ignored.
81 """
81 """
82
82
83 # teach ui that write() can take **opts
83 # teach ui that write() can take **opts
84 structured = True
84 structured = True
85
85
86 def __init__(self, out, channel, encodename, encodefn):
86 def __init__(self, out, channel, encodename, encodefn):
87 self._cout = channeledoutput(out, channel)
87 self._cout = channeledoutput(out, channel)
88 self.encoding = encodename
88 self.encoding = encodename
89 self._encodefn = encodefn
89 self._encodefn = encodefn
90
90
91 def write(self, data, **opts):
91 def write(self, data, **opts):
92 opts = pycompat.byteskwargs(opts)
92 opts = pycompat.byteskwargs(opts)
93 if data is not None:
93 if data is not None:
94 opts[b'data'] = data
94 opts[b'data'] = data
95 self._cout.write(self._encodefn(opts))
95 self._cout.write(self._encodefn(opts))
96
96
97 def __getattr__(self, attr):
97 def __getattr__(self, attr):
98 return getattr(self._cout, attr)
98 return getattr(self._cout, attr)
99
99
100
100
101 class channeledinput(object):
101 class channeledinput(object):
102 """
102 """
103 Read data from in_.
103 Read data from in_.
104
104
105 Requests for input are written to out in the following format:
105 Requests for input are written to out in the following format:
106 channel identifier - 'I' for plain input, 'L' line based (1 byte)
106 channel identifier - 'I' for plain input, 'L' line based (1 byte)
107 how many bytes to send at most (unsigned int),
107 how many bytes to send at most (unsigned int),
108
108
109 The client replies with:
109 The client replies with:
110 data length (unsigned int), 0 meaning EOF
110 data length (unsigned int), 0 meaning EOF
111 data
111 data
112 """
112 """
113
113
114 maxchunksize = 4 * 1024
114 maxchunksize = 4 * 1024
115
115
116 def __init__(self, in_, out, channel):
116 def __init__(self, in_, out, channel):
117 self.in_ = in_
117 self.in_ = in_
118 self.out = out
118 self.out = out
119 self.channel = channel
119 self.channel = channel
120
120
121 @property
121 @property
122 def name(self):
122 def name(self):
123 return b'<%c-channel>' % self.channel
123 return b'<%c-channel>' % self.channel
124
124
125 def read(self, size=-1):
125 def read(self, size=-1):
126 if size < 0:
126 if size < 0:
127 # if we need to consume all the clients input, ask for 4k chunks
127 # if we need to consume all the clients input, ask for 4k chunks
128 # so the pipe doesn't fill up risking a deadlock
128 # so the pipe doesn't fill up risking a deadlock
129 size = self.maxchunksize
129 size = self.maxchunksize
130 s = self._read(size, self.channel)
130 s = self._read(size, self.channel)
131 buf = s
131 buf = s
132 while s:
132 while s:
133 s = self._read(size, self.channel)
133 s = self._read(size, self.channel)
134 buf += s
134 buf += s
135
135
136 return buf
136 return buf
137 else:
137 else:
138 return self._read(size, self.channel)
138 return self._read(size, self.channel)
139
139
140 def _read(self, size, channel):
140 def _read(self, size, channel):
141 if not size:
141 if not size:
142 return b''
142 return b''
143 assert size > 0
143 assert size > 0
144
144
145 # tell the client we need at most size bytes
145 # tell the client we need at most size bytes
146 self.out.write(struct.pack(b'>cI', channel, size))
146 self.out.write(struct.pack(b'>cI', channel, size))
147 self.out.flush()
147 self.out.flush()
148
148
149 length = self.in_.read(4)
149 length = self.in_.read(4)
150 length = struct.unpack(b'>I', length)[0]
150 length = struct.unpack(b'>I', length)[0]
151 if not length:
151 if not length:
152 return b''
152 return b''
153 else:
153 else:
154 return self.in_.read(length)
154 return self.in_.read(length)
155
155
156 def readline(self, size=-1):
156 def readline(self, size=-1):
157 if size < 0:
157 if size < 0:
158 size = self.maxchunksize
158 size = self.maxchunksize
159 s = self._read(size, b'L')
159 s = self._read(size, b'L')
160 buf = s
160 buf = s
161 # keep asking for more until there's either no more or
161 # keep asking for more until there's either no more or
162 # we got a full line
162 # we got a full line
163 while s and s[-1] != b'\n':
163 while s and not s.endswith(b'\n'):
164 s = self._read(size, b'L')
164 s = self._read(size, b'L')
165 buf += s
165 buf += s
166
166
167 return buf
167 return buf
168 else:
168 else:
169 return self._read(size, b'L')
169 return self._read(size, b'L')
170
170
171 def __iter__(self):
171 def __iter__(self):
172 return self
172 return self
173
173
174 def next(self):
174 def next(self):
175 l = self.readline()
175 l = self.readline()
176 if not l:
176 if not l:
177 raise StopIteration
177 raise StopIteration
178 return l
178 return l
179
179
180 __next__ = next
180 __next__ = next
181
181
182 def __getattr__(self, attr):
182 def __getattr__(self, attr):
183 if attr in ('isatty', 'fileno', 'tell', 'seek'):
183 if attr in ('isatty', 'fileno', 'tell', 'seek'):
184 raise AttributeError(attr)
184 raise AttributeError(attr)
185 return getattr(self.in_, attr)
185 return getattr(self.in_, attr)
186
186
187
187
188 _messageencoders = {
188 _messageencoders = {
189 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
189 b'cbor': lambda v: b''.join(cborutil.streamencode(v)),
190 }
190 }
191
191
192
192
193 def _selectmessageencoder(ui):
193 def _selectmessageencoder(ui):
194 # experimental config: cmdserver.message-encodings
194 # experimental config: cmdserver.message-encodings
195 encnames = ui.configlist(b'cmdserver', b'message-encodings')
195 encnames = ui.configlist(b'cmdserver', b'message-encodings')
196 for n in encnames:
196 for n in encnames:
197 f = _messageencoders.get(n)
197 f = _messageencoders.get(n)
198 if f:
198 if f:
199 return n, f
199 return n, f
200 raise error.Abort(
200 raise error.Abort(
201 b'no supported message encodings: %s' % b' '.join(encnames)
201 b'no supported message encodings: %s' % b' '.join(encnames)
202 )
202 )
203
203
204
204
205 class server(object):
205 class server(object):
206 """
206 """
207 Listens for commands on fin, runs them and writes the output on a channel
207 Listens for commands on fin, runs them and writes the output on a channel
208 based stream to fout.
208 based stream to fout.
209 """
209 """
210
210
211 def __init__(self, ui, repo, fin, fout, prereposetups=None):
211 def __init__(self, ui, repo, fin, fout, prereposetups=None):
212 self.cwd = encoding.getcwd()
212 self.cwd = encoding.getcwd()
213
213
214 if repo:
214 if repo:
215 # the ui here is really the repo ui so take its baseui so we don't
215 # the ui here is really the repo ui so take its baseui so we don't
216 # end up with its local configuration
216 # end up with its local configuration
217 self.ui = repo.baseui
217 self.ui = repo.baseui
218 self.repo = repo
218 self.repo = repo
219 self.repoui = repo.ui
219 self.repoui = repo.ui
220 else:
220 else:
221 self.ui = ui
221 self.ui = ui
222 self.repo = self.repoui = None
222 self.repo = self.repoui = None
223 self._prereposetups = prereposetups
223 self._prereposetups = prereposetups
224
224
225 self.cdebug = channeledoutput(fout, b'd')
225 self.cdebug = channeledoutput(fout, b'd')
226 self.cerr = channeledoutput(fout, b'e')
226 self.cerr = channeledoutput(fout, b'e')
227 self.cout = channeledoutput(fout, b'o')
227 self.cout = channeledoutput(fout, b'o')
228 self.cin = channeledinput(fin, fout, b'I')
228 self.cin = channeledinput(fin, fout, b'I')
229 self.cresult = channeledoutput(fout, b'r')
229 self.cresult = channeledoutput(fout, b'r')
230
230
231 if self.ui.config(b'cmdserver', b'log') == b'-':
231 if self.ui.config(b'cmdserver', b'log') == b'-':
232 # switch log stream of server's ui to the 'd' (debug) channel
232 # switch log stream of server's ui to the 'd' (debug) channel
233 # (don't touch repo.ui as its lifetime is longer than the server)
233 # (don't touch repo.ui as its lifetime is longer than the server)
234 self.ui = self.ui.copy()
234 self.ui = self.ui.copy()
235 setuplogging(self.ui, repo=None, fp=self.cdebug)
235 setuplogging(self.ui, repo=None, fp=self.cdebug)
236
236
237 # TODO: add this to help/config.txt when stabilized
237 # TODO: add this to help/config.txt when stabilized
238 # ``channel``
238 # ``channel``
239 # Use separate channel for structured output. (Command-server only)
239 # Use separate channel for structured output. (Command-server only)
240 self.cmsg = None
240 self.cmsg = None
241 if ui.config(b'ui', b'message-output') == b'channel':
241 if ui.config(b'ui', b'message-output') == b'channel':
242 encname, encfn = _selectmessageencoder(ui)
242 encname, encfn = _selectmessageencoder(ui)
243 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
243 self.cmsg = channeledmessage(fout, b'm', encname, encfn)
244
244
245 self.client = fin
245 self.client = fin
246
246
247 def cleanup(self):
247 def cleanup(self):
248 """release and restore resources taken during server session"""
248 """release and restore resources taken during server session"""
249
249
250 def _read(self, size):
250 def _read(self, size):
251 if not size:
251 if not size:
252 return b''
252 return b''
253
253
254 data = self.client.read(size)
254 data = self.client.read(size)
255
255
256 # is the other end closed?
256 # is the other end closed?
257 if not data:
257 if not data:
258 raise EOFError
258 raise EOFError
259
259
260 return data
260 return data
261
261
262 def _readstr(self):
262 def _readstr(self):
263 """read a string from the channel
263 """read a string from the channel
264
264
265 format:
265 format:
266 data length (uint32), data
266 data length (uint32), data
267 """
267 """
268 length = struct.unpack(b'>I', self._read(4))[0]
268 length = struct.unpack(b'>I', self._read(4))[0]
269 if not length:
269 if not length:
270 return b''
270 return b''
271 return self._read(length)
271 return self._read(length)
272
272
273 def _readlist(self):
273 def _readlist(self):
274 """read a list of NULL separated strings from the channel"""
274 """read a list of NULL separated strings from the channel"""
275 s = self._readstr()
275 s = self._readstr()
276 if s:
276 if s:
277 return s.split(b'\0')
277 return s.split(b'\0')
278 else:
278 else:
279 return []
279 return []
280
280
281 def runcommand(self):
281 def runcommand(self):
282 """ reads a list of \0 terminated arguments, executes
282 """ reads a list of \0 terminated arguments, executes
283 and writes the return code to the result channel """
283 and writes the return code to the result channel """
284 from . import dispatch # avoid cycle
284 from . import dispatch # avoid cycle
285
285
286 args = self._readlist()
286 args = self._readlist()
287
287
288 # copy the uis so changes (e.g. --config or --verbose) don't
288 # copy the uis so changes (e.g. --config or --verbose) don't
289 # persist between requests
289 # persist between requests
290 copiedui = self.ui.copy()
290 copiedui = self.ui.copy()
291 uis = [copiedui]
291 uis = [copiedui]
292 if self.repo:
292 if self.repo:
293 self.repo.baseui = copiedui
293 self.repo.baseui = copiedui
294 # clone ui without using ui.copy because this is protected
294 # clone ui without using ui.copy because this is protected
295 repoui = self.repoui.__class__(self.repoui)
295 repoui = self.repoui.__class__(self.repoui)
296 repoui.copy = copiedui.copy # redo copy protection
296 repoui.copy = copiedui.copy # redo copy protection
297 uis.append(repoui)
297 uis.append(repoui)
298 self.repo.ui = self.repo.dirstate._ui = repoui
298 self.repo.ui = self.repo.dirstate._ui = repoui
299 self.repo.invalidateall()
299 self.repo.invalidateall()
300
300
301 for ui in uis:
301 for ui in uis:
302 ui.resetstate()
302 ui.resetstate()
303 # any kind of interaction must use server channels, but chg may
303 # any kind of interaction must use server channels, but chg may
304 # replace channels by fully functional tty files. so nontty is
304 # replace channels by fully functional tty files. so nontty is
305 # enforced only if cin is a channel.
305 # enforced only if cin is a channel.
306 if not util.safehasattr(self.cin, b'fileno'):
306 if not util.safehasattr(self.cin, b'fileno'):
307 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver')
307 ui.setconfig(b'ui', b'nontty', b'true', b'commandserver')
308
308
309 req = dispatch.request(
309 req = dispatch.request(
310 args[:],
310 args[:],
311 copiedui,
311 copiedui,
312 self.repo,
312 self.repo,
313 self.cin,
313 self.cin,
314 self.cout,
314 self.cout,
315 self.cerr,
315 self.cerr,
316 self.cmsg,
316 self.cmsg,
317 prereposetups=self._prereposetups,
317 prereposetups=self._prereposetups,
318 )
318 )
319
319
320 try:
320 try:
321 ret = dispatch.dispatch(req) & 255
321 ret = dispatch.dispatch(req) & 255
322 self.cresult.write(struct.pack(b'>i', int(ret)))
322 self.cresult.write(struct.pack(b'>i', int(ret)))
323 finally:
323 finally:
324 # restore old cwd
324 # restore old cwd
325 if b'--cwd' in args:
325 if b'--cwd' in args:
326 os.chdir(self.cwd)
326 os.chdir(self.cwd)
327
327
328 def getencoding(self):
328 def getencoding(self):
329 """ writes the current encoding to the result channel """
329 """ writes the current encoding to the result channel """
330 self.cresult.write(encoding.encoding)
330 self.cresult.write(encoding.encoding)
331
331
332 def serveone(self):
332 def serveone(self):
333 cmd = self.client.readline()[:-1]
333 cmd = self.client.readline()[:-1]
334 if cmd:
334 if cmd:
335 handler = self.capabilities.get(cmd)
335 handler = self.capabilities.get(cmd)
336 if handler:
336 if handler:
337 handler(self)
337 handler(self)
338 else:
338 else:
339 # clients are expected to check what commands are supported by
339 # clients are expected to check what commands are supported by
340 # looking at the servers capabilities
340 # looking at the servers capabilities
341 raise error.Abort(_(b'unknown command %s') % cmd)
341 raise error.Abort(_(b'unknown command %s') % cmd)
342
342
343 return cmd != b''
343 return cmd != b''
344
344
345 capabilities = {b'runcommand': runcommand, b'getencoding': getencoding}
345 capabilities = {b'runcommand': runcommand, b'getencoding': getencoding}
346
346
347 def serve(self):
347 def serve(self):
348 hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities))
348 hellomsg = b'capabilities: ' + b' '.join(sorted(self.capabilities))
349 hellomsg += b'\n'
349 hellomsg += b'\n'
350 hellomsg += b'encoding: ' + encoding.encoding
350 hellomsg += b'encoding: ' + encoding.encoding
351 hellomsg += b'\n'
351 hellomsg += b'\n'
352 if self.cmsg:
352 if self.cmsg:
353 hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding
353 hellomsg += b'message-encoding: %s\n' % self.cmsg.encoding
354 hellomsg += b'pid: %d' % procutil.getpid()
354 hellomsg += b'pid: %d' % procutil.getpid()
355 if util.safehasattr(os, b'getpgid'):
355 if util.safehasattr(os, b'getpgid'):
356 hellomsg += b'\n'
356 hellomsg += b'\n'
357 hellomsg += b'pgid: %d' % os.getpgid(0)
357 hellomsg += b'pgid: %d' % os.getpgid(0)
358
358
359 # write the hello msg in -one- chunk
359 # write the hello msg in -one- chunk
360 self.cout.write(hellomsg)
360 self.cout.write(hellomsg)
361
361
362 try:
362 try:
363 while self.serveone():
363 while self.serveone():
364 pass
364 pass
365 except EOFError:
365 except EOFError:
366 # we'll get here if the client disconnected while we were reading
366 # we'll get here if the client disconnected while we were reading
367 # its request
367 # its request
368 return 1
368 return 1
369
369
370 return 0
370 return 0
371
371
372
372
373 def setuplogging(ui, repo=None, fp=None):
373 def setuplogging(ui, repo=None, fp=None):
374 """Set up server logging facility
374 """Set up server logging facility
375
375
376 If cmdserver.log is '-', log messages will be sent to the given fp.
376 If cmdserver.log is '-', log messages will be sent to the given fp.
377 It should be the 'd' channel while a client is connected, and otherwise
377 It should be the 'd' channel while a client is connected, and otherwise
378 is the stderr of the server process.
378 is the stderr of the server process.
379 """
379 """
380 # developer config: cmdserver.log
380 # developer config: cmdserver.log
381 logpath = ui.config(b'cmdserver', b'log')
381 logpath = ui.config(b'cmdserver', b'log')
382 if not logpath:
382 if not logpath:
383 return
383 return
384 # developer config: cmdserver.track-log
384 # developer config: cmdserver.track-log
385 tracked = set(ui.configlist(b'cmdserver', b'track-log'))
385 tracked = set(ui.configlist(b'cmdserver', b'track-log'))
386
386
387 if logpath == b'-' and fp:
387 if logpath == b'-' and fp:
388 logger = loggingutil.fileobjectlogger(fp, tracked)
388 logger = loggingutil.fileobjectlogger(fp, tracked)
389 elif logpath == b'-':
389 elif logpath == b'-':
390 logger = loggingutil.fileobjectlogger(ui.ferr, tracked)
390 logger = loggingutil.fileobjectlogger(ui.ferr, tracked)
391 else:
391 else:
392 logpath = os.path.abspath(util.expandpath(logpath))
392 logpath = os.path.abspath(util.expandpath(logpath))
393 # developer config: cmdserver.max-log-files
393 # developer config: cmdserver.max-log-files
394 maxfiles = ui.configint(b'cmdserver', b'max-log-files')
394 maxfiles = ui.configint(b'cmdserver', b'max-log-files')
395 # developer config: cmdserver.max-log-size
395 # developer config: cmdserver.max-log-size
396 maxsize = ui.configbytes(b'cmdserver', b'max-log-size')
396 maxsize = ui.configbytes(b'cmdserver', b'max-log-size')
397 vfs = vfsmod.vfs(os.path.dirname(logpath))
397 vfs = vfsmod.vfs(os.path.dirname(logpath))
398 logger = loggingutil.filelogger(
398 logger = loggingutil.filelogger(
399 vfs,
399 vfs,
400 os.path.basename(logpath),
400 os.path.basename(logpath),
401 tracked,
401 tracked,
402 maxfiles=maxfiles,
402 maxfiles=maxfiles,
403 maxsize=maxsize,
403 maxsize=maxsize,
404 )
404 )
405
405
406 targetuis = {ui}
406 targetuis = {ui}
407 if repo:
407 if repo:
408 targetuis.add(repo.baseui)
408 targetuis.add(repo.baseui)
409 targetuis.add(repo.ui)
409 targetuis.add(repo.ui)
410 for u in targetuis:
410 for u in targetuis:
411 u.setlogger(b'cmdserver', logger)
411 u.setlogger(b'cmdserver', logger)
412
412
413
413
414 class pipeservice(object):
414 class pipeservice(object):
415 def __init__(self, ui, repo, opts):
415 def __init__(self, ui, repo, opts):
416 self.ui = ui
416 self.ui = ui
417 self.repo = repo
417 self.repo = repo
418
418
419 def init(self):
419 def init(self):
420 pass
420 pass
421
421
422 def run(self):
422 def run(self):
423 ui = self.ui
423 ui = self.ui
424 # redirect stdio to null device so that broken extensions or in-process
424 # redirect stdio to null device so that broken extensions or in-process
425 # hooks will never cause corruption of channel protocol.
425 # hooks will never cause corruption of channel protocol.
426 with ui.protectedfinout() as (fin, fout):
426 with ui.protectedfinout() as (fin, fout):
427 sv = server(ui, self.repo, fin, fout)
427 sv = server(ui, self.repo, fin, fout)
428 try:
428 try:
429 return sv.serve()
429 return sv.serve()
430 finally:
430 finally:
431 sv.cleanup()
431 sv.cleanup()
432
432
433
433
434 def _initworkerprocess():
434 def _initworkerprocess():
435 # use a different process group from the master process, in order to:
435 # use a different process group from the master process, in order to:
436 # 1. make the current process group no longer "orphaned" (because the
436 # 1. make the current process group no longer "orphaned" (because the
437 # parent of this process is in a different process group while
437 # parent of this process is in a different process group while
438 # remains in a same session)
438 # remains in a same session)
439 # according to POSIX 2.2.2.52, orphaned process group will ignore
439 # according to POSIX 2.2.2.52, orphaned process group will ignore
440 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
440 # terminal-generated stop signals like SIGTSTP (Ctrl+Z), which will
441 # cause trouble for things like ncurses.
441 # cause trouble for things like ncurses.
442 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
442 # 2. the client can use kill(-pgid, sig) to simulate terminal-generated
443 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
443 # SIGINT (Ctrl+C) and process-exit-generated SIGHUP. our child
444 # processes like ssh will be killed properly, without affecting
444 # processes like ssh will be killed properly, without affecting
445 # unrelated processes.
445 # unrelated processes.
446 os.setpgid(0, 0)
446 os.setpgid(0, 0)
447 # change random state otherwise forked request handlers would have a
447 # change random state otherwise forked request handlers would have a
448 # same state inherited from parent.
448 # same state inherited from parent.
449 random.seed()
449 random.seed()
450
450
451
451
452 def _serverequest(ui, repo, conn, createcmdserver, prereposetups):
452 def _serverequest(ui, repo, conn, createcmdserver, prereposetups):
453 fin = conn.makefile('rb')
453 fin = conn.makefile('rb')
454 fout = conn.makefile('wb')
454 fout = conn.makefile('wb')
455 sv = None
455 sv = None
456 try:
456 try:
457 sv = createcmdserver(repo, conn, fin, fout, prereposetups)
457 sv = createcmdserver(repo, conn, fin, fout, prereposetups)
458 try:
458 try:
459 sv.serve()
459 sv.serve()
460 # handle exceptions that may be raised by command server. most of
460 # handle exceptions that may be raised by command server. most of
461 # known exceptions are caught by dispatch.
461 # known exceptions are caught by dispatch.
462 except error.Abort as inst:
462 except error.Abort as inst:
463 ui.error(_(b'abort: %s\n') % inst)
463 ui.error(_(b'abort: %s\n') % inst)
464 except IOError as inst:
464 except IOError as inst:
465 if inst.errno != errno.EPIPE:
465 if inst.errno != errno.EPIPE:
466 raise
466 raise
467 except KeyboardInterrupt:
467 except KeyboardInterrupt:
468 pass
468 pass
469 finally:
469 finally:
470 sv.cleanup()
470 sv.cleanup()
471 except: # re-raises
471 except: # re-raises
472 # also write traceback to error channel. otherwise client cannot
472 # also write traceback to error channel. otherwise client cannot
473 # see it because it is written to server's stderr by default.
473 # see it because it is written to server's stderr by default.
474 if sv:
474 if sv:
475 cerr = sv.cerr
475 cerr = sv.cerr
476 else:
476 else:
477 cerr = channeledoutput(fout, b'e')
477 cerr = channeledoutput(fout, b'e')
478 cerr.write(encoding.strtolocal(traceback.format_exc()))
478 cerr.write(encoding.strtolocal(traceback.format_exc()))
479 raise
479 raise
480 finally:
480 finally:
481 fin.close()
481 fin.close()
482 try:
482 try:
483 fout.close() # implicit flush() may cause another EPIPE
483 fout.close() # implicit flush() may cause another EPIPE
484 except IOError as inst:
484 except IOError as inst:
485 if inst.errno != errno.EPIPE:
485 if inst.errno != errno.EPIPE:
486 raise
486 raise
487
487
488
488
489 class unixservicehandler(object):
489 class unixservicehandler(object):
490 """Set of pluggable operations for unix-mode services
490 """Set of pluggable operations for unix-mode services
491
491
492 Almost all methods except for createcmdserver() are called in the main
492 Almost all methods except for createcmdserver() are called in the main
493 process. You can't pass mutable resource back from createcmdserver().
493 process. You can't pass mutable resource back from createcmdserver().
494 """
494 """
495
495
496 pollinterval = None
496 pollinterval = None
497
497
498 def __init__(self, ui):
498 def __init__(self, ui):
499 self.ui = ui
499 self.ui = ui
500
500
501 def bindsocket(self, sock, address):
501 def bindsocket(self, sock, address):
502 util.bindunixsocket(sock, address)
502 util.bindunixsocket(sock, address)
503 sock.listen(socket.SOMAXCONN)
503 sock.listen(socket.SOMAXCONN)
504 self.ui.status(_(b'listening at %s\n') % address)
504 self.ui.status(_(b'listening at %s\n') % address)
505 self.ui.flush() # avoid buffering of status message
505 self.ui.flush() # avoid buffering of status message
506
506
507 def unlinksocket(self, address):
507 def unlinksocket(self, address):
508 os.unlink(address)
508 os.unlink(address)
509
509
510 def shouldexit(self):
510 def shouldexit(self):
511 """True if server should shut down; checked per pollinterval"""
511 """True if server should shut down; checked per pollinterval"""
512 return False
512 return False
513
513
514 def newconnection(self):
514 def newconnection(self):
515 """Called when main process notices new connection"""
515 """Called when main process notices new connection"""
516
516
517 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
517 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
518 """Create new command server instance; called in the process that
518 """Create new command server instance; called in the process that
519 serves for the current connection"""
519 serves for the current connection"""
520 return server(self.ui, repo, fin, fout, prereposetups)
520 return server(self.ui, repo, fin, fout, prereposetups)
521
521
522
522
523 class unixforkingservice(object):
523 class unixforkingservice(object):
524 """
524 """
525 Listens on unix domain socket and forks server per connection
525 Listens on unix domain socket and forks server per connection
526 """
526 """
527
527
528 def __init__(self, ui, repo, opts, handler=None):
528 def __init__(self, ui, repo, opts, handler=None):
529 self.ui = ui
529 self.ui = ui
530 self.repo = repo
530 self.repo = repo
531 self.address = opts[b'address']
531 self.address = opts[b'address']
532 if not util.safehasattr(socket, b'AF_UNIX'):
532 if not util.safehasattr(socket, b'AF_UNIX'):
533 raise error.Abort(_(b'unsupported platform'))
533 raise error.Abort(_(b'unsupported platform'))
534 if not self.address:
534 if not self.address:
535 raise error.Abort(_(b'no socket path specified with --address'))
535 raise error.Abort(_(b'no socket path specified with --address'))
536 self._servicehandler = handler or unixservicehandler(ui)
536 self._servicehandler = handler or unixservicehandler(ui)
537 self._sock = None
537 self._sock = None
538 self._mainipc = None
538 self._mainipc = None
539 self._workeripc = None
539 self._workeripc = None
540 self._oldsigchldhandler = None
540 self._oldsigchldhandler = None
541 self._workerpids = set() # updated by signal handler; do not iterate
541 self._workerpids = set() # updated by signal handler; do not iterate
542 self._socketunlinked = None
542 self._socketunlinked = None
543 # experimental config: cmdserver.max-repo-cache
543 # experimental config: cmdserver.max-repo-cache
544 maxlen = ui.configint(b'cmdserver', b'max-repo-cache')
544 maxlen = ui.configint(b'cmdserver', b'max-repo-cache')
545 if maxlen < 0:
545 if maxlen < 0:
546 raise error.Abort(_(b'negative max-repo-cache size not allowed'))
546 raise error.Abort(_(b'negative max-repo-cache size not allowed'))
547 self._repoloader = repocache.repoloader(ui, maxlen)
547 self._repoloader = repocache.repoloader(ui, maxlen)
548
548
549 def init(self):
549 def init(self):
550 self._sock = socket.socket(socket.AF_UNIX)
550 self._sock = socket.socket(socket.AF_UNIX)
551 # IPC channel from many workers to one main process; this is actually
551 # IPC channel from many workers to one main process; this is actually
552 # a uni-directional pipe, but is backed by a DGRAM socket so each
552 # a uni-directional pipe, but is backed by a DGRAM socket so each
553 # message can be easily separated.
553 # message can be easily separated.
554 o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
554 o = socket.socketpair(socket.AF_UNIX, socket.SOCK_DGRAM)
555 self._mainipc, self._workeripc = o
555 self._mainipc, self._workeripc = o
556 self._servicehandler.bindsocket(self._sock, self.address)
556 self._servicehandler.bindsocket(self._sock, self.address)
557 if util.safehasattr(procutil, b'unblocksignal'):
557 if util.safehasattr(procutil, b'unblocksignal'):
558 procutil.unblocksignal(signal.SIGCHLD)
558 procutil.unblocksignal(signal.SIGCHLD)
559 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
559 o = signal.signal(signal.SIGCHLD, self._sigchldhandler)
560 self._oldsigchldhandler = o
560 self._oldsigchldhandler = o
561 self._socketunlinked = False
561 self._socketunlinked = False
562 self._repoloader.start()
562 self._repoloader.start()
563
563
564 def _unlinksocket(self):
564 def _unlinksocket(self):
565 if not self._socketunlinked:
565 if not self._socketunlinked:
566 self._servicehandler.unlinksocket(self.address)
566 self._servicehandler.unlinksocket(self.address)
567 self._socketunlinked = True
567 self._socketunlinked = True
568
568
569 def _cleanup(self):
569 def _cleanup(self):
570 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
570 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
571 self._sock.close()
571 self._sock.close()
572 self._mainipc.close()
572 self._mainipc.close()
573 self._workeripc.close()
573 self._workeripc.close()
574 self._unlinksocket()
574 self._unlinksocket()
575 self._repoloader.stop()
575 self._repoloader.stop()
576 # don't kill child processes as they have active clients, just wait
576 # don't kill child processes as they have active clients, just wait
577 self._reapworkers(0)
577 self._reapworkers(0)
578
578
579 def run(self):
579 def run(self):
580 try:
580 try:
581 self._mainloop()
581 self._mainloop()
582 finally:
582 finally:
583 self._cleanup()
583 self._cleanup()
584
584
585 def _mainloop(self):
585 def _mainloop(self):
586 exiting = False
586 exiting = False
587 h = self._servicehandler
587 h = self._servicehandler
588 selector = selectors.DefaultSelector()
588 selector = selectors.DefaultSelector()
589 selector.register(
589 selector.register(
590 self._sock, selectors.EVENT_READ, self._acceptnewconnection
590 self._sock, selectors.EVENT_READ, self._acceptnewconnection
591 )
591 )
592 selector.register(
592 selector.register(
593 self._mainipc, selectors.EVENT_READ, self._handlemainipc
593 self._mainipc, selectors.EVENT_READ, self._handlemainipc
594 )
594 )
595 while True:
595 while True:
596 if not exiting and h.shouldexit():
596 if not exiting and h.shouldexit():
597 # clients can no longer connect() to the domain socket, so
597 # clients can no longer connect() to the domain socket, so
598 # we stop queuing new requests.
598 # we stop queuing new requests.
599 # for requests that are queued (connect()-ed, but haven't been
599 # for requests that are queued (connect()-ed, but haven't been
600 # accept()-ed), handle them before exit. otherwise, clients
600 # accept()-ed), handle them before exit. otherwise, clients
601 # waiting for recv() will receive ECONNRESET.
601 # waiting for recv() will receive ECONNRESET.
602 self._unlinksocket()
602 self._unlinksocket()
603 exiting = True
603 exiting = True
604 try:
604 try:
605 events = selector.select(timeout=h.pollinterval)
605 events = selector.select(timeout=h.pollinterval)
606 except OSError as inst:
606 except OSError as inst:
607 # selectors2 raises ETIMEDOUT if timeout exceeded while
607 # selectors2 raises ETIMEDOUT if timeout exceeded while
608 # handling signal interrupt. That's probably wrong, but
608 # handling signal interrupt. That's probably wrong, but
609 # we can easily get around it.
609 # we can easily get around it.
610 if inst.errno != errno.ETIMEDOUT:
610 if inst.errno != errno.ETIMEDOUT:
611 raise
611 raise
612 events = []
612 events = []
613 if not events:
613 if not events:
614 # only exit if we completed all queued requests
614 # only exit if we completed all queued requests
615 if exiting:
615 if exiting:
616 break
616 break
617 continue
617 continue
618 for key, _mask in events:
618 for key, _mask in events:
619 key.data(key.fileobj, selector)
619 key.data(key.fileobj, selector)
620 selector.close()
620 selector.close()
621
621
622 def _acceptnewconnection(self, sock, selector):
622 def _acceptnewconnection(self, sock, selector):
623 h = self._servicehandler
623 h = self._servicehandler
624 try:
624 try:
625 conn, _addr = sock.accept()
625 conn, _addr = sock.accept()
626 except socket.error as inst:
626 except socket.error as inst:
627 if inst.args[0] == errno.EINTR:
627 if inst.args[0] == errno.EINTR:
628 return
628 return
629 raise
629 raise
630
630
631 # Future improvement: On Python 3.7, maybe gc.freeze() can be used
631 # Future improvement: On Python 3.7, maybe gc.freeze() can be used
632 # to prevent COW memory from being touched by GC.
632 # to prevent COW memory from being touched by GC.
633 # https://instagram-engineering.com/
633 # https://instagram-engineering.com/
634 # copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf
634 # copy-on-write-friendly-python-garbage-collection-ad6ed5233ddf
635 pid = os.fork()
635 pid = os.fork()
636 if pid:
636 if pid:
637 try:
637 try:
638 self.ui.log(
638 self.ui.log(
639 b'cmdserver', b'forked worker process (pid=%d)\n', pid
639 b'cmdserver', b'forked worker process (pid=%d)\n', pid
640 )
640 )
641 self._workerpids.add(pid)
641 self._workerpids.add(pid)
642 h.newconnection()
642 h.newconnection()
643 finally:
643 finally:
644 conn.close() # release handle in parent process
644 conn.close() # release handle in parent process
645 else:
645 else:
646 try:
646 try:
647 selector.close()
647 selector.close()
648 sock.close()
648 sock.close()
649 self._mainipc.close()
649 self._mainipc.close()
650 self._runworker(conn)
650 self._runworker(conn)
651 conn.close()
651 conn.close()
652 self._workeripc.close()
652 self._workeripc.close()
653 os._exit(0)
653 os._exit(0)
654 except: # never return, hence no re-raises
654 except: # never return, hence no re-raises
655 try:
655 try:
656 self.ui.traceback(force=True)
656 self.ui.traceback(force=True)
657 finally:
657 finally:
658 os._exit(255)
658 os._exit(255)
659
659
660 def _handlemainipc(self, sock, selector):
660 def _handlemainipc(self, sock, selector):
661 """Process messages sent from a worker"""
661 """Process messages sent from a worker"""
662 try:
662 try:
663 path = sock.recv(32768) # large enough to receive path
663 path = sock.recv(32768) # large enough to receive path
664 except socket.error as inst:
664 except socket.error as inst:
665 if inst.args[0] == errno.EINTR:
665 if inst.args[0] == errno.EINTR:
666 return
666 return
667 raise
667 raise
668 self._repoloader.load(path)
668 self._repoloader.load(path)
669
669
670 def _sigchldhandler(self, signal, frame):
670 def _sigchldhandler(self, signal, frame):
671 self._reapworkers(os.WNOHANG)
671 self._reapworkers(os.WNOHANG)
672
672
673 def _reapworkers(self, options):
673 def _reapworkers(self, options):
674 while self._workerpids:
674 while self._workerpids:
675 try:
675 try:
676 pid, _status = os.waitpid(-1, options)
676 pid, _status = os.waitpid(-1, options)
677 except OSError as inst:
677 except OSError as inst:
678 if inst.errno == errno.EINTR:
678 if inst.errno == errno.EINTR:
679 continue
679 continue
680 if inst.errno != errno.ECHILD:
680 if inst.errno != errno.ECHILD:
681 raise
681 raise
682 # no child processes at all (reaped by other waitpid()?)
682 # no child processes at all (reaped by other waitpid()?)
683 self._workerpids.clear()
683 self._workerpids.clear()
684 return
684 return
685 if pid == 0:
685 if pid == 0:
686 # no waitable child processes
686 # no waitable child processes
687 return
687 return
688 self.ui.log(b'cmdserver', b'worker process exited (pid=%d)\n', pid)
688 self.ui.log(b'cmdserver', b'worker process exited (pid=%d)\n', pid)
689 self._workerpids.discard(pid)
689 self._workerpids.discard(pid)
690
690
691 def _runworker(self, conn):
691 def _runworker(self, conn):
692 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
692 signal.signal(signal.SIGCHLD, self._oldsigchldhandler)
693 _initworkerprocess()
693 _initworkerprocess()
694 h = self._servicehandler
694 h = self._servicehandler
695 try:
695 try:
696 _serverequest(
696 _serverequest(
697 self.ui,
697 self.ui,
698 self.repo,
698 self.repo,
699 conn,
699 conn,
700 h.createcmdserver,
700 h.createcmdserver,
701 prereposetups=[self._reposetup],
701 prereposetups=[self._reposetup],
702 )
702 )
703 finally:
703 finally:
704 gc.collect() # trigger __del__ since worker process uses os._exit
704 gc.collect() # trigger __del__ since worker process uses os._exit
705
705
706 def _reposetup(self, ui, repo):
706 def _reposetup(self, ui, repo):
707 if not repo.local():
707 if not repo.local():
708 return
708 return
709
709
710 class unixcmdserverrepo(repo.__class__):
710 class unixcmdserverrepo(repo.__class__):
711 def close(self):
711 def close(self):
712 super(unixcmdserverrepo, self).close()
712 super(unixcmdserverrepo, self).close()
713 try:
713 try:
714 self._cmdserveripc.send(self.root)
714 self._cmdserveripc.send(self.root)
715 except socket.error:
715 except socket.error:
716 self.ui.log(
716 self.ui.log(
717 b'cmdserver', b'failed to send repo root to master\n'
717 b'cmdserver', b'failed to send repo root to master\n'
718 )
718 )
719
719
720 repo.__class__ = unixcmdserverrepo
720 repo.__class__ = unixcmdserverrepo
721 repo._cmdserveripc = self._workeripc
721 repo._cmdserveripc = self._workeripc
722
722
723 cachedrepo = self._repoloader.get(repo.root)
723 cachedrepo = self._repoloader.get(repo.root)
724 if cachedrepo is None:
724 if cachedrepo is None:
725 return
725 return
726 repo.ui.log(b'repocache', b'repo from cache: %s\n', repo.root)
726 repo.ui.log(b'repocache', b'repo from cache: %s\n', repo.root)
727 repocache.copycache(cachedrepo, repo)
727 repocache.copycache(cachedrepo, repo)
@@ -1,1106 +1,1111 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from __future__ import absolute_import
16 >>> from __future__ import absolute_import
17 >>> import os
17 >>> import os
18 >>> import sys
18 >>> import sys
19 >>> from hgclient import bprint, check, readchannel, runcommand
19 >>> from hgclient import bprint, check, readchannel, runcommand
20 >>> @check
20 >>> @check
21 ... def hellomessage(server):
21 ... def hellomessage(server):
22 ... ch, data = readchannel(server)
22 ... ch, data = readchannel(server)
23 ... bprint(b'%c, %r' % (ch, data))
23 ... bprint(b'%c, %r' % (ch, data))
24 ... # run an arbitrary command to make sure the next thing the server
24 ... # run an arbitrary command to make sure the next thing the server
25 ... # sends isn't part of the hello message
25 ... # sends isn't part of the hello message
26 ... runcommand(server, [b'id'])
26 ... runcommand(server, [b'id'])
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 *** runcommand id
28 *** runcommand id
29 000000000000 tip
29 000000000000 tip
30
30
31 >>> from hgclient import check
31 >>> from hgclient import check
32 >>> @check
32 >>> @check
33 ... def unknowncommand(server):
33 ... def unknowncommand(server):
34 ... server.stdin.write(b'unknowncommand\n')
34 ... server.stdin.write(b'unknowncommand\n')
35 abort: unknown command unknowncommand
35 abort: unknown command unknowncommand
36
36
37 >>> from hgclient import check, readchannel, runcommand
37 >>> from hgclient import check, readchannel, runcommand
38 >>> @check
38 >>> @check
39 ... def checkruncommand(server):
39 ... def checkruncommand(server):
40 ... # hello block
40 ... # hello block
41 ... readchannel(server)
41 ... readchannel(server)
42 ...
42 ...
43 ... # no args
43 ... # no args
44 ... runcommand(server, [])
44 ... runcommand(server, [])
45 ...
45 ...
46 ... # global options
46 ... # global options
47 ... runcommand(server, [b'id', b'--quiet'])
47 ... runcommand(server, [b'id', b'--quiet'])
48 ...
48 ...
49 ... # make sure global options don't stick through requests
49 ... # make sure global options don't stick through requests
50 ... runcommand(server, [b'id'])
50 ... runcommand(server, [b'id'])
51 ...
51 ...
52 ... # --config
52 ... # --config
53 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
53 ... runcommand(server, [b'id', b'--config', b'ui.quiet=True'])
54 ...
54 ...
55 ... # make sure --config doesn't stick
55 ... # make sure --config doesn't stick
56 ... runcommand(server, [b'id'])
56 ... runcommand(server, [b'id'])
57 ...
57 ...
58 ... # negative return code should be masked
58 ... # negative return code should be masked
59 ... runcommand(server, [b'id', b'-runknown'])
59 ... runcommand(server, [b'id', b'-runknown'])
60 *** runcommand
60 *** runcommand
61 Mercurial Distributed SCM
61 Mercurial Distributed SCM
62
62
63 basic commands:
63 basic commands:
64
64
65 add add the specified files on the next commit
65 add add the specified files on the next commit
66 annotate show changeset information by line for each file
66 annotate show changeset information by line for each file
67 clone make a copy of an existing repository
67 clone make a copy of an existing repository
68 commit commit the specified files or all outstanding changes
68 commit commit the specified files or all outstanding changes
69 diff diff repository (or selected files)
69 diff diff repository (or selected files)
70 export dump the header and diffs for one or more changesets
70 export dump the header and diffs for one or more changesets
71 forget forget the specified files on the next commit
71 forget forget the specified files on the next commit
72 init create a new repository in the given directory
72 init create a new repository in the given directory
73 log show revision history of entire repository or files
73 log show revision history of entire repository or files
74 merge merge another revision into working directory
74 merge merge another revision into working directory
75 pull pull changes from the specified source
75 pull pull changes from the specified source
76 push push changes to the specified destination
76 push push changes to the specified destination
77 remove remove the specified files on the next commit
77 remove remove the specified files on the next commit
78 serve start stand-alone webserver
78 serve start stand-alone webserver
79 status show changed files in the working directory
79 status show changed files in the working directory
80 summary summarize working directory state
80 summary summarize working directory state
81 update update working directory (or switch revisions)
81 update update working directory (or switch revisions)
82
82
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 *** runcommand id --quiet
84 *** runcommand id --quiet
85 000000000000
85 000000000000
86 *** runcommand id
86 *** runcommand id
87 000000000000 tip
87 000000000000 tip
88 *** runcommand id --config ui.quiet=True
88 *** runcommand id --config ui.quiet=True
89 000000000000
89 000000000000
90 *** runcommand id
90 *** runcommand id
91 000000000000 tip
91 000000000000 tip
92 *** runcommand id -runknown
92 *** runcommand id -runknown
93 abort: unknown revision 'unknown'!
93 abort: unknown revision 'unknown'!
94 [255]
94 [255]
95
95
96 >>> from hgclient import bprint, check, readchannel
96 >>> from hgclient import bprint, check, readchannel
97 >>> @check
97 >>> @check
98 ... def inputeof(server):
98 ... def inputeof(server):
99 ... readchannel(server)
99 ... readchannel(server)
100 ... server.stdin.write(b'runcommand\n')
100 ... server.stdin.write(b'runcommand\n')
101 ... # close stdin while server is waiting for input
101 ... # close stdin while server is waiting for input
102 ... server.stdin.close()
102 ... server.stdin.close()
103 ...
103 ...
104 ... # server exits with 1 if the pipe closed while reading the command
104 ... # server exits with 1 if the pipe closed while reading the command
105 ... bprint(b'server exit code =', b'%d' % server.wait())
105 ... bprint(b'server exit code =', b'%d' % server.wait())
106 server exit code = 1
106 server exit code = 1
107
107
108 >>> from hgclient import check, readchannel, runcommand, stringio
108 >>> from hgclient import check, readchannel, runcommand, stringio
109 >>> @check
109 >>> @check
110 ... def serverinput(server):
110 ... def serverinput(server):
111 ... readchannel(server)
111 ... readchannel(server)
112 ...
112 ...
113 ... patch = b"""
113 ... patch = b"""
114 ... # HG changeset patch
114 ... # HG changeset patch
115 ... # User test
115 ... # User test
116 ... # Date 0 0
116 ... # Date 0 0
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 ... # Parent 0000000000000000000000000000000000000000
118 ... # Parent 0000000000000000000000000000000000000000
119 ... 1
119 ... 1
120 ...
120 ...
121 ... diff -r 000000000000 -r c103a3dec114 a
121 ... diff -r 000000000000 -r c103a3dec114 a
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 ... @@ -0,0 +1,1 @@
124 ... @@ -0,0 +1,1 @@
125 ... +1
125 ... +1
126 ... """
126 ... """
127 ...
127 ...
128 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
128 ... runcommand(server, [b'import', b'-'], input=stringio(patch))
129 ... runcommand(server, [b'log'])
129 ... runcommand(server, [b'log'])
130 *** runcommand import -
130 *** runcommand import -
131 applying patch from stdin
131 applying patch from stdin
132 *** runcommand log
132 *** runcommand log
133 changeset: 0:eff892de26ec
133 changeset: 0:eff892de26ec
134 tag: tip
134 tag: tip
135 user: test
135 user: test
136 date: Thu Jan 01 00:00:00 1970 +0000
136 date: Thu Jan 01 00:00:00 1970 +0000
137 summary: 1
137 summary: 1
138
138
139
139
140 check strict parsing of early options:
140 check strict parsing of early options:
141
141
142 >>> import os
142 >>> import os
143 >>> from hgclient import check, readchannel, runcommand
143 >>> from hgclient import check, readchannel, runcommand
144 >>> os.environ['HGPLAIN'] = '+strictflags'
144 >>> os.environ['HGPLAIN'] = '+strictflags'
145 >>> @check
145 >>> @check
146 ... def cwd(server):
146 ... def cwd(server):
147 ... readchannel(server)
147 ... readchannel(server)
148 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
148 ... runcommand(server, [b'log', b'-b', b'--config=alias.log=!echo pwned',
149 ... b'default'])
149 ... b'default'])
150 *** runcommand log -b --config=alias.log=!echo pwned default
150 *** runcommand log -b --config=alias.log=!echo pwned default
151 abort: unknown revision '--config=alias.log=!echo pwned'!
151 abort: unknown revision '--config=alias.log=!echo pwned'!
152 [255]
152 [255]
153
153
154 check that "histedit --commands=-" can read rules from the input channel:
154 check that "histedit --commands=-" can read rules from the input channel:
155
155
156 >>> from hgclient import check, readchannel, runcommand, stringio
156 >>> from hgclient import check, readchannel, runcommand, stringio
157 >>> @check
157 >>> @check
158 ... def serverinput(server):
158 ... def serverinput(server):
159 ... readchannel(server)
159 ... readchannel(server)
160 ... rules = b'pick eff892de26ec\n'
160 ... rules = b'pick eff892de26ec\n'
161 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
161 ... runcommand(server, [b'histedit', b'0', b'--commands=-',
162 ... b'--config', b'extensions.histedit='],
162 ... b'--config', b'extensions.histedit='],
163 ... input=stringio(rules))
163 ... input=stringio(rules))
164 *** runcommand histedit 0 --commands=- --config extensions.histedit=
164 *** runcommand histedit 0 --commands=- --config extensions.histedit=
165
165
166 check that --cwd doesn't persist between requests:
166 check that --cwd doesn't persist between requests:
167
167
168 $ mkdir foo
168 $ mkdir foo
169 $ touch foo/bar
169 $ touch foo/bar
170 >>> from hgclient import check, readchannel, runcommand
170 >>> from hgclient import check, readchannel, runcommand
171 >>> @check
171 >>> @check
172 ... def cwd(server):
172 ... def cwd(server):
173 ... readchannel(server)
173 ... readchannel(server)
174 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
174 ... runcommand(server, [b'--cwd', b'foo', b'st', b'bar'])
175 ... runcommand(server, [b'st', b'foo/bar'])
175 ... runcommand(server, [b'st', b'foo/bar'])
176 *** runcommand --cwd foo st bar
176 *** runcommand --cwd foo st bar
177 ? bar
177 ? bar
178 *** runcommand st foo/bar
178 *** runcommand st foo/bar
179 ? foo/bar
179 ? foo/bar
180
180
181 $ rm foo/bar
181 $ rm foo/bar
182
182
183
183
184 check that local configs for the cached repo aren't inherited when -R is used:
184 check that local configs for the cached repo aren't inherited when -R is used:
185
185
186 $ cat <<EOF >> .hg/hgrc
186 $ cat <<EOF >> .hg/hgrc
187 > [ui]
187 > [ui]
188 > foo = bar
188 > foo = bar
189 > EOF
189 > EOF
190
190
191 #if no-extraextensions
191 #if no-extraextensions
192
192
193 >>> from hgclient import check, readchannel, runcommand, sep
193 >>> from hgclient import check, readchannel, runcommand, sep
194 >>> @check
194 >>> @check
195 ... def localhgrc(server):
195 ... def localhgrc(server):
196 ... readchannel(server)
196 ... readchannel(server)
197 ...
197 ...
198 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
198 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
199 ... # show it
199 ... # show it
200 ... runcommand(server, [b'showconfig'], outfilter=sep)
200 ... runcommand(server, [b'showconfig'], outfilter=sep)
201 ...
201 ...
202 ... # but not for this repo
202 ... # but not for this repo
203 ... runcommand(server, [b'init', b'foo'])
203 ... runcommand(server, [b'init', b'foo'])
204 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
204 ... runcommand(server, [b'-R', b'foo', b'showconfig', b'ui', b'defaults'])
205 *** runcommand showconfig
205 *** runcommand showconfig
206 bundle.mainreporoot=$TESTTMP/repo
206 bundle.mainreporoot=$TESTTMP/repo
207 devel.all-warnings=true
207 devel.all-warnings=true
208 devel.default-date=0 0
208 devel.default-date=0 0
209 extensions.fsmonitor= (fsmonitor !)
209 extensions.fsmonitor= (fsmonitor !)
210 largefiles.usercache=$TESTTMP/.cache/largefiles
210 largefiles.usercache=$TESTTMP/.cache/largefiles
211 lfs.usercache=$TESTTMP/.cache/lfs
211 lfs.usercache=$TESTTMP/.cache/lfs
212 ui.slash=True
212 ui.slash=True
213 ui.interactive=False
213 ui.interactive=False
214 ui.merge=internal:merge
214 ui.merge=internal:merge
215 ui.mergemarkers=detailed
215 ui.mergemarkers=detailed
216 ui.foo=bar
216 ui.foo=bar
217 ui.nontty=true
217 ui.nontty=true
218 web.address=localhost
218 web.address=localhost
219 web\.ipv6=(?:True|False) (re)
219 web\.ipv6=(?:True|False) (re)
220 web.server-header=testing stub value
220 web.server-header=testing stub value
221 *** runcommand init foo
221 *** runcommand init foo
222 *** runcommand -R foo showconfig ui defaults
222 *** runcommand -R foo showconfig ui defaults
223 ui.slash=True
223 ui.slash=True
224 ui.interactive=False
224 ui.interactive=False
225 ui.merge=internal:merge
225 ui.merge=internal:merge
226 ui.mergemarkers=detailed
226 ui.mergemarkers=detailed
227 ui.nontty=true
227 ui.nontty=true
228 #endif
228 #endif
229
229
230 $ rm -R foo
230 $ rm -R foo
231
231
232 #if windows
232 #if windows
233 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
233 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
234 #else
234 #else
235 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
235 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
236 #endif
236 #endif
237
237
238 $ cat <<EOF > hook.py
238 $ cat <<EOF > hook.py
239 > import sys
239 > import sys
240 > from hgclient import bprint
240 > from hgclient import bprint
241 > def hook(**args):
241 > def hook(**args):
242 > bprint(b'hook talking')
242 > bprint(b'hook talking')
243 > bprint(b'now try to read something: %r' % sys.stdin.read())
243 > bprint(b'now try to read something: %r' % sys.stdin.read())
244 > EOF
244 > EOF
245
245
246 >>> from hgclient import check, readchannel, runcommand, stringio
246 >>> from hgclient import check, readchannel, runcommand, stringio
247 >>> @check
247 >>> @check
248 ... def hookoutput(server):
248 ... def hookoutput(server):
249 ... readchannel(server)
249 ... readchannel(server)
250 ... runcommand(server, [b'--config',
250 ... runcommand(server, [b'--config',
251 ... b'hooks.pre-identify=python:hook.hook',
251 ... b'hooks.pre-identify=python:hook.hook',
252 ... b'id'],
252 ... b'id'],
253 ... input=stringio(b'some input'))
253 ... input=stringio(b'some input'))
254 *** runcommand --config hooks.pre-identify=python:hook.hook id
254 *** runcommand --config hooks.pre-identify=python:hook.hook id
255 eff892de26ec tip
255 eff892de26ec tip
256 hook talking
256 hook talking
257 now try to read something: ''
257 now try to read something: ''
258
258
259 Clean hook cached version
259 Clean hook cached version
260 $ rm hook.py*
260 $ rm hook.py*
261 $ rm -Rf __pycache__
261 $ rm -Rf __pycache__
262
262
263 $ echo a >> a
263 $ echo a >> a
264 >>> import os
264 >>> import os
265 >>> from hgclient import check, readchannel, runcommand
265 >>> from hgclient import check, readchannel, runcommand
266 >>> @check
266 >>> @check
267 ... def outsidechanges(server):
267 ... def outsidechanges(server):
268 ... readchannel(server)
268 ... readchannel(server)
269 ... runcommand(server, [b'status'])
269 ... runcommand(server, [b'status'])
270 ... os.system('hg ci -Am2')
270 ... os.system('hg ci -Am2')
271 ... runcommand(server, [b'tip'])
271 ... runcommand(server, [b'tip'])
272 ... runcommand(server, [b'status'])
272 ... runcommand(server, [b'status'])
273 *** runcommand status
273 *** runcommand status
274 M a
274 M a
275 *** runcommand tip
275 *** runcommand tip
276 changeset: 1:d3a0a68be6de
276 changeset: 1:d3a0a68be6de
277 tag: tip
277 tag: tip
278 user: test
278 user: test
279 date: Thu Jan 01 00:00:00 1970 +0000
279 date: Thu Jan 01 00:00:00 1970 +0000
280 summary: 2
280 summary: 2
281
281
282 *** runcommand status
282 *** runcommand status
283
283
284 >>> import os
284 >>> import os
285 >>> from hgclient import bprint, check, readchannel, runcommand
285 >>> from hgclient import bprint, check, readchannel, runcommand
286 >>> @check
286 >>> @check
287 ... def bookmarks(server):
287 ... def bookmarks(server):
288 ... readchannel(server)
288 ... readchannel(server)
289 ... runcommand(server, [b'bookmarks'])
289 ... runcommand(server, [b'bookmarks'])
290 ...
290 ...
291 ... # changes .hg/bookmarks
291 ... # changes .hg/bookmarks
292 ... os.system('hg bookmark -i bm1')
292 ... os.system('hg bookmark -i bm1')
293 ... os.system('hg bookmark -i bm2')
293 ... os.system('hg bookmark -i bm2')
294 ... runcommand(server, [b'bookmarks'])
294 ... runcommand(server, [b'bookmarks'])
295 ...
295 ...
296 ... # changes .hg/bookmarks.current
296 ... # changes .hg/bookmarks.current
297 ... os.system('hg upd bm1 -q')
297 ... os.system('hg upd bm1 -q')
298 ... runcommand(server, [b'bookmarks'])
298 ... runcommand(server, [b'bookmarks'])
299 ...
299 ...
300 ... runcommand(server, [b'bookmarks', b'bm3'])
300 ... runcommand(server, [b'bookmarks', b'bm3'])
301 ... f = open('a', 'ab')
301 ... f = open('a', 'ab')
302 ... f.write(b'a\n') and None
302 ... f.write(b'a\n') and None
303 ... f.close()
303 ... f.close()
304 ... runcommand(server, [b'commit', b'-Amm'])
304 ... runcommand(server, [b'commit', b'-Amm'])
305 ... runcommand(server, [b'bookmarks'])
305 ... runcommand(server, [b'bookmarks'])
306 ... bprint(b'')
306 ... bprint(b'')
307 *** runcommand bookmarks
307 *** runcommand bookmarks
308 no bookmarks set
308 no bookmarks set
309 *** runcommand bookmarks
309 *** runcommand bookmarks
310 bm1 1:d3a0a68be6de
310 bm1 1:d3a0a68be6de
311 bm2 1:d3a0a68be6de
311 bm2 1:d3a0a68be6de
312 *** runcommand bookmarks
312 *** runcommand bookmarks
313 * bm1 1:d3a0a68be6de
313 * bm1 1:d3a0a68be6de
314 bm2 1:d3a0a68be6de
314 bm2 1:d3a0a68be6de
315 *** runcommand bookmarks bm3
315 *** runcommand bookmarks bm3
316 *** runcommand commit -Amm
316 *** runcommand commit -Amm
317 *** runcommand bookmarks
317 *** runcommand bookmarks
318 bm1 1:d3a0a68be6de
318 bm1 1:d3a0a68be6de
319 bm2 1:d3a0a68be6de
319 bm2 1:d3a0a68be6de
320 * bm3 2:aef17e88f5f0
320 * bm3 2:aef17e88f5f0
321
321
322
322
323 >>> import os
323 >>> import os
324 >>> from hgclient import check, readchannel, runcommand
324 >>> from hgclient import check, readchannel, runcommand
325 >>> @check
325 >>> @check
326 ... def tagscache(server):
326 ... def tagscache(server):
327 ... readchannel(server)
327 ... readchannel(server)
328 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
328 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
329 ... os.system('hg tag -r 0 foo')
329 ... os.system('hg tag -r 0 foo')
330 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
330 ... runcommand(server, [b'id', b'-t', b'-r', b'0'])
331 *** runcommand id -t -r 0
331 *** runcommand id -t -r 0
332
332
333 *** runcommand id -t -r 0
333 *** runcommand id -t -r 0
334 foo
334 foo
335
335
336 >>> import os
336 >>> import os
337 >>> from hgclient import check, readchannel, runcommand
337 >>> from hgclient import check, readchannel, runcommand
338 >>> @check
338 >>> @check
339 ... def setphase(server):
339 ... def setphase(server):
340 ... readchannel(server)
340 ... readchannel(server)
341 ... runcommand(server, [b'phase', b'-r', b'.'])
341 ... runcommand(server, [b'phase', b'-r', b'.'])
342 ... os.system('hg phase -r . -p')
342 ... os.system('hg phase -r . -p')
343 ... runcommand(server, [b'phase', b'-r', b'.'])
343 ... runcommand(server, [b'phase', b'-r', b'.'])
344 *** runcommand phase -r .
344 *** runcommand phase -r .
345 3: draft
345 3: draft
346 *** runcommand phase -r .
346 *** runcommand phase -r .
347 3: public
347 3: public
348
348
349 $ echo a >> a
349 $ echo a >> a
350 >>> from hgclient import bprint, check, readchannel, runcommand
350 >>> from hgclient import bprint, check, readchannel, runcommand
351 >>> @check
351 >>> @check
352 ... def rollback(server):
352 ... def rollback(server):
353 ... readchannel(server)
353 ... readchannel(server)
354 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
354 ... runcommand(server, [b'phase', b'-r', b'.', b'-p'])
355 ... runcommand(server, [b'commit', b'-Am.'])
355 ... runcommand(server, [b'commit', b'-Am.'])
356 ... runcommand(server, [b'rollback'])
356 ... runcommand(server, [b'rollback'])
357 ... runcommand(server, [b'phase', b'-r', b'.'])
357 ... runcommand(server, [b'phase', b'-r', b'.'])
358 ... bprint(b'')
358 ... bprint(b'')
359 *** runcommand phase -r . -p
359 *** runcommand phase -r . -p
360 no phases changed
360 no phases changed
361 *** runcommand commit -Am.
361 *** runcommand commit -Am.
362 *** runcommand rollback
362 *** runcommand rollback
363 repository tip rolled back to revision 3 (undo commit)
363 repository tip rolled back to revision 3 (undo commit)
364 working directory now based on revision 3
364 working directory now based on revision 3
365 *** runcommand phase -r .
365 *** runcommand phase -r .
366 3: public
366 3: public
367
367
368
368
369 >>> import os
369 >>> import os
370 >>> from hgclient import check, readchannel, runcommand
370 >>> from hgclient import check, readchannel, runcommand
371 >>> @check
371 >>> @check
372 ... def branch(server):
372 ... def branch(server):
373 ... readchannel(server)
373 ... readchannel(server)
374 ... runcommand(server, [b'branch'])
374 ... runcommand(server, [b'branch'])
375 ... os.system('hg branch foo')
375 ... os.system('hg branch foo')
376 ... runcommand(server, [b'branch'])
376 ... runcommand(server, [b'branch'])
377 ... os.system('hg branch default')
377 ... os.system('hg branch default')
378 *** runcommand branch
378 *** runcommand branch
379 default
379 default
380 marked working directory as branch foo
380 marked working directory as branch foo
381 (branches are permanent and global, did you want a bookmark?)
381 (branches are permanent and global, did you want a bookmark?)
382 *** runcommand branch
382 *** runcommand branch
383 foo
383 foo
384 marked working directory as branch default
384 marked working directory as branch default
385 (branches are permanent and global, did you want a bookmark?)
385 (branches are permanent and global, did you want a bookmark?)
386
386
387 $ touch .hgignore
387 $ touch .hgignore
388 >>> import os
388 >>> import os
389 >>> from hgclient import bprint, check, readchannel, runcommand
389 >>> from hgclient import bprint, check, readchannel, runcommand
390 >>> @check
390 >>> @check
391 ... def hgignore(server):
391 ... def hgignore(server):
392 ... readchannel(server)
392 ... readchannel(server)
393 ... runcommand(server, [b'commit', b'-Am.'])
393 ... runcommand(server, [b'commit', b'-Am.'])
394 ... f = open('ignored-file', 'ab')
394 ... f = open('ignored-file', 'ab')
395 ... f.write(b'') and None
395 ... f.write(b'') and None
396 ... f.close()
396 ... f.close()
397 ... f = open('.hgignore', 'ab')
397 ... f = open('.hgignore', 'ab')
398 ... f.write(b'ignored-file')
398 ... f.write(b'ignored-file')
399 ... f.close()
399 ... f.close()
400 ... runcommand(server, [b'status', b'-i', b'-u'])
400 ... runcommand(server, [b'status', b'-i', b'-u'])
401 ... bprint(b'')
401 ... bprint(b'')
402 *** runcommand commit -Am.
402 *** runcommand commit -Am.
403 adding .hgignore
403 adding .hgignore
404 *** runcommand status -i -u
404 *** runcommand status -i -u
405 I ignored-file
405 I ignored-file
406
406
407
407
408 cache of non-public revisions should be invalidated on repository change
408 cache of non-public revisions should be invalidated on repository change
409 (issue4855):
409 (issue4855):
410
410
411 >>> import os
411 >>> import os
412 >>> from hgclient import bprint, check, readchannel, runcommand
412 >>> from hgclient import bprint, check, readchannel, runcommand
413 >>> @check
413 >>> @check
414 ... def phasesetscacheaftercommit(server):
414 ... def phasesetscacheaftercommit(server):
415 ... readchannel(server)
415 ... readchannel(server)
416 ... # load _phasecache._phaserevs and _phasesets
416 ... # load _phasecache._phaserevs and _phasesets
417 ... runcommand(server, [b'log', b'-qr', b'draft()'])
417 ... runcommand(server, [b'log', b'-qr', b'draft()'])
418 ... # create draft commits by another process
418 ... # create draft commits by another process
419 ... for i in range(5, 7):
419 ... for i in range(5, 7):
420 ... f = open('a', 'ab')
420 ... f = open('a', 'ab')
421 ... f.seek(0, os.SEEK_END)
421 ... f.seek(0, os.SEEK_END)
422 ... f.write(b'a\n') and None
422 ... f.write(b'a\n') and None
423 ... f.close()
423 ... f.close()
424 ... os.system('hg commit -Aqm%d' % i)
424 ... os.system('hg commit -Aqm%d' % i)
425 ... # new commits should be listed as draft revisions
425 ... # new commits should be listed as draft revisions
426 ... runcommand(server, [b'log', b'-qr', b'draft()'])
426 ... runcommand(server, [b'log', b'-qr', b'draft()'])
427 ... bprint(b'')
427 ... bprint(b'')
428 *** runcommand log -qr draft()
428 *** runcommand log -qr draft()
429 4:7966c8e3734d
429 4:7966c8e3734d
430 *** runcommand log -qr draft()
430 *** runcommand log -qr draft()
431 4:7966c8e3734d
431 4:7966c8e3734d
432 5:41f6602d1c4f
432 5:41f6602d1c4f
433 6:10501e202c35
433 6:10501e202c35
434
434
435
435
436 >>> import os
436 >>> import os
437 >>> from hgclient import bprint, check, readchannel, runcommand
437 >>> from hgclient import bprint, check, readchannel, runcommand
438 >>> @check
438 >>> @check
439 ... def phasesetscacheafterstrip(server):
439 ... def phasesetscacheafterstrip(server):
440 ... readchannel(server)
440 ... readchannel(server)
441 ... # load _phasecache._phaserevs and _phasesets
441 ... # load _phasecache._phaserevs and _phasesets
442 ... runcommand(server, [b'log', b'-qr', b'draft()'])
442 ... runcommand(server, [b'log', b'-qr', b'draft()'])
443 ... # strip cached revisions by another process
443 ... # strip cached revisions by another process
444 ... os.system('hg --config extensions.strip= strip -q 5')
444 ... os.system('hg --config extensions.strip= strip -q 5')
445 ... # shouldn't abort by "unknown revision '6'"
445 ... # shouldn't abort by "unknown revision '6'"
446 ... runcommand(server, [b'log', b'-qr', b'draft()'])
446 ... runcommand(server, [b'log', b'-qr', b'draft()'])
447 ... bprint(b'')
447 ... bprint(b'')
448 *** runcommand log -qr draft()
448 *** runcommand log -qr draft()
449 4:7966c8e3734d
449 4:7966c8e3734d
450 5:41f6602d1c4f
450 5:41f6602d1c4f
451 6:10501e202c35
451 6:10501e202c35
452 *** runcommand log -qr draft()
452 *** runcommand log -qr draft()
453 4:7966c8e3734d
453 4:7966c8e3734d
454
454
455
455
456 cache of phase roots should be invalidated on strip (issue3827):
456 cache of phase roots should be invalidated on strip (issue3827):
457
457
458 >>> import os
458 >>> import os
459 >>> from hgclient import check, readchannel, runcommand, sep
459 >>> from hgclient import check, readchannel, runcommand, sep
460 >>> @check
460 >>> @check
461 ... def phasecacheafterstrip(server):
461 ... def phasecacheafterstrip(server):
462 ... readchannel(server)
462 ... readchannel(server)
463 ...
463 ...
464 ... # create new head, 5:731265503d86
464 ... # create new head, 5:731265503d86
465 ... runcommand(server, [b'update', b'-C', b'0'])
465 ... runcommand(server, [b'update', b'-C', b'0'])
466 ... f = open('a', 'ab')
466 ... f = open('a', 'ab')
467 ... f.write(b'a\n') and None
467 ... f.write(b'a\n') and None
468 ... f.close()
468 ... f.close()
469 ... runcommand(server, [b'commit', b'-Am.', b'a'])
469 ... runcommand(server, [b'commit', b'-Am.', b'a'])
470 ... runcommand(server, [b'log', b'-Gq'])
470 ... runcommand(server, [b'log', b'-Gq'])
471 ...
471 ...
472 ... # make it public; draft marker moves to 4:7966c8e3734d
472 ... # make it public; draft marker moves to 4:7966c8e3734d
473 ... runcommand(server, [b'phase', b'-p', b'.'])
473 ... runcommand(server, [b'phase', b'-p', b'.'])
474 ... # load _phasecache.phaseroots
474 ... # load _phasecache.phaseroots
475 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
475 ... runcommand(server, [b'phase', b'.'], outfilter=sep)
476 ...
476 ...
477 ... # strip 1::4 outside server
477 ... # strip 1::4 outside server
478 ... os.system('hg -q --config extensions.mq= strip 1')
478 ... os.system('hg -q --config extensions.mq= strip 1')
479 ...
479 ...
480 ... # shouldn't raise "7966c8e3734d: no node!"
480 ... # shouldn't raise "7966c8e3734d: no node!"
481 ... runcommand(server, [b'branches'])
481 ... runcommand(server, [b'branches'])
482 *** runcommand update -C 0
482 *** runcommand update -C 0
483 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
483 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
484 (leaving bookmark bm3)
484 (leaving bookmark bm3)
485 *** runcommand commit -Am. a
485 *** runcommand commit -Am. a
486 created new head
486 created new head
487 *** runcommand log -Gq
487 *** runcommand log -Gq
488 @ 5:731265503d86
488 @ 5:731265503d86
489 |
489 |
490 | o 4:7966c8e3734d
490 | o 4:7966c8e3734d
491 | |
491 | |
492 | o 3:b9b85890c400
492 | o 3:b9b85890c400
493 | |
493 | |
494 | o 2:aef17e88f5f0
494 | o 2:aef17e88f5f0
495 | |
495 | |
496 | o 1:d3a0a68be6de
496 | o 1:d3a0a68be6de
497 |/
497 |/
498 o 0:eff892de26ec
498 o 0:eff892de26ec
499
499
500 *** runcommand phase -p .
500 *** runcommand phase -p .
501 *** runcommand phase .
501 *** runcommand phase .
502 5: public
502 5: public
503 *** runcommand branches
503 *** runcommand branches
504 default 1:731265503d86
504 default 1:731265503d86
505
505
506 in-memory cache must be reloaded if transaction is aborted. otherwise
506 in-memory cache must be reloaded if transaction is aborted. otherwise
507 changelog and manifest would have invalid node:
507 changelog and manifest would have invalid node:
508
508
509 $ echo a >> a
509 $ echo a >> a
510 >>> from hgclient import check, readchannel, runcommand
510 >>> from hgclient import check, readchannel, runcommand
511 >>> @check
511 >>> @check
512 ... def txabort(server):
512 ... def txabort(server):
513 ... readchannel(server)
513 ... readchannel(server)
514 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
514 ... runcommand(server, [b'commit', b'--config', b'hooks.pretxncommit=false',
515 ... b'-mfoo'])
515 ... b'-mfoo'])
516 ... runcommand(server, [b'verify'])
516 ... runcommand(server, [b'verify'])
517 *** runcommand commit --config hooks.pretxncommit=false -mfoo
517 *** runcommand commit --config hooks.pretxncommit=false -mfoo
518 transaction abort!
518 transaction abort!
519 rollback completed
519 rollback completed
520 abort: pretxncommit hook exited with status 1
520 abort: pretxncommit hook exited with status 1
521 [255]
521 [255]
522 *** runcommand verify
522 *** runcommand verify
523 checking changesets
523 checking changesets
524 checking manifests
524 checking manifests
525 crosschecking files in changesets and manifests
525 crosschecking files in changesets and manifests
526 checking files
526 checking files
527 checked 2 changesets with 2 changes to 1 files
527 checked 2 changesets with 2 changes to 1 files
528 $ hg revert --no-backup -aq
528 $ hg revert --no-backup -aq
529
529
530 $ cat >> .hg/hgrc << EOF
530 $ cat >> .hg/hgrc << EOF
531 > [experimental]
531 > [experimental]
532 > evolution.createmarkers=True
532 > evolution.createmarkers=True
533 > EOF
533 > EOF
534
534
535 >>> import os
535 >>> import os
536 >>> from hgclient import check, readchannel, runcommand
536 >>> from hgclient import check, readchannel, runcommand
537 >>> @check
537 >>> @check
538 ... def obsolete(server):
538 ... def obsolete(server):
539 ... readchannel(server)
539 ... readchannel(server)
540 ...
540 ...
541 ... runcommand(server, [b'up', b'null'])
541 ... runcommand(server, [b'up', b'null'])
542 ... runcommand(server, [b'phase', b'-df', b'tip'])
542 ... runcommand(server, [b'phase', b'-df', b'tip'])
543 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
543 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
544 ... if os.name == 'nt':
544 ... if os.name == 'nt':
545 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
545 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
546 ... os.system(cmd)
546 ... os.system(cmd)
547 ... runcommand(server, [b'log', b'--hidden'])
547 ... runcommand(server, [b'log', b'--hidden'])
548 ... runcommand(server, [b'log'])
548 ... runcommand(server, [b'log'])
549 *** runcommand up null
549 *** runcommand up null
550 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
550 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
551 *** runcommand phase -df tip
551 *** runcommand phase -df tip
552 1 new obsolescence markers
552 1 new obsolescence markers
553 obsoleted 1 changesets
553 obsoleted 1 changesets
554 *** runcommand log --hidden
554 *** runcommand log --hidden
555 changeset: 1:731265503d86
555 changeset: 1:731265503d86
556 tag: tip
556 tag: tip
557 user: test
557 user: test
558 date: Thu Jan 01 00:00:00 1970 +0000
558 date: Thu Jan 01 00:00:00 1970 +0000
559 obsolete: pruned
559 obsolete: pruned
560 summary: .
560 summary: .
561
561
562 changeset: 0:eff892de26ec
562 changeset: 0:eff892de26ec
563 bookmark: bm1
563 bookmark: bm1
564 bookmark: bm2
564 bookmark: bm2
565 bookmark: bm3
565 bookmark: bm3
566 user: test
566 user: test
567 date: Thu Jan 01 00:00:00 1970 +0000
567 date: Thu Jan 01 00:00:00 1970 +0000
568 summary: 1
568 summary: 1
569
569
570 *** runcommand log
570 *** runcommand log
571 changeset: 0:eff892de26ec
571 changeset: 0:eff892de26ec
572 bookmark: bm1
572 bookmark: bm1
573 bookmark: bm2
573 bookmark: bm2
574 bookmark: bm3
574 bookmark: bm3
575 tag: tip
575 tag: tip
576 user: test
576 user: test
577 date: Thu Jan 01 00:00:00 1970 +0000
577 date: Thu Jan 01 00:00:00 1970 +0000
578 summary: 1
578 summary: 1
579
579
580
580
581 $ cat <<EOF >> .hg/hgrc
581 $ cat <<EOF >> .hg/hgrc
582 > [extensions]
582 > [extensions]
583 > mq =
583 > mq =
584 > EOF
584 > EOF
585
585
586 >>> import os
586 >>> import os
587 >>> from hgclient import check, readchannel, runcommand
587 >>> from hgclient import check, readchannel, runcommand
588 >>> @check
588 >>> @check
589 ... def mqoutsidechanges(server):
589 ... def mqoutsidechanges(server):
590 ... readchannel(server)
590 ... readchannel(server)
591 ...
591 ...
592 ... # load repo.mq
592 ... # load repo.mq
593 ... runcommand(server, [b'qapplied'])
593 ... runcommand(server, [b'qapplied'])
594 ... os.system('hg qnew 0.diff')
594 ... os.system('hg qnew 0.diff')
595 ... # repo.mq should be invalidated
595 ... # repo.mq should be invalidated
596 ... runcommand(server, [b'qapplied'])
596 ... runcommand(server, [b'qapplied'])
597 ...
597 ...
598 ... runcommand(server, [b'qpop', b'--all'])
598 ... runcommand(server, [b'qpop', b'--all'])
599 ... os.system('hg qqueue --create foo')
599 ... os.system('hg qqueue --create foo')
600 ... # repo.mq should be recreated to point to new queue
600 ... # repo.mq should be recreated to point to new queue
601 ... runcommand(server, [b'qqueue', b'--active'])
601 ... runcommand(server, [b'qqueue', b'--active'])
602 *** runcommand qapplied
602 *** runcommand qapplied
603 *** runcommand qapplied
603 *** runcommand qapplied
604 0.diff
604 0.diff
605 *** runcommand qpop --all
605 *** runcommand qpop --all
606 popping 0.diff
606 popping 0.diff
607 patch queue now empty
607 patch queue now empty
608 *** runcommand qqueue --active
608 *** runcommand qqueue --active
609 foo
609 foo
610
610
611 $ cat <<'EOF' > ../dbgui.py
611 $ cat <<'EOF' > ../dbgui.py
612 > import os
612 > import os
613 > import sys
613 > import sys
614 > from mercurial import commands, registrar
614 > from mercurial import commands, registrar
615 > cmdtable = {}
615 > cmdtable = {}
616 > command = registrar.command(cmdtable)
616 > command = registrar.command(cmdtable)
617 > @command(b"debuggetpass", norepo=True)
617 > @command(b"debuggetpass", norepo=True)
618 > def debuggetpass(ui):
618 > def debuggetpass(ui):
619 > ui.write(b"%s\n" % ui.getpass())
619 > ui.write(b"%s\n" % ui.getpass())
620 > @command(b"debugprompt", norepo=True)
620 > @command(b"debugprompt", norepo=True)
621 > def debugprompt(ui):
621 > def debugprompt(ui):
622 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
622 > ui.write(b"%s\n" % ui.prompt(b"prompt:"))
623 > @command(b"debugpromptchoice", norepo=True)
623 > @command(b"debugpromptchoice", norepo=True)
624 > def debugpromptchoice(ui):
624 > def debugpromptchoice(ui):
625 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
625 > msg = b"promptchoice (y/n)? $$ &Yes $$ &No"
626 > ui.write(b"%d\n" % ui.promptchoice(msg))
626 > ui.write(b"%d\n" % ui.promptchoice(msg))
627 > @command(b"debugreadstdin", norepo=True)
627 > @command(b"debugreadstdin", norepo=True)
628 > def debugreadstdin(ui):
628 > def debugreadstdin(ui):
629 > ui.write(b"read: %r\n" % sys.stdin.read(1))
629 > ui.write(b"read: %r\n" % sys.stdin.read(1))
630 > @command(b"debugwritestdout", norepo=True)
630 > @command(b"debugwritestdout", norepo=True)
631 > def debugwritestdout(ui):
631 > def debugwritestdout(ui):
632 > os.write(1, b"low-level stdout fd and\n")
632 > os.write(1, b"low-level stdout fd and\n")
633 > sys.stdout.write("stdout should be redirected to stderr\n")
633 > sys.stdout.write("stdout should be redirected to stderr\n")
634 > sys.stdout.flush()
634 > sys.stdout.flush()
635 > EOF
635 > EOF
636 $ cat <<EOF >> .hg/hgrc
636 $ cat <<EOF >> .hg/hgrc
637 > [extensions]
637 > [extensions]
638 > dbgui = ../dbgui.py
638 > dbgui = ../dbgui.py
639 > EOF
639 > EOF
640
640
641 >>> from hgclient import check, readchannel, runcommand, stringio
641 >>> from hgclient import check, readchannel, runcommand, stringio
642 >>> @check
642 >>> @check
643 ... def getpass(server):
643 ... def getpass(server):
644 ... readchannel(server)
644 ... readchannel(server)
645 ... runcommand(server, [b'debuggetpass', b'--config',
645 ... runcommand(server, [b'debuggetpass', b'--config',
646 ... b'ui.interactive=True'],
646 ... b'ui.interactive=True'],
647 ... input=stringio(b'1234\n'))
647 ... input=stringio(b'1234\n'))
648 ... runcommand(server, [b'debuggetpass', b'--config',
648 ... runcommand(server, [b'debuggetpass', b'--config',
649 ... b'ui.interactive=True'],
649 ... b'ui.interactive=True'],
650 ... input=stringio(b'\n'))
650 ... input=stringio(b'\n'))
651 ... runcommand(server, [b'debuggetpass', b'--config',
651 ... runcommand(server, [b'debuggetpass', b'--config',
652 ... b'ui.interactive=True'],
652 ... b'ui.interactive=True'],
653 ... input=stringio(b''))
653 ... input=stringio(b''))
654 ... runcommand(server, [b'debugprompt', b'--config',
654 ... runcommand(server, [b'debugprompt', b'--config',
655 ... b'ui.interactive=True'],
655 ... b'ui.interactive=True'],
656 ... input=stringio(b'5678\n'))
656 ... input=stringio(b'5678\n'))
657 ... runcommand(server, [b'debugprompt', b'--config',
658 ... b'ui.interactive=True'],
659 ... input=stringio(b'\nremainder\nshould\nnot\nbe\nread\n'))
657 ... runcommand(server, [b'debugreadstdin'])
660 ... runcommand(server, [b'debugreadstdin'])
658 ... runcommand(server, [b'debugwritestdout'])
661 ... runcommand(server, [b'debugwritestdout'])
659 *** runcommand debuggetpass --config ui.interactive=True
662 *** runcommand debuggetpass --config ui.interactive=True
660 password: 1234
663 password: 1234
661 *** runcommand debuggetpass --config ui.interactive=True
664 *** runcommand debuggetpass --config ui.interactive=True
662 password:
665 password:
663 *** runcommand debuggetpass --config ui.interactive=True
666 *** runcommand debuggetpass --config ui.interactive=True
664 password: abort: response expected
667 password: abort: response expected
665 [255]
668 [255]
666 *** runcommand debugprompt --config ui.interactive=True
669 *** runcommand debugprompt --config ui.interactive=True
667 prompt: 5678
670 prompt: 5678
671 *** runcommand debugprompt --config ui.interactive=True
672 prompt: y
668 *** runcommand debugreadstdin
673 *** runcommand debugreadstdin
669 read: ''
674 read: ''
670 *** runcommand debugwritestdout
675 *** runcommand debugwritestdout
671 low-level stdout fd and
676 low-level stdout fd and
672 stdout should be redirected to stderr
677 stdout should be redirected to stderr
673
678
674
679
675 run commandserver in commandserver, which is silly but should work:
680 run commandserver in commandserver, which is silly but should work:
676
681
677 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
682 >>> from hgclient import bprint, check, readchannel, runcommand, stringio
678 >>> @check
683 >>> @check
679 ... def nested(server):
684 ... def nested(server):
680 ... bprint(b'%c, %r' % readchannel(server))
685 ... bprint(b'%c, %r' % readchannel(server))
681 ... class nestedserver(object):
686 ... class nestedserver(object):
682 ... stdin = stringio(b'getencoding\n')
687 ... stdin = stringio(b'getencoding\n')
683 ... stdout = stringio()
688 ... stdout = stringio()
684 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
689 ... runcommand(server, [b'serve', b'--cmdserver', b'pipe'],
685 ... output=nestedserver.stdout, input=nestedserver.stdin)
690 ... output=nestedserver.stdout, input=nestedserver.stdin)
686 ... nestedserver.stdout.seek(0)
691 ... nestedserver.stdout.seek(0)
687 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
692 ... bprint(b'%c, %r' % readchannel(nestedserver)) # hello
688 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
693 ... bprint(b'%c, %r' % readchannel(nestedserver)) # getencoding
689 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
694 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
690 *** runcommand serve --cmdserver pipe
695 *** runcommand serve --cmdserver pipe
691 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
696 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
692 r, '*' (glob)
697 r, '*' (glob)
693
698
694
699
695 start without repository:
700 start without repository:
696
701
697 $ cd ..
702 $ cd ..
698
703
699 >>> from hgclient import bprint, check, readchannel, runcommand
704 >>> from hgclient import bprint, check, readchannel, runcommand
700 >>> @check
705 >>> @check
701 ... def hellomessage(server):
706 ... def hellomessage(server):
702 ... ch, data = readchannel(server)
707 ... ch, data = readchannel(server)
703 ... bprint(b'%c, %r' % (ch, data))
708 ... bprint(b'%c, %r' % (ch, data))
704 ... # run an arbitrary command to make sure the next thing the server
709 ... # run an arbitrary command to make sure the next thing the server
705 ... # sends isn't part of the hello message
710 ... # sends isn't part of the hello message
706 ... runcommand(server, [b'id'])
711 ... runcommand(server, [b'id'])
707 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
712 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
708 *** runcommand id
713 *** runcommand id
709 abort: there is no Mercurial repository here (.hg not found)
714 abort: there is no Mercurial repository here (.hg not found)
710 [255]
715 [255]
711
716
712 >>> from hgclient import check, readchannel, runcommand
717 >>> from hgclient import check, readchannel, runcommand
713 >>> @check
718 >>> @check
714 ... def startwithoutrepo(server):
719 ... def startwithoutrepo(server):
715 ... readchannel(server)
720 ... readchannel(server)
716 ... runcommand(server, [b'init', b'repo2'])
721 ... runcommand(server, [b'init', b'repo2'])
717 ... runcommand(server, [b'id', b'-R', b'repo2'])
722 ... runcommand(server, [b'id', b'-R', b'repo2'])
718 *** runcommand init repo2
723 *** runcommand init repo2
719 *** runcommand id -R repo2
724 *** runcommand id -R repo2
720 000000000000 tip
725 000000000000 tip
721
726
722
727
723 don't fall back to cwd if invalid -R path is specified (issue4805):
728 don't fall back to cwd if invalid -R path is specified (issue4805):
724
729
725 $ cd repo
730 $ cd repo
726 $ hg serve --cmdserver pipe -R ../nonexistent
731 $ hg serve --cmdserver pipe -R ../nonexistent
727 abort: repository ../nonexistent not found!
732 abort: repository ../nonexistent not found!
728 [255]
733 [255]
729 $ cd ..
734 $ cd ..
730
735
731
736
732 structured message channel:
737 structured message channel:
733
738
734 $ cat <<'EOF' >> repo2/.hg/hgrc
739 $ cat <<'EOF' >> repo2/.hg/hgrc
735 > [ui]
740 > [ui]
736 > # server --config should precede repository option
741 > # server --config should precede repository option
737 > message-output = stdio
742 > message-output = stdio
738 > EOF
743 > EOF
739
744
740 >>> from hgclient import bprint, checkwith, readchannel, runcommand
745 >>> from hgclient import bprint, checkwith, readchannel, runcommand
741 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
746 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
742 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
747 ... b'--config', b'cmdserver.message-encodings=foo cbor'])
743 ... def verify(server):
748 ... def verify(server):
744 ... _ch, data = readchannel(server)
749 ... _ch, data = readchannel(server)
745 ... bprint(data)
750 ... bprint(data)
746 ... runcommand(server, [b'-R', b'repo2', b'verify'])
751 ... runcommand(server, [b'-R', b'repo2', b'verify'])
747 capabilities: getencoding runcommand
752 capabilities: getencoding runcommand
748 encoding: ascii
753 encoding: ascii
749 message-encoding: cbor
754 message-encoding: cbor
750 pid: * (glob)
755 pid: * (glob)
751 pgid: * (glob) (no-windows !)
756 pgid: * (glob) (no-windows !)
752 *** runcommand -R repo2 verify
757 *** runcommand -R repo2 verify
753 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
758 message: '\xa2DdataTchecking changesets\nDtypeFstatus'
754 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
759 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
755 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
760 message: '\xa2DdataSchecking manifests\nDtypeFstatus'
756 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
761 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
757 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
762 message: '\xa2DdataX0crosschecking files in changesets and manifests\nDtypeFstatus'
758 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
763 message: '\xa6Ditem@Cpos\xf6EtopicMcrosscheckingEtotal\xf6DtypeHprogressDunit@'
759 message: '\xa2DdataOchecking files\nDtypeFstatus'
764 message: '\xa2DdataOchecking files\nDtypeFstatus'
760 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
765 message: '\xa6Ditem@Cpos\xf6EtopicHcheckingEtotal\xf6DtypeHprogressDunit@'
761 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
766 message: '\xa2DdataX/checked 0 changesets with 0 changes to 0 files\nDtypeFstatus'
762
767
763 >>> from hgclient import checkwith, readchannel, runcommand, stringio
768 >>> from hgclient import checkwith, readchannel, runcommand, stringio
764 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
769 >>> @checkwith(extraargs=[b'--config', b'ui.message-output=channel',
765 ... b'--config', b'cmdserver.message-encodings=cbor',
770 ... b'--config', b'cmdserver.message-encodings=cbor',
766 ... b'--config', b'extensions.dbgui=dbgui.py'])
771 ... b'--config', b'extensions.dbgui=dbgui.py'])
767 ... def prompt(server):
772 ... def prompt(server):
768 ... readchannel(server)
773 ... readchannel(server)
769 ... interactive = [b'--config', b'ui.interactive=True']
774 ... interactive = [b'--config', b'ui.interactive=True']
770 ... runcommand(server, [b'debuggetpass'] + interactive,
775 ... runcommand(server, [b'debuggetpass'] + interactive,
771 ... input=stringio(b'1234\n'))
776 ... input=stringio(b'1234\n'))
772 ... runcommand(server, [b'debugprompt'] + interactive,
777 ... runcommand(server, [b'debugprompt'] + interactive,
773 ... input=stringio(b'5678\n'))
778 ... input=stringio(b'5678\n'))
774 ... runcommand(server, [b'debugpromptchoice'] + interactive,
779 ... runcommand(server, [b'debugpromptchoice'] + interactive,
775 ... input=stringio(b'n\n'))
780 ... input=stringio(b'n\n'))
776 *** runcommand debuggetpass --config ui.interactive=True
781 *** runcommand debuggetpass --config ui.interactive=True
777 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
782 message: '\xa3DdataJpassword: Hpassword\xf5DtypeFprompt'
778 1234
783 1234
779 *** runcommand debugprompt --config ui.interactive=True
784 *** runcommand debugprompt --config ui.interactive=True
780 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
785 message: '\xa3DdataGprompt:GdefaultAyDtypeFprompt'
781 5678
786 5678
782 *** runcommand debugpromptchoice --config ui.interactive=True
787 *** runcommand debugpromptchoice --config ui.interactive=True
783 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
788 message: '\xa4Gchoices\x82\x82AyCYes\x82AnBNoDdataTpromptchoice (y/n)? GdefaultAyDtypeFprompt'
784 1
789 1
785
790
786 bad message encoding:
791 bad message encoding:
787
792
788 $ hg serve --cmdserver pipe --config ui.message-output=channel
793 $ hg serve --cmdserver pipe --config ui.message-output=channel
789 abort: no supported message encodings:
794 abort: no supported message encodings:
790 [255]
795 [255]
791 $ hg serve --cmdserver pipe --config ui.message-output=channel \
796 $ hg serve --cmdserver pipe --config ui.message-output=channel \
792 > --config cmdserver.message-encodings='foo bar'
797 > --config cmdserver.message-encodings='foo bar'
793 abort: no supported message encodings: foo bar
798 abort: no supported message encodings: foo bar
794 [255]
799 [255]
795
800
796 unix domain socket:
801 unix domain socket:
797
802
798 $ cd repo
803 $ cd repo
799 $ hg update -q
804 $ hg update -q
800
805
801 #if unix-socket unix-permissions
806 #if unix-socket unix-permissions
802
807
803 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
808 >>> from hgclient import bprint, check, readchannel, runcommand, stringio, unixserver
804 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
809 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
805 >>> def hellomessage(conn):
810 >>> def hellomessage(conn):
806 ... ch, data = readchannel(conn)
811 ... ch, data = readchannel(conn)
807 ... bprint(b'%c, %r' % (ch, data))
812 ... bprint(b'%c, %r' % (ch, data))
808 ... runcommand(conn, [b'id'])
813 ... runcommand(conn, [b'id'])
809 >>> check(hellomessage, server.connect)
814 >>> check(hellomessage, server.connect)
810 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
815 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
811 *** runcommand id
816 *** runcommand id
812 eff892de26ec tip bm1/bm2/bm3
817 eff892de26ec tip bm1/bm2/bm3
813 >>> def unknowncommand(conn):
818 >>> def unknowncommand(conn):
814 ... readchannel(conn)
819 ... readchannel(conn)
815 ... conn.stdin.write(b'unknowncommand\n')
820 ... conn.stdin.write(b'unknowncommand\n')
816 >>> check(unknowncommand, server.connect) # error sent to server.log
821 >>> check(unknowncommand, server.connect) # error sent to server.log
817 >>> def serverinput(conn):
822 >>> def serverinput(conn):
818 ... readchannel(conn)
823 ... readchannel(conn)
819 ... patch = b"""
824 ... patch = b"""
820 ... # HG changeset patch
825 ... # HG changeset patch
821 ... # User test
826 ... # User test
822 ... # Date 0 0
827 ... # Date 0 0
823 ... 2
828 ... 2
824 ...
829 ...
825 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
830 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
826 ... --- a/a
831 ... --- a/a
827 ... +++ b/a
832 ... +++ b/a
828 ... @@ -1,1 +1,2 @@
833 ... @@ -1,1 +1,2 @@
829 ... 1
834 ... 1
830 ... +2
835 ... +2
831 ... """
836 ... """
832 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
837 ... runcommand(conn, [b'import', b'-'], input=stringio(patch))
833 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
838 ... runcommand(conn, [b'log', b'-rtip', b'-q'])
834 >>> check(serverinput, server.connect)
839 >>> check(serverinput, server.connect)
835 *** runcommand import -
840 *** runcommand import -
836 applying patch from stdin
841 applying patch from stdin
837 *** runcommand log -rtip -q
842 *** runcommand log -rtip -q
838 2:1ed24be7e7a0
843 2:1ed24be7e7a0
839 >>> server.shutdown()
844 >>> server.shutdown()
840
845
841 $ cat .hg/server.log
846 $ cat .hg/server.log
842 listening at .hg/server.sock
847 listening at .hg/server.sock
843 abort: unknown command unknowncommand
848 abort: unknown command unknowncommand
844 killed!
849 killed!
845 $ rm .hg/server.log
850 $ rm .hg/server.log
846
851
847 if server crashed before hello, traceback will be sent to 'e' channel as
852 if server crashed before hello, traceback will be sent to 'e' channel as
848 last ditch:
853 last ditch:
849
854
850 $ cat <<'EOF' > ../earlycrasher.py
855 $ cat <<'EOF' > ../earlycrasher.py
851 > from mercurial import commandserver, extensions
856 > from mercurial import commandserver, extensions
852 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
857 > def _serverequest(orig, ui, repo, conn, createcmdserver, prereposetups):
853 > def createcmdserver(*args, **kwargs):
858 > def createcmdserver(*args, **kwargs):
854 > raise Exception('crash')
859 > raise Exception('crash')
855 > return orig(ui, repo, conn, createcmdserver, prereposetups)
860 > return orig(ui, repo, conn, createcmdserver, prereposetups)
856 > def extsetup(ui):
861 > def extsetup(ui):
857 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
862 > extensions.wrapfunction(commandserver, b'_serverequest', _serverequest)
858 > EOF
863 > EOF
859 $ cat <<EOF >> .hg/hgrc
864 $ cat <<EOF >> .hg/hgrc
860 > [extensions]
865 > [extensions]
861 > earlycrasher = ../earlycrasher.py
866 > earlycrasher = ../earlycrasher.py
862 > EOF
867 > EOF
863 >>> from hgclient import bprint, check, readchannel, unixserver
868 >>> from hgclient import bprint, check, readchannel, unixserver
864 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
869 >>> server = unixserver(b'.hg/server.sock', b'.hg/server.log')
865 >>> def earlycrash(conn):
870 >>> def earlycrash(conn):
866 ... while True:
871 ... while True:
867 ... try:
872 ... try:
868 ... ch, data = readchannel(conn)
873 ... ch, data = readchannel(conn)
869 ... for l in data.splitlines(True):
874 ... for l in data.splitlines(True):
870 ... if not l.startswith(b' '):
875 ... if not l.startswith(b' '):
871 ... bprint(b'%c, %r' % (ch, l))
876 ... bprint(b'%c, %r' % (ch, l))
872 ... except EOFError:
877 ... except EOFError:
873 ... break
878 ... break
874 >>> check(earlycrash, server.connect)
879 >>> check(earlycrash, server.connect)
875 e, 'Traceback (most recent call last):\n'
880 e, 'Traceback (most recent call last):\n'
876 e, 'Exception: crash\n'
881 e, 'Exception: crash\n'
877 >>> server.shutdown()
882 >>> server.shutdown()
878
883
879 $ cat .hg/server.log | grep -v '^ '
884 $ cat .hg/server.log | grep -v '^ '
880 listening at .hg/server.sock
885 listening at .hg/server.sock
881 Traceback (most recent call last):
886 Traceback (most recent call last):
882 Exception: crash
887 Exception: crash
883 killed!
888 killed!
884 #endif
889 #endif
885 #if no-unix-socket
890 #if no-unix-socket
886
891
887 $ hg serve --cmdserver unix -a .hg/server.sock
892 $ hg serve --cmdserver unix -a .hg/server.sock
888 abort: unsupported platform
893 abort: unsupported platform
889 [255]
894 [255]
890
895
891 #endif
896 #endif
892
897
893 $ cd ..
898 $ cd ..
894
899
895 Test that accessing to invalid changelog cache is avoided at
900 Test that accessing to invalid changelog cache is avoided at
896 subsequent operations even if repo object is reused even after failure
901 subsequent operations even if repo object is reused even after failure
897 of transaction (see 0a7610758c42 also)
902 of transaction (see 0a7610758c42 also)
898
903
899 "hg log" after failure of transaction is needed to detect invalid
904 "hg log" after failure of transaction is needed to detect invalid
900 cache in repoview: this can't detect by "hg verify" only.
905 cache in repoview: this can't detect by "hg verify" only.
901
906
902 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
907 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
903 4) are tested, because '00changelog.i' are differently changed in each
908 4) are tested, because '00changelog.i' are differently changed in each
904 cases.
909 cases.
905
910
906 $ cat > $TESTTMP/failafterfinalize.py <<EOF
911 $ cat > $TESTTMP/failafterfinalize.py <<EOF
907 > # extension to abort transaction after finalization forcibly
912 > # extension to abort transaction after finalization forcibly
908 > from mercurial import commands, error, extensions, lock as lockmod
913 > from mercurial import commands, error, extensions, lock as lockmod
909 > from mercurial import registrar
914 > from mercurial import registrar
910 > cmdtable = {}
915 > cmdtable = {}
911 > command = registrar.command(cmdtable)
916 > command = registrar.command(cmdtable)
912 > configtable = {}
917 > configtable = {}
913 > configitem = registrar.configitem(configtable)
918 > configitem = registrar.configitem(configtable)
914 > configitem(b'failafterfinalize', b'fail',
919 > configitem(b'failafterfinalize', b'fail',
915 > default=None,
920 > default=None,
916 > )
921 > )
917 > def fail(tr):
922 > def fail(tr):
918 > raise error.Abort(b'fail after finalization')
923 > raise error.Abort(b'fail after finalization')
919 > def reposetup(ui, repo):
924 > def reposetup(ui, repo):
920 > class failrepo(repo.__class__):
925 > class failrepo(repo.__class__):
921 > def commitctx(self, ctx, error=False, origctx=None):
926 > def commitctx(self, ctx, error=False, origctx=None):
922 > if self.ui.configbool(b'failafterfinalize', b'fail'):
927 > if self.ui.configbool(b'failafterfinalize', b'fail'):
923 > # 'sorted()' by ASCII code on category names causes
928 > # 'sorted()' by ASCII code on category names causes
924 > # invoking 'fail' after finalization of changelog
929 > # invoking 'fail' after finalization of changelog
925 > # using "'cl-%i' % id(self)" as category name
930 > # using "'cl-%i' % id(self)" as category name
926 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
931 > self.currenttransaction().addfinalize(b'zzzzzzzz', fail)
927 > return super(failrepo, self).commitctx(ctx, error, origctx)
932 > return super(failrepo, self).commitctx(ctx, error, origctx)
928 > repo.__class__ = failrepo
933 > repo.__class__ = failrepo
929 > EOF
934 > EOF
930
935
931 $ hg init repo3
936 $ hg init repo3
932 $ cd repo3
937 $ cd repo3
933
938
934 $ cat <<EOF >> $HGRCPATH
939 $ cat <<EOF >> $HGRCPATH
935 > [ui]
940 > [ui]
936 > logtemplate = {rev} {desc|firstline} ({files})\n
941 > logtemplate = {rev} {desc|firstline} ({files})\n
937 >
942 >
938 > [extensions]
943 > [extensions]
939 > failafterfinalize = $TESTTMP/failafterfinalize.py
944 > failafterfinalize = $TESTTMP/failafterfinalize.py
940 > EOF
945 > EOF
941
946
942 - test failure with "empty changelog"
947 - test failure with "empty changelog"
943
948
944 $ echo foo > foo
949 $ echo foo > foo
945 $ hg add foo
950 $ hg add foo
946
951
947 (failure before finalization)
952 (failure before finalization)
948
953
949 >>> from hgclient import check, readchannel, runcommand
954 >>> from hgclient import check, readchannel, runcommand
950 >>> @check
955 >>> @check
951 ... def abort(server):
956 ... def abort(server):
952 ... readchannel(server)
957 ... readchannel(server)
953 ... runcommand(server, [b'commit',
958 ... runcommand(server, [b'commit',
954 ... b'--config', b'hooks.pretxncommit=false',
959 ... b'--config', b'hooks.pretxncommit=false',
955 ... b'-mfoo'])
960 ... b'-mfoo'])
956 ... runcommand(server, [b'log'])
961 ... runcommand(server, [b'log'])
957 ... runcommand(server, [b'verify', b'-q'])
962 ... runcommand(server, [b'verify', b'-q'])
958 *** runcommand commit --config hooks.pretxncommit=false -mfoo
963 *** runcommand commit --config hooks.pretxncommit=false -mfoo
959 transaction abort!
964 transaction abort!
960 rollback completed
965 rollback completed
961 abort: pretxncommit hook exited with status 1
966 abort: pretxncommit hook exited with status 1
962 [255]
967 [255]
963 *** runcommand log
968 *** runcommand log
964 *** runcommand verify -q
969 *** runcommand verify -q
965
970
966 (failure after finalization)
971 (failure after finalization)
967
972
968 >>> from hgclient import check, readchannel, runcommand
973 >>> from hgclient import check, readchannel, runcommand
969 >>> @check
974 >>> @check
970 ... def abort(server):
975 ... def abort(server):
971 ... readchannel(server)
976 ... readchannel(server)
972 ... runcommand(server, [b'commit',
977 ... runcommand(server, [b'commit',
973 ... b'--config', b'failafterfinalize.fail=true',
978 ... b'--config', b'failafterfinalize.fail=true',
974 ... b'-mfoo'])
979 ... b'-mfoo'])
975 ... runcommand(server, [b'log'])
980 ... runcommand(server, [b'log'])
976 ... runcommand(server, [b'verify', b'-q'])
981 ... runcommand(server, [b'verify', b'-q'])
977 *** runcommand commit --config failafterfinalize.fail=true -mfoo
982 *** runcommand commit --config failafterfinalize.fail=true -mfoo
978 transaction abort!
983 transaction abort!
979 rollback completed
984 rollback completed
980 abort: fail after finalization
985 abort: fail after finalization
981 [255]
986 [255]
982 *** runcommand log
987 *** runcommand log
983 *** runcommand verify -q
988 *** runcommand verify -q
984
989
985 - test failure with "not-empty changelog"
990 - test failure with "not-empty changelog"
986
991
987 $ echo bar > bar
992 $ echo bar > bar
988 $ hg add bar
993 $ hg add bar
989 $ hg commit -mbar bar
994 $ hg commit -mbar bar
990
995
991 (failure before finalization)
996 (failure before finalization)
992
997
993 >>> from hgclient import check, readchannel, runcommand
998 >>> from hgclient import check, readchannel, runcommand
994 >>> @check
999 >>> @check
995 ... def abort(server):
1000 ... def abort(server):
996 ... readchannel(server)
1001 ... readchannel(server)
997 ... runcommand(server, [b'commit',
1002 ... runcommand(server, [b'commit',
998 ... b'--config', b'hooks.pretxncommit=false',
1003 ... b'--config', b'hooks.pretxncommit=false',
999 ... b'-mfoo', b'foo'])
1004 ... b'-mfoo', b'foo'])
1000 ... runcommand(server, [b'log'])
1005 ... runcommand(server, [b'log'])
1001 ... runcommand(server, [b'verify', b'-q'])
1006 ... runcommand(server, [b'verify', b'-q'])
1002 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1007 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
1003 transaction abort!
1008 transaction abort!
1004 rollback completed
1009 rollback completed
1005 abort: pretxncommit hook exited with status 1
1010 abort: pretxncommit hook exited with status 1
1006 [255]
1011 [255]
1007 *** runcommand log
1012 *** runcommand log
1008 0 bar (bar)
1013 0 bar (bar)
1009 *** runcommand verify -q
1014 *** runcommand verify -q
1010
1015
1011 (failure after finalization)
1016 (failure after finalization)
1012
1017
1013 >>> from hgclient import check, readchannel, runcommand
1018 >>> from hgclient import check, readchannel, runcommand
1014 >>> @check
1019 >>> @check
1015 ... def abort(server):
1020 ... def abort(server):
1016 ... readchannel(server)
1021 ... readchannel(server)
1017 ... runcommand(server, [b'commit',
1022 ... runcommand(server, [b'commit',
1018 ... b'--config', b'failafterfinalize.fail=true',
1023 ... b'--config', b'failafterfinalize.fail=true',
1019 ... b'-mfoo', b'foo'])
1024 ... b'-mfoo', b'foo'])
1020 ... runcommand(server, [b'log'])
1025 ... runcommand(server, [b'log'])
1021 ... runcommand(server, [b'verify', b'-q'])
1026 ... runcommand(server, [b'verify', b'-q'])
1022 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1027 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
1023 transaction abort!
1028 transaction abort!
1024 rollback completed
1029 rollback completed
1025 abort: fail after finalization
1030 abort: fail after finalization
1026 [255]
1031 [255]
1027 *** runcommand log
1032 *** runcommand log
1028 0 bar (bar)
1033 0 bar (bar)
1029 *** runcommand verify -q
1034 *** runcommand verify -q
1030
1035
1031 $ cd ..
1036 $ cd ..
1032
1037
1033 Test symlink traversal over cached audited paths:
1038 Test symlink traversal over cached audited paths:
1034 -------------------------------------------------
1039 -------------------------------------------------
1035
1040
1036 #if symlink
1041 #if symlink
1037
1042
1038 set up symlink hell
1043 set up symlink hell
1039
1044
1040 $ mkdir merge-symlink-out
1045 $ mkdir merge-symlink-out
1041 $ hg init merge-symlink
1046 $ hg init merge-symlink
1042 $ cd merge-symlink
1047 $ cd merge-symlink
1043 $ touch base
1048 $ touch base
1044 $ hg commit -qAm base
1049 $ hg commit -qAm base
1045 $ ln -s ../merge-symlink-out a
1050 $ ln -s ../merge-symlink-out a
1046 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1051 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
1047 $ hg up -q 0
1052 $ hg up -q 0
1048 $ mkdir a
1053 $ mkdir a
1049 $ touch a/poisoned
1054 $ touch a/poisoned
1050 $ hg commit -qAm 'file a/poisoned'
1055 $ hg commit -qAm 'file a/poisoned'
1051 $ hg log -G -T '{rev}: {desc}\n'
1056 $ hg log -G -T '{rev}: {desc}\n'
1052 @ 2: file a/poisoned
1057 @ 2: file a/poisoned
1053 |
1058 |
1054 | o 1: symlink a -> ../merge-symlink-out
1059 | o 1: symlink a -> ../merge-symlink-out
1055 |/
1060 |/
1056 o 0: base
1061 o 0: base
1057
1062
1058
1063
1059 try trivial merge after update: cache of audited paths should be discarded,
1064 try trivial merge after update: cache of audited paths should be discarded,
1060 and the merge should fail (issue5628)
1065 and the merge should fail (issue5628)
1061
1066
1062 $ hg up -q null
1067 $ hg up -q null
1063 >>> from hgclient import check, readchannel, runcommand
1068 >>> from hgclient import check, readchannel, runcommand
1064 >>> @check
1069 >>> @check
1065 ... def merge(server):
1070 ... def merge(server):
1066 ... readchannel(server)
1071 ... readchannel(server)
1067 ... # audit a/poisoned as a good path
1072 ... # audit a/poisoned as a good path
1068 ... runcommand(server, [b'up', b'-qC', b'2'])
1073 ... runcommand(server, [b'up', b'-qC', b'2'])
1069 ... runcommand(server, [b'up', b'-qC', b'1'])
1074 ... runcommand(server, [b'up', b'-qC', b'1'])
1070 ... # here a is a symlink, so a/poisoned is bad
1075 ... # here a is a symlink, so a/poisoned is bad
1071 ... runcommand(server, [b'merge', b'2'])
1076 ... runcommand(server, [b'merge', b'2'])
1072 *** runcommand up -qC 2
1077 *** runcommand up -qC 2
1073 *** runcommand up -qC 1
1078 *** runcommand up -qC 1
1074 *** runcommand merge 2
1079 *** runcommand merge 2
1075 abort: path 'a/poisoned' traverses symbolic link 'a'
1080 abort: path 'a/poisoned' traverses symbolic link 'a'
1076 [255]
1081 [255]
1077 $ ls ../merge-symlink-out
1082 $ ls ../merge-symlink-out
1078
1083
1079 cache of repo.auditor should be discarded, so matcher would never traverse
1084 cache of repo.auditor should be discarded, so matcher would never traverse
1080 symlinks:
1085 symlinks:
1081
1086
1082 $ hg up -qC 0
1087 $ hg up -qC 0
1083 $ touch ../merge-symlink-out/poisoned
1088 $ touch ../merge-symlink-out/poisoned
1084 >>> from hgclient import check, readchannel, runcommand
1089 >>> from hgclient import check, readchannel, runcommand
1085 >>> @check
1090 >>> @check
1086 ... def files(server):
1091 ... def files(server):
1087 ... readchannel(server)
1092 ... readchannel(server)
1088 ... runcommand(server, [b'up', b'-qC', b'2'])
1093 ... runcommand(server, [b'up', b'-qC', b'2'])
1089 ... # audit a/poisoned as a good path
1094 ... # audit a/poisoned as a good path
1090 ... runcommand(server, [b'files', b'a/poisoned'])
1095 ... runcommand(server, [b'files', b'a/poisoned'])
1091 ... runcommand(server, [b'up', b'-qC', b'0'])
1096 ... runcommand(server, [b'up', b'-qC', b'0'])
1092 ... runcommand(server, [b'up', b'-qC', b'1'])
1097 ... runcommand(server, [b'up', b'-qC', b'1'])
1093 ... # here 'a' is a symlink, so a/poisoned should be warned
1098 ... # here 'a' is a symlink, so a/poisoned should be warned
1094 ... runcommand(server, [b'files', b'a/poisoned'])
1099 ... runcommand(server, [b'files', b'a/poisoned'])
1095 *** runcommand up -qC 2
1100 *** runcommand up -qC 2
1096 *** runcommand files a/poisoned
1101 *** runcommand files a/poisoned
1097 a/poisoned
1102 a/poisoned
1098 *** runcommand up -qC 0
1103 *** runcommand up -qC 0
1099 *** runcommand up -qC 1
1104 *** runcommand up -qC 1
1100 *** runcommand files a/poisoned
1105 *** runcommand files a/poisoned
1101 abort: path 'a/poisoned' traverses symbolic link 'a'
1106 abort: path 'a/poisoned' traverses symbolic link 'a'
1102 [255]
1107 [255]
1103
1108
1104 $ cd ..
1109 $ cd ..
1105
1110
1106 #endif
1111 #endif
General Comments 0
You need to be logged in to leave comments. Login now