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