##// END OF EJS Templates
py3: replace "%r" by"'%s'% for py3-compatible (and clearer) quoting in chg...
Martin von Zweigbergk -
r44020:975e5174 default
parent child Browse files
Show More
@@ -1,703 +1,703 b''
1 1 # chgserver.py - command server extension for cHg
2 2 #
3 3 # Copyright 2011 Yuya Nishihara <yuya@tcha.org>
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 """command server extension for cHg
9 9
10 10 'S' channel (read/write)
11 11 propagate ui.system() request to client
12 12
13 13 'attachio' command
14 14 attach client's stdio passed by sendmsg()
15 15
16 16 'chdir' command
17 17 change current directory
18 18
19 19 'setenv' command
20 20 replace os.environ completely
21 21
22 22 'setumask' command (DEPRECATED)
23 23 'setumask2' command
24 24 set umask
25 25
26 26 'validate' command
27 27 reload the config and check if the server is up to date
28 28
29 29 Config
30 30 ------
31 31
32 32 ::
33 33
34 34 [chgserver]
35 35 # how long (in seconds) should an idle chg server exit
36 36 idletimeout = 3600
37 37
38 38 # whether to skip config or env change checks
39 39 skiphash = False
40 40 """
41 41
42 42 from __future__ import absolute_import
43 43
44 44 import hashlib
45 45 import inspect
46 46 import os
47 47 import re
48 48 import socket
49 49 import stat
50 50 import struct
51 51 import time
52 52
53 53 from .i18n import _
54 54 from .pycompat import (
55 55 getattr,
56 56 setattr,
57 57 )
58 58
59 59 from . import (
60 60 commandserver,
61 61 encoding,
62 62 error,
63 63 extensions,
64 64 node,
65 65 pycompat,
66 66 util,
67 67 )
68 68
69 69 from .utils import (
70 70 procutil,
71 71 stringutil,
72 72 )
73 73
74 74
75 75 def _hashlist(items):
76 76 """return sha1 hexdigest for a list"""
77 77 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest())
78 78
79 79
80 80 # sensitive config sections affecting confighash
81 81 _configsections = [
82 82 b'alias', # affects global state commands.table
83 83 b'eol', # uses setconfig('eol', ...)
84 84 b'extdiff', # uisetup will register new commands
85 85 b'extensions',
86 86 ]
87 87
88 88 _configsectionitems = [
89 89 (b'commands', b'show.aliasprefix'), # show.py reads it in extsetup
90 90 ]
91 91
92 92 # sensitive environment variables affecting confighash
93 93 _envre = re.compile(
94 94 br'''\A(?:
95 95 CHGHG
96 96 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
97 97 |HG(?:ENCODING|PLAIN).*
98 98 |LANG(?:UAGE)?
99 99 |LC_.*
100 100 |LD_.*
101 101 |PATH
102 102 |PYTHON.*
103 103 |TERM(?:INFO)?
104 104 |TZ
105 105 )\Z''',
106 106 re.X,
107 107 )
108 108
109 109
110 110 def _confighash(ui):
111 111 """return a quick hash for detecting config/env changes
112 112
113 113 confighash is the hash of sensitive config items and environment variables.
114 114
115 115 for chgserver, it is designed that once confighash changes, the server is
116 116 not qualified to serve its client and should redirect the client to a new
117 117 server. different from mtimehash, confighash change will not mark the
118 118 server outdated and exit since the user can have different configs at the
119 119 same time.
120 120 """
121 121 sectionitems = []
122 122 for section in _configsections:
123 123 sectionitems.append(ui.configitems(section))
124 124 for section, item in _configsectionitems:
125 125 sectionitems.append(ui.config(section, item))
126 126 sectionhash = _hashlist(sectionitems)
127 127 # If $CHGHG is set, the change to $HG should not trigger a new chg server
128 128 if b'CHGHG' in encoding.environ:
129 129 ignored = {b'HG'}
130 130 else:
131 131 ignored = set()
132 132 envitems = [
133 133 (k, v)
134 134 for k, v in pycompat.iteritems(encoding.environ)
135 135 if _envre.match(k) and k not in ignored
136 136 ]
137 137 envhash = _hashlist(sorted(envitems))
138 138 return sectionhash[:6] + envhash[:6]
139 139
140 140
141 141 def _getmtimepaths(ui):
142 142 """get a list of paths that should be checked to detect change
143 143
144 144 The list will include:
145 145 - extensions (will not cover all files for complex extensions)
146 146 - mercurial/__version__.py
147 147 - python binary
148 148 """
149 149 modules = [m for n, m in extensions.extensions(ui)]
150 150 try:
151 151 from . import __version__
152 152
153 153 modules.append(__version__)
154 154 except ImportError:
155 155 pass
156 156 files = []
157 157 if pycompat.sysexecutable:
158 158 files.append(pycompat.sysexecutable)
159 159 for m in modules:
160 160 try:
161 161 files.append(pycompat.fsencode(inspect.getabsfile(m)))
162 162 except TypeError:
163 163 pass
164 164 return sorted(set(files))
165 165
166 166
167 167 def _mtimehash(paths):
168 168 """return a quick hash for detecting file changes
169 169
170 170 mtimehash calls stat on given paths and calculate a hash based on size and
171 171 mtime of each file. mtimehash does not read file content because reading is
172 172 expensive. therefore it's not 100% reliable for detecting content changes.
173 173 it's possible to return different hashes for same file contents.
174 174 it's also possible to return a same hash for different file contents for
175 175 some carefully crafted situation.
176 176
177 177 for chgserver, it is designed that once mtimehash changes, the server is
178 178 considered outdated immediately and should no longer provide service.
179 179
180 180 mtimehash is not included in confighash because we only know the paths of
181 181 extensions after importing them (there is imp.find_module but that faces
182 182 race conditions). We need to calculate confighash without importing.
183 183 """
184 184
185 185 def trystat(path):
186 186 try:
187 187 st = os.stat(path)
188 188 return (st[stat.ST_MTIME], st.st_size)
189 189 except OSError:
190 190 # could be ENOENT, EPERM etc. not fatal in any case
191 191 pass
192 192
193 193 return _hashlist(pycompat.maplist(trystat, paths))[:12]
194 194
195 195
196 196 class hashstate(object):
197 197 """a structure storing confighash, mtimehash, paths used for mtimehash"""
198 198
199 199 def __init__(self, confighash, mtimehash, mtimepaths):
200 200 self.confighash = confighash
201 201 self.mtimehash = mtimehash
202 202 self.mtimepaths = mtimepaths
203 203
204 204 @staticmethod
205 205 def fromui(ui, mtimepaths=None):
206 206 if mtimepaths is None:
207 207 mtimepaths = _getmtimepaths(ui)
208 208 confighash = _confighash(ui)
209 209 mtimehash = _mtimehash(mtimepaths)
210 210 ui.log(
211 211 b'cmdserver',
212 212 b'confighash = %s mtimehash = %s\n',
213 213 confighash,
214 214 mtimehash,
215 215 )
216 216 return hashstate(confighash, mtimehash, mtimepaths)
217 217
218 218
219 219 def _newchgui(srcui, csystem, attachio):
220 220 class chgui(srcui.__class__):
221 221 def __init__(self, src=None):
222 222 super(chgui, self).__init__(src)
223 223 if src:
224 224 self._csystem = getattr(src, '_csystem', csystem)
225 225 else:
226 226 self._csystem = csystem
227 227
228 228 def _runsystem(self, cmd, environ, cwd, out):
229 229 # fallback to the original system method if
230 230 # a. the output stream is not stdout (e.g. stderr, cStringIO),
231 231 # b. or stdout is redirected by protectfinout(),
232 232 # because the chg client is not aware of these situations and
233 233 # will behave differently (i.e. write to stdout).
234 234 if (
235 235 out is not self.fout
236 236 or not util.safehasattr(self.fout, b'fileno')
237 237 or self.fout.fileno() != procutil.stdout.fileno()
238 238 or self._finoutredirected
239 239 ):
240 240 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
241 241 self.flush()
242 242 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
243 243
244 244 def _runpager(self, cmd, env=None):
245 245 self._csystem(
246 246 cmd,
247 247 procutil.shellenviron(env),
248 248 type=b'pager',
249 249 cmdtable={b'attachio': attachio},
250 250 )
251 251 return True
252 252
253 253 return chgui(srcui)
254 254
255 255
256 256 def _loadnewui(srcui, args, cdebug):
257 257 from . import dispatch # avoid cycle
258 258
259 259 newui = srcui.__class__.load()
260 260 for a in [b'fin', b'fout', b'ferr', b'environ']:
261 261 setattr(newui, a, getattr(srcui, a))
262 262 if util.safehasattr(srcui, b'_csystem'):
263 263 newui._csystem = srcui._csystem
264 264
265 265 # command line args
266 266 options = dispatch._earlyparseopts(newui, args)
267 267 dispatch._parseconfig(newui, options[b'config'])
268 268
269 269 # stolen from tortoisehg.util.copydynamicconfig()
270 270 for section, name, value in srcui.walkconfig():
271 271 source = srcui.configsource(section, name)
272 272 if b':' in source or source == b'--config' or source.startswith(b'$'):
273 273 # path:line or command line, or environ
274 274 continue
275 275 newui.setconfig(section, name, value, source)
276 276
277 277 # load wd and repo config, copied from dispatch.py
278 278 cwd = options[b'cwd']
279 279 cwd = cwd and os.path.realpath(cwd) or None
280 280 rpath = options[b'repository']
281 281 path, newlui = dispatch._getlocal(newui, rpath, wd=cwd)
282 282
283 283 extensions.populateui(newui)
284 284 commandserver.setuplogging(newui, fp=cdebug)
285 285 if newui is not newlui:
286 286 extensions.populateui(newlui)
287 287 commandserver.setuplogging(newlui, fp=cdebug)
288 288
289 289 return (newui, newlui)
290 290
291 291
292 292 class channeledsystem(object):
293 293 """Propagate ui.system() request in the following format:
294 294
295 295 payload length (unsigned int),
296 296 type, '\0',
297 297 cmd, '\0',
298 298 cwd, '\0',
299 299 envkey, '=', val, '\0',
300 300 ...
301 301 envkey, '=', val
302 302
303 303 if type == 'system', waits for:
304 304
305 305 exitcode length (unsigned int),
306 306 exitcode (int)
307 307
308 308 if type == 'pager', repetitively waits for a command name ending with '\n'
309 309 and executes it defined by cmdtable, or exits the loop if the command name
310 310 is empty.
311 311 """
312 312
313 313 def __init__(self, in_, out, channel):
314 314 self.in_ = in_
315 315 self.out = out
316 316 self.channel = channel
317 317
318 318 def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
319 319 args = [type, procutil.quotecommand(cmd), os.path.abspath(cwd or b'.')]
320 320 args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
321 321 data = b'\0'.join(args)
322 322 self.out.write(struct.pack(b'>cI', self.channel, len(data)))
323 323 self.out.write(data)
324 324 self.out.flush()
325 325
326 326 if type == b'system':
327 327 length = self.in_.read(4)
328 328 (length,) = struct.unpack(b'>I', length)
329 329 if length != 4:
330 330 raise error.Abort(_(b'invalid response'))
331 331 (rc,) = struct.unpack(b'>i', self.in_.read(4))
332 332 return rc
333 333 elif type == b'pager':
334 334 while True:
335 335 cmd = self.in_.readline()[:-1]
336 336 if not cmd:
337 337 break
338 338 if cmdtable and cmd in cmdtable:
339 339 cmdtable[cmd]()
340 340 else:
341 341 raise error.Abort(_(b'unexpected command: %s') % cmd)
342 342 else:
343 343 raise error.ProgrammingError(b'invalid S channel type: %s' % type)
344 344
345 345
346 346 _iochannels = [
347 347 # server.ch, ui.fp, mode
348 348 (b'cin', b'fin', 'rb'),
349 349 (b'cout', b'fout', 'wb'),
350 350 (b'cerr', b'ferr', 'wb'),
351 351 ]
352 352
353 353
354 354 class chgcmdserver(commandserver.server):
355 355 def __init__(
356 356 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
357 357 ):
358 358 super(chgcmdserver, self).__init__(
359 359 _newchgui(ui, channeledsystem(fin, fout, b'S'), self.attachio),
360 360 repo,
361 361 fin,
362 362 fout,
363 363 prereposetups,
364 364 )
365 365 self.clientsock = sock
366 366 self._ioattached = False
367 367 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
368 368 self.hashstate = hashstate
369 369 self.baseaddress = baseaddress
370 370 if hashstate is not None:
371 371 self.capabilities = self.capabilities.copy()
372 372 self.capabilities[b'validate'] = chgcmdserver.validate
373 373
374 374 def cleanup(self):
375 375 super(chgcmdserver, self).cleanup()
376 376 # dispatch._runcatch() does not flush outputs if exception is not
377 377 # handled by dispatch._dispatch()
378 378 self.ui.flush()
379 379 self._restoreio()
380 380 self._ioattached = False
381 381
382 382 def attachio(self):
383 383 """Attach to client's stdio passed via unix domain socket; all
384 384 channels except cresult will no longer be used
385 385 """
386 386 # tell client to sendmsg() with 1-byte payload, which makes it
387 387 # distinctive from "attachio\n" command consumed by client.read()
388 388 self.clientsock.sendall(struct.pack(b'>cI', b'I', 1))
389 389 clientfds = util.recvfds(self.clientsock.fileno())
390 390 self.ui.log(b'chgserver', b'received fds: %r\n', clientfds)
391 391
392 392 ui = self.ui
393 393 ui.flush()
394 394 self._saveio()
395 395 for fd, (cn, fn, mode) in zip(clientfds, _iochannels):
396 396 assert fd > 0
397 397 fp = getattr(ui, fn)
398 398 os.dup2(fd, fp.fileno())
399 399 os.close(fd)
400 400 if self._ioattached:
401 401 continue
402 402 # reset buffering mode when client is first attached. as we want
403 403 # to see output immediately on pager, the mode stays unchanged
404 404 # when client re-attached. ferr is unchanged because it should
405 405 # be unbuffered no matter if it is a tty or not.
406 406 if fn == b'ferr':
407 407 newfp = fp
408 408 else:
409 409 # make it line buffered explicitly because the default is
410 410 # decided on first write(), where fout could be a pager.
411 411 if fp.isatty():
412 412 bufsize = 1 # line buffered
413 413 else:
414 414 bufsize = -1 # system default
415 415 newfp = os.fdopen(fp.fileno(), mode, bufsize)
416 416 setattr(ui, fn, newfp)
417 417 setattr(self, cn, newfp)
418 418
419 419 self._ioattached = True
420 420 self.cresult.write(struct.pack(b'>i', len(clientfds)))
421 421
422 422 def _saveio(self):
423 423 if self._oldios:
424 424 return
425 425 ui = self.ui
426 426 for cn, fn, _mode in _iochannels:
427 427 ch = getattr(self, cn)
428 428 fp = getattr(ui, fn)
429 429 fd = os.dup(fp.fileno())
430 430 self._oldios.append((ch, fp, fd))
431 431
432 432 def _restoreio(self):
433 433 ui = self.ui
434 434 for (ch, fp, fd), (cn, fn, _mode) in zip(self._oldios, _iochannels):
435 435 newfp = getattr(ui, fn)
436 436 # close newfp while it's associated with client; otherwise it
437 437 # would be closed when newfp is deleted
438 438 if newfp is not fp:
439 439 newfp.close()
440 440 # restore original fd: fp is open again
441 441 os.dup2(fd, fp.fileno())
442 442 os.close(fd)
443 443 setattr(self, cn, ch)
444 444 setattr(ui, fn, fp)
445 445 del self._oldios[:]
446 446
447 447 def validate(self):
448 448 """Reload the config and check if the server is up to date
449 449
450 450 Read a list of '\0' separated arguments.
451 451 Write a non-empty list of '\0' separated instruction strings or '\0'
452 452 if the list is empty.
453 453 An instruction string could be either:
454 454 - "unlink $path", the client should unlink the path to stop the
455 455 outdated server.
456 456 - "redirect $path", the client should attempt to connect to $path
457 457 first. If it does not work, start a new server. It implies
458 458 "reconnect".
459 459 - "exit $n", the client should exit directly with code n.
460 460 This may happen if we cannot parse the config.
461 461 - "reconnect", the client should close the connection and
462 462 reconnect.
463 463 If neither "reconnect" nor "redirect" is included in the instruction
464 464 list, the client can continue with this server after completing all
465 465 the instructions.
466 466 """
467 467 from . import dispatch # avoid cycle
468 468
469 469 args = self._readlist()
470 470 try:
471 471 self.ui, lui = _loadnewui(self.ui, args, self.cdebug)
472 472 except error.ParseError as inst:
473 473 dispatch._formatparse(self.ui.warn, inst)
474 474 self.ui.flush()
475 475 self.cresult.write(b'exit 255')
476 476 return
477 477 except error.Abort as inst:
478 478 self.ui.error(_(b"abort: %s\n") % inst)
479 479 if inst.hint:
480 480 self.ui.error(_(b"(%s)\n") % inst.hint)
481 481 self.ui.flush()
482 482 self.cresult.write(b'exit 255')
483 483 return
484 484 newhash = hashstate.fromui(lui, self.hashstate.mtimepaths)
485 485 insts = []
486 486 if newhash.mtimehash != self.hashstate.mtimehash:
487 487 addr = _hashaddress(self.baseaddress, self.hashstate.confighash)
488 488 insts.append(b'unlink %s' % addr)
489 489 # mtimehash is empty if one or more extensions fail to load.
490 490 # to be compatible with hg, still serve the client this time.
491 491 if self.hashstate.mtimehash:
492 492 insts.append(b'reconnect')
493 493 if newhash.confighash != self.hashstate.confighash:
494 494 addr = _hashaddress(self.baseaddress, newhash.confighash)
495 495 insts.append(b'redirect %s' % addr)
496 496 self.ui.log(b'chgserver', b'validate: %s\n', stringutil.pprint(insts))
497 497 self.cresult.write(b'\0'.join(insts) or b'\0')
498 498
499 499 def chdir(self):
500 500 """Change current directory
501 501
502 502 Note that the behavior of --cwd option is bit different from this.
503 503 It does not affect --config parameter.
504 504 """
505 505 path = self._readstr()
506 506 if not path:
507 507 return
508 self.ui.log(b'chgserver', b'chdir to %r\n', path)
508 self.ui.log(b'chgserver', b"chdir to '%s'\n", path)
509 509 os.chdir(path)
510 510
511 511 def setumask(self):
512 512 """Change umask (DEPRECATED)"""
513 513 # BUG: this does not follow the message frame structure, but kept for
514 514 # backward compatibility with old chg clients for some time
515 515 self._setumask(self._read(4))
516 516
517 517 def setumask2(self):
518 518 """Change umask"""
519 519 data = self._readstr()
520 520 if len(data) != 4:
521 521 raise ValueError(b'invalid mask length in setumask2 request')
522 522 self._setumask(data)
523 523
524 524 def _setumask(self, data):
525 525 mask = struct.unpack(b'>I', data)[0]
526 526 self.ui.log(b'chgserver', b'setumask %r\n', mask)
527 527 os.umask(mask)
528 528
529 529 def runcommand(self):
530 530 # pager may be attached within the runcommand session, which should
531 531 # be detached at the end of the session. otherwise the pager wouldn't
532 532 # receive EOF.
533 533 globaloldios = self._oldios
534 534 self._oldios = []
535 535 try:
536 536 return super(chgcmdserver, self).runcommand()
537 537 finally:
538 538 self._restoreio()
539 539 self._oldios = globaloldios
540 540
541 541 def setenv(self):
542 542 """Clear and update os.environ
543 543
544 544 Note that not all variables can make an effect on the running process.
545 545 """
546 546 l = self._readlist()
547 547 try:
548 548 newenv = dict(s.split(b'=', 1) for s in l)
549 549 except ValueError:
550 550 raise ValueError(b'unexpected value in setenv request')
551 551 self.ui.log(b'chgserver', b'setenv: %r\n', sorted(newenv.keys()))
552 552 encoding.environ.clear()
553 553 encoding.environ.update(newenv)
554 554
555 555 capabilities = commandserver.server.capabilities.copy()
556 556 capabilities.update(
557 557 {
558 558 b'attachio': attachio,
559 559 b'chdir': chdir,
560 560 b'runcommand': runcommand,
561 561 b'setenv': setenv,
562 562 b'setumask': setumask,
563 563 b'setumask2': setumask2,
564 564 }
565 565 )
566 566
567 567 if util.safehasattr(procutil, b'setprocname'):
568 568
569 569 def setprocname(self):
570 570 """Change process title"""
571 571 name = self._readstr()
572 572 self.ui.log(b'chgserver', b'setprocname: %r\n', name)
573 573 procutil.setprocname(name)
574 574
575 575 capabilities[b'setprocname'] = setprocname
576 576
577 577
578 578 def _tempaddress(address):
579 579 return b'%s.%d.tmp' % (address, os.getpid())
580 580
581 581
582 582 def _hashaddress(address, hashstr):
583 583 # if the basename of address contains '.', use only the left part. this
584 584 # makes it possible for the client to pass 'server.tmp$PID' and follow by
585 585 # an atomic rename to avoid locking when spawning new servers.
586 586 dirname, basename = os.path.split(address)
587 587 basename = basename.split(b'.', 1)[0]
588 588 return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
589 589
590 590
591 591 class chgunixservicehandler(object):
592 592 """Set of operations for chg services"""
593 593
594 594 pollinterval = 1 # [sec]
595 595
596 596 def __init__(self, ui):
597 597 self.ui = ui
598 598 self._idletimeout = ui.configint(b'chgserver', b'idletimeout')
599 599 self._lastactive = time.time()
600 600
601 601 def bindsocket(self, sock, address):
602 602 self._inithashstate(address)
603 603 self._checkextensions()
604 604 self._bind(sock)
605 605 self._createsymlink()
606 606 # no "listening at" message should be printed to simulate hg behavior
607 607
608 608 def _inithashstate(self, address):
609 609 self._baseaddress = address
610 610 if self.ui.configbool(b'chgserver', b'skiphash'):
611 611 self._hashstate = None
612 612 self._realaddress = address
613 613 return
614 614 self._hashstate = hashstate.fromui(self.ui)
615 615 self._realaddress = _hashaddress(address, self._hashstate.confighash)
616 616
617 617 def _checkextensions(self):
618 618 if not self._hashstate:
619 619 return
620 620 if extensions.notloaded():
621 621 # one or more extensions failed to load. mtimehash becomes
622 622 # meaningless because we do not know the paths of those extensions.
623 623 # set mtimehash to an illegal hash value to invalidate the server.
624 624 self._hashstate.mtimehash = b''
625 625
626 626 def _bind(self, sock):
627 627 # use a unique temp address so we can stat the file and do ownership
628 628 # check later
629 629 tempaddress = _tempaddress(self._realaddress)
630 630 util.bindunixsocket(sock, tempaddress)
631 631 self._socketstat = os.stat(tempaddress)
632 632 sock.listen(socket.SOMAXCONN)
633 633 # rename will replace the old socket file if exists atomically. the
634 634 # old server will detect ownership change and exit.
635 635 util.rename(tempaddress, self._realaddress)
636 636
637 637 def _createsymlink(self):
638 638 if self._baseaddress == self._realaddress:
639 639 return
640 640 tempaddress = _tempaddress(self._baseaddress)
641 641 os.symlink(os.path.basename(self._realaddress), tempaddress)
642 642 util.rename(tempaddress, self._baseaddress)
643 643
644 644 def _issocketowner(self):
645 645 try:
646 646 st = os.stat(self._realaddress)
647 647 return (
648 648 st.st_ino == self._socketstat.st_ino
649 649 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
650 650 )
651 651 except OSError:
652 652 return False
653 653
654 654 def unlinksocket(self, address):
655 655 if not self._issocketowner():
656 656 return
657 657 # it is possible to have a race condition here that we may
658 658 # remove another server's socket file. but that's okay
659 659 # since that server will detect and exit automatically and
660 660 # the client will start a new server on demand.
661 661 util.tryunlink(self._realaddress)
662 662
663 663 def shouldexit(self):
664 664 if not self._issocketowner():
665 665 self.ui.log(
666 666 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
667 667 )
668 668 return True
669 669 if time.time() - self._lastactive > self._idletimeout:
670 670 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
671 671 return True
672 672 return False
673 673
674 674 def newconnection(self):
675 675 self._lastactive = time.time()
676 676
677 677 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
678 678 return chgcmdserver(
679 679 self.ui,
680 680 repo,
681 681 fin,
682 682 fout,
683 683 conn,
684 684 prereposetups,
685 685 self._hashstate,
686 686 self._baseaddress,
687 687 )
688 688
689 689
690 690 def chgunixservice(ui, repo, opts):
691 691 # CHGINTERNALMARK is set by chg client. It is an indication of things are
692 692 # started by chg so other code can do things accordingly, like disabling
693 693 # demandimport or detecting chg client started by chg client. When executed
694 694 # here, CHGINTERNALMARK is no longer useful and hence dropped to make
695 695 # environ cleaner.
696 696 if b'CHGINTERNALMARK' in encoding.environ:
697 697 del encoding.environ[b'CHGINTERNALMARK']
698 698
699 699 if repo:
700 700 # one chgserver can serve multiple repos. drop repo information
701 701 ui.setconfig(b'bundle', b'mainreporoot', b'', b'repo')
702 702 h = chgunixservicehandler(ui)
703 703 return commandserver.unixforkingservice(ui, repo=None, opts=opts, handler=h)
General Comments 0
You need to be logged in to leave comments. Login now