comparison mercurial/chgserver.py @ 43076:2372284d9457

formatting: blacken the codebase This is using my patch to black (https://github.com/psf/black/pull/826) so we don't un-wrap collection literals. Done with: hg files 'set:**.py - mercurial/thirdparty/** - "contrib/python-zstandard/**"' | xargs black -S # skip-blame mass-reformatting only # no-check-commit reformats foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D6971
author Augie Fackler <augie@google.com>
date Sun, 06 Oct 2019 09:45:02 -0400
parents 0cbe17335857
children 687b865b95ad
comparison
equal deleted inserted replaced
43075:57875cf423c9 43076:2372284d9457
65 from .utils import ( 65 from .utils import (
66 procutil, 66 procutil,
67 stringutil, 67 stringutil,
68 ) 68 )
69 69
70
70 def _hashlist(items): 71 def _hashlist(items):
71 """return sha1 hexdigest for a list""" 72 """return sha1 hexdigest for a list"""
72 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest()) 73 return node.hex(hashlib.sha1(stringutil.pprint(items)).digest())
73 74
75
74 # sensitive config sections affecting confighash 76 # sensitive config sections affecting confighash
75 _configsections = [ 77 _configsections = [
76 'alias', # affects global state commands.table 78 'alias', # affects global state commands.table
77 'eol', # uses setconfig('eol', ...) 79 'eol', # uses setconfig('eol', ...)
78 'extdiff', # uisetup will register new commands 80 'extdiff', # uisetup will register new commands
79 'extensions', 81 'extensions',
80 ] 82 ]
81 83
82 _configsectionitems = [ 84 _configsectionitems = [
83 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup 85 ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
84 ] 86 ]
85 87
86 # sensitive environment variables affecting confighash 88 # sensitive environment variables affecting confighash
87 _envre = re.compile(br'''\A(?: 89 _envre = re.compile(
90 br'''\A(?:
88 CHGHG 91 CHGHG
89 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)? 92 |HG(?:DEMANDIMPORT|EMITWARNINGS|MODULEPOLICY|PROF|RCPATH)?
90 |HG(?:ENCODING|PLAIN).* 93 |HG(?:ENCODING|PLAIN).*
91 |LANG(?:UAGE)? 94 |LANG(?:UAGE)?
92 |LC_.* 95 |LC_.*
93 |LD_.* 96 |LD_.*
94 |PATH 97 |PATH
95 |PYTHON.* 98 |PYTHON.*
96 |TERM(?:INFO)? 99 |TERM(?:INFO)?
97 |TZ 100 |TZ
98 )\Z''', re.X) 101 )\Z''',
102 re.X,
103 )
104
99 105
100 def _confighash(ui): 106 def _confighash(ui):
101 """return a quick hash for detecting config/env changes 107 """return a quick hash for detecting config/env changes
102 108
103 confighash is the hash of sensitive config items and environment variables. 109 confighash is the hash of sensitive config items and environment variables.
117 # If $CHGHG is set, the change to $HG should not trigger a new chg server 123 # If $CHGHG is set, the change to $HG should not trigger a new chg server
118 if 'CHGHG' in encoding.environ: 124 if 'CHGHG' in encoding.environ:
119 ignored = {'HG'} 125 ignored = {'HG'}
120 else: 126 else:
121 ignored = set() 127 ignored = set()
122 envitems = [(k, v) for k, v in encoding.environ.iteritems() 128 envitems = [
123 if _envre.match(k) and k not in ignored] 129 (k, v)
130 for k, v in encoding.environ.iteritems()
131 if _envre.match(k) and k not in ignored
132 ]
124 envhash = _hashlist(sorted(envitems)) 133 envhash = _hashlist(sorted(envitems))
125 return sectionhash[:6] + envhash[:6] 134 return sectionhash[:6] + envhash[:6]
135
126 136
127 def _getmtimepaths(ui): 137 def _getmtimepaths(ui):
128 """get a list of paths that should be checked to detect change 138 """get a list of paths that should be checked to detect change
129 139
130 The list will include: 140 The list will include:
133 - python binary 143 - python binary
134 """ 144 """
135 modules = [m for n, m in extensions.extensions(ui)] 145 modules = [m for n, m in extensions.extensions(ui)]
136 try: 146 try:
137 from . import __version__ 147 from . import __version__
148
138 modules.append(__version__) 149 modules.append(__version__)
139 except ImportError: 150 except ImportError:
140 pass 151 pass
141 files = [] 152 files = []
142 if pycompat.sysexecutable: 153 if pycompat.sysexecutable:
146 files.append(pycompat.fsencode(inspect.getabsfile(m))) 157 files.append(pycompat.fsencode(inspect.getabsfile(m)))
147 except TypeError: 158 except TypeError:
148 pass 159 pass
149 return sorted(set(files)) 160 return sorted(set(files))
150 161
162
151 def _mtimehash(paths): 163 def _mtimehash(paths):
152 """return a quick hash for detecting file changes 164 """return a quick hash for detecting file changes
153 165
154 mtimehash calls stat on given paths and calculate a hash based on size and 166 mtimehash calls stat on given paths and calculate a hash based on size and
155 mtime of each file. mtimehash does not read file content because reading is 167 mtime of each file. mtimehash does not read file content because reading is
163 175
164 mtimehash is not included in confighash because we only know the paths of 176 mtimehash is not included in confighash because we only know the paths of
165 extensions after importing them (there is imp.find_module but that faces 177 extensions after importing them (there is imp.find_module but that faces
166 race conditions). We need to calculate confighash without importing. 178 race conditions). We need to calculate confighash without importing.
167 """ 179 """
180
168 def trystat(path): 181 def trystat(path):
169 try: 182 try:
170 st = os.stat(path) 183 st = os.stat(path)
171 return (st[stat.ST_MTIME], st.st_size) 184 return (st[stat.ST_MTIME], st.st_size)
172 except OSError: 185 except OSError:
173 # could be ENOENT, EPERM etc. not fatal in any case 186 # could be ENOENT, EPERM etc. not fatal in any case
174 pass 187 pass
188
175 return _hashlist(pycompat.maplist(trystat, paths))[:12] 189 return _hashlist(pycompat.maplist(trystat, paths))[:12]
190
176 191
177 class hashstate(object): 192 class hashstate(object):
178 """a structure storing confighash, mtimehash, paths used for mtimehash""" 193 """a structure storing confighash, mtimehash, paths used for mtimehash"""
194
179 def __init__(self, confighash, mtimehash, mtimepaths): 195 def __init__(self, confighash, mtimehash, mtimepaths):
180 self.confighash = confighash 196 self.confighash = confighash
181 self.mtimehash = mtimehash 197 self.mtimehash = mtimehash
182 self.mtimepaths = mtimepaths 198 self.mtimepaths = mtimepaths
183 199
185 def fromui(ui, mtimepaths=None): 201 def fromui(ui, mtimepaths=None):
186 if mtimepaths is None: 202 if mtimepaths is None:
187 mtimepaths = _getmtimepaths(ui) 203 mtimepaths = _getmtimepaths(ui)
188 confighash = _confighash(ui) 204 confighash = _confighash(ui)
189 mtimehash = _mtimehash(mtimepaths) 205 mtimehash = _mtimehash(mtimepaths)
190 ui.log('cmdserver', 'confighash = %s mtimehash = %s\n', 206 ui.log(
191 confighash, mtimehash) 207 'cmdserver',
208 'confighash = %s mtimehash = %s\n',
209 confighash,
210 mtimehash,
211 )
192 return hashstate(confighash, mtimehash, mtimepaths) 212 return hashstate(confighash, mtimehash, mtimepaths)
213
193 214
194 def _newchgui(srcui, csystem, attachio): 215 def _newchgui(srcui, csystem, attachio):
195 class chgui(srcui.__class__): 216 class chgui(srcui.__class__):
196 def __init__(self, src=None): 217 def __init__(self, src=None):
197 super(chgui, self).__init__(src) 218 super(chgui, self).__init__(src)
204 # fallback to the original system method if 225 # fallback to the original system method if
205 # a. the output stream is not stdout (e.g. stderr, cStringIO), 226 # a. the output stream is not stdout (e.g. stderr, cStringIO),
206 # b. or stdout is redirected by protectfinout(), 227 # b. or stdout is redirected by protectfinout(),
207 # because the chg client is not aware of these situations and 228 # because the chg client is not aware of these situations and
208 # will behave differently (i.e. write to stdout). 229 # will behave differently (i.e. write to stdout).
209 if (out is not self.fout 230 if (
231 out is not self.fout
210 or not util.safehasattr(self.fout, 'fileno') 232 or not util.safehasattr(self.fout, 'fileno')
211 or self.fout.fileno() != procutil.stdout.fileno() 233 or self.fout.fileno() != procutil.stdout.fileno()
212 or self._finoutredirected): 234 or self._finoutredirected
235 ):
213 return procutil.system(cmd, environ=environ, cwd=cwd, out=out) 236 return procutil.system(cmd, environ=environ, cwd=cwd, out=out)
214 self.flush() 237 self.flush()
215 return self._csystem(cmd, procutil.shellenviron(environ), cwd) 238 return self._csystem(cmd, procutil.shellenviron(environ), cwd)
216 239
217 def _runpager(self, cmd, env=None): 240 def _runpager(self, cmd, env=None):
218 self._csystem(cmd, procutil.shellenviron(env), type='pager', 241 self._csystem(
219 cmdtable={'attachio': attachio}) 242 cmd,
243 procutil.shellenviron(env),
244 type='pager',
245 cmdtable={'attachio': attachio},
246 )
220 return True 247 return True
221 248
222 return chgui(srcui) 249 return chgui(srcui)
250
223 251
224 def _loadnewui(srcui, args, cdebug): 252 def _loadnewui(srcui, args, cdebug):
225 from . import dispatch # avoid cycle 253 from . import dispatch # avoid cycle
226 254
227 newui = srcui.__class__.load() 255 newui = srcui.__class__.load()
254 extensions.populateui(newlui) 282 extensions.populateui(newlui)
255 commandserver.setuplogging(newlui, fp=cdebug) 283 commandserver.setuplogging(newlui, fp=cdebug)
256 284
257 return (newui, newlui) 285 return (newui, newlui)
258 286
287
259 class channeledsystem(object): 288 class channeledsystem(object):
260 """Propagate ui.system() request in the following format: 289 """Propagate ui.system() request in the following format:
261 290
262 payload length (unsigned int), 291 payload length (unsigned int),
263 type, '\0', 292 type, '\0',
274 303
275 if type == 'pager', repetitively waits for a command name ending with '\n' 304 if type == 'pager', repetitively waits for a command name ending with '\n'
276 and executes it defined by cmdtable, or exits the loop if the command name 305 and executes it defined by cmdtable, or exits the loop if the command name
277 is empty. 306 is empty.
278 """ 307 """
308
279 def __init__(self, in_, out, channel): 309 def __init__(self, in_, out, channel):
280 self.in_ = in_ 310 self.in_ = in_
281 self.out = out 311 self.out = out
282 self.channel = channel 312 self.channel = channel
283 313
289 self.out.write(data) 319 self.out.write(data)
290 self.out.flush() 320 self.out.flush()
291 321
292 if type == 'system': 322 if type == 'system':
293 length = self.in_.read(4) 323 length = self.in_.read(4)
294 length, = struct.unpack('>I', length) 324 (length,) = struct.unpack('>I', length)
295 if length != 4: 325 if length != 4:
296 raise error.Abort(_('invalid response')) 326 raise error.Abort(_('invalid response'))
297 rc, = struct.unpack('>i', self.in_.read(4)) 327 (rc,) = struct.unpack('>i', self.in_.read(4))
298 return rc 328 return rc
299 elif type == 'pager': 329 elif type == 'pager':
300 while True: 330 while True:
301 cmd = self.in_.readline()[:-1] 331 cmd = self.in_.readline()[:-1]
302 if not cmd: 332 if not cmd:
306 else: 336 else:
307 raise error.Abort(_('unexpected command: %s') % cmd) 337 raise error.Abort(_('unexpected command: %s') % cmd)
308 else: 338 else:
309 raise error.ProgrammingError('invalid S channel type: %s' % type) 339 raise error.ProgrammingError('invalid S channel type: %s' % type)
310 340
341
311 _iochannels = [ 342 _iochannels = [
312 # server.ch, ui.fp, mode 343 # server.ch, ui.fp, mode
313 ('cin', 'fin', r'rb'), 344 ('cin', 'fin', r'rb'),
314 ('cout', 'fout', r'wb'), 345 ('cout', 'fout', r'wb'),
315 ('cerr', 'ferr', r'wb'), 346 ('cerr', 'ferr', r'wb'),
316 ] 347 ]
317 348
349
318 class chgcmdserver(commandserver.server): 350 class chgcmdserver(commandserver.server):
319 def __init__(self, ui, repo, fin, fout, sock, prereposetups, 351 def __init__(
320 hashstate, baseaddress): 352 self, ui, repo, fin, fout, sock, prereposetups, hashstate, baseaddress
353 ):
321 super(chgcmdserver, self).__init__( 354 super(chgcmdserver, self).__init__(
322 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio), 355 _newchgui(ui, channeledsystem(fin, fout, 'S'), self.attachio),
323 repo, fin, fout, prereposetups) 356 repo,
357 fin,
358 fout,
359 prereposetups,
360 )
324 self.clientsock = sock 361 self.clientsock = sock
325 self._ioattached = False 362 self._ioattached = False
326 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio" 363 self._oldios = [] # original (self.ch, ui.fp, fd) before "attachio"
327 self.hashstate = hashstate 364 self.hashstate = hashstate
328 self.baseaddress = baseaddress 365 self.baseaddress = baseaddress
510 self.ui.log('chgserver', 'setenv: %r\n', sorted(newenv.keys())) 547 self.ui.log('chgserver', 'setenv: %r\n', sorted(newenv.keys()))
511 encoding.environ.clear() 548 encoding.environ.clear()
512 encoding.environ.update(newenv) 549 encoding.environ.update(newenv)
513 550
514 capabilities = commandserver.server.capabilities.copy() 551 capabilities = commandserver.server.capabilities.copy()
515 capabilities.update({'attachio': attachio, 552 capabilities.update(
516 'chdir': chdir, 553 {
517 'runcommand': runcommand, 554 'attachio': attachio,
518 'setenv': setenv, 555 'chdir': chdir,
519 'setumask': setumask, 556 'runcommand': runcommand,
520 'setumask2': setumask2}) 557 'setenv': setenv,
558 'setumask': setumask,
559 'setumask2': setumask2,
560 }
561 )
521 562
522 if util.safehasattr(procutil, 'setprocname'): 563 if util.safehasattr(procutil, 'setprocname'):
564
523 def setprocname(self): 565 def setprocname(self):
524 """Change process title""" 566 """Change process title"""
525 name = self._readstr() 567 name = self._readstr()
526 self.ui.log('chgserver', 'setprocname: %r\n', name) 568 self.ui.log('chgserver', 'setprocname: %r\n', name)
527 procutil.setprocname(name) 569 procutil.setprocname(name)
570
528 capabilities['setprocname'] = setprocname 571 capabilities['setprocname'] = setprocname
572
529 573
530 def _tempaddress(address): 574 def _tempaddress(address):
531 return '%s.%d.tmp' % (address, os.getpid()) 575 return '%s.%d.tmp' % (address, os.getpid())
576
532 577
533 def _hashaddress(address, hashstr): 578 def _hashaddress(address, hashstr):
534 # if the basename of address contains '.', use only the left part. this 579 # if the basename of address contains '.', use only the left part. this
535 # makes it possible for the client to pass 'server.tmp$PID' and follow by 580 # makes it possible for the client to pass 'server.tmp$PID' and follow by
536 # an atomic rename to avoid locking when spawning new servers. 581 # an atomic rename to avoid locking when spawning new servers.
537 dirname, basename = os.path.split(address) 582 dirname, basename = os.path.split(address)
538 basename = basename.split('.', 1)[0] 583 basename = basename.split('.', 1)[0]
539 return '%s-%s' % (os.path.join(dirname, basename), hashstr) 584 return '%s-%s' % (os.path.join(dirname, basename), hashstr)
585
540 586
541 class chgunixservicehandler(object): 587 class chgunixservicehandler(object):
542 """Set of operations for chg services""" 588 """Set of operations for chg services"""
543 589
544 pollinterval = 1 # [sec] 590 pollinterval = 1 # [sec]
592 util.rename(tempaddress, self._baseaddress) 638 util.rename(tempaddress, self._baseaddress)
593 639
594 def _issocketowner(self): 640 def _issocketowner(self):
595 try: 641 try:
596 st = os.stat(self._realaddress) 642 st = os.stat(self._realaddress)
597 return (st.st_ino == self._socketstat.st_ino and 643 return (
598 st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]) 644 st.st_ino == self._socketstat.st_ino
645 and st[stat.ST_MTIME] == self._socketstat[stat.ST_MTIME]
646 )
599 except OSError: 647 except OSError:
600 return False 648 return False
601 649
602 def unlinksocket(self, address): 650 def unlinksocket(self, address):
603 if not self._issocketowner(): 651 if not self._issocketowner():
608 # the client will start a new server on demand. 656 # the client will start a new server on demand.
609 util.tryunlink(self._realaddress) 657 util.tryunlink(self._realaddress)
610 658
611 def shouldexit(self): 659 def shouldexit(self):
612 if not self._issocketowner(): 660 if not self._issocketowner():
613 self.ui.log(b'chgserver', b'%s is not owned, exiting.\n', 661 self.ui.log(
614 self._realaddress) 662 b'chgserver', b'%s is not owned, exiting.\n', self._realaddress
663 )
615 return True 664 return True
616 if time.time() - self._lastactive > self._idletimeout: 665 if time.time() - self._lastactive > self._idletimeout:
617 self.ui.log(b'chgserver', b'being idle too long. exiting.\n') 666 self.ui.log(b'chgserver', b'being idle too long. exiting.\n')
618 return True 667 return True
619 return False 668 return False
620 669
621 def newconnection(self): 670 def newconnection(self):
622 self._lastactive = time.time() 671 self._lastactive = time.time()
623 672
624 def createcmdserver(self, repo, conn, fin, fout, prereposetups): 673 def createcmdserver(self, repo, conn, fin, fout, prereposetups):
625 return chgcmdserver(self.ui, repo, fin, fout, conn, prereposetups, 674 return chgcmdserver(
626 self._hashstate, self._baseaddress) 675 self.ui,
676 repo,
677 fin,
678 fout,
679 conn,
680 prereposetups,
681 self._hashstate,
682 self._baseaddress,
683 )
684
627 685
628 def chgunixservice(ui, repo, opts): 686 def chgunixservice(ui, repo, opts):
629 # CHGINTERNALMARK is set by chg client. It is an indication of things are 687 # CHGINTERNALMARK is set by chg client. It is an indication of things are
630 # started by chg so other code can do things accordingly, like disabling 688 # started by chg so other code can do things accordingly, like disabling
631 # demandimport or detecting chg client started by chg client. When executed 689 # demandimport or detecting chg client started by chg client. When executed