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