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