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