hgext/logtoprocess.py
changeset 40499 2e09d1cae90c
parent 40401 6bd477ee7294
child 40619 fbac323eb625
equal deleted inserted replaced
40498:8fab95aa5280 40499:2e09d1cae90c
    34 
    34 
    35 from __future__ import absolute_import
    35 from __future__ import absolute_import
    36 
    36 
    37 import itertools
    37 import itertools
    38 import os
    38 import os
    39 import subprocess
       
    40 import sys
       
    41 
       
    42 from mercurial import (
       
    43     pycompat,
       
    44 )
       
    45 
    39 
    46 from mercurial.utils import (
    40 from mercurial.utils import (
    47     procutil,
    41     procutil,
    48 )
    42 )
    49 
    43 
    52 # be specifying the version(s) of Mercurial they are tested with, or
    46 # be specifying the version(s) of Mercurial they are tested with, or
    53 # leave the attribute unspecified.
    47 # leave the attribute unspecified.
    54 testedwith = 'ships-with-hg-core'
    48 testedwith = 'ships-with-hg-core'
    55 
    49 
    56 def uisetup(ui):
    50 def uisetup(ui):
    57     if pycompat.iswindows:
       
    58         # no fork on Windows, but we can create a detached process
       
    59         # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
       
    60         # No stdlib constant exists for this value
       
    61         DETACHED_PROCESS = 0x00000008
       
    62         _creationflags = DETACHED_PROCESS | subprocess.CREATE_NEW_PROCESS_GROUP
       
    63 
       
    64         def runshellcommand(script, env):
       
    65             # we can't use close_fds *and* redirect stdin. I'm not sure that we
       
    66             # need to because the detached process has no console connection.
       
    67             subprocess.Popen(
       
    68                 procutil.tonativestr(script),
       
    69                 shell=True, env=procutil.tonativeenv(env), close_fds=True,
       
    70                 creationflags=_creationflags)
       
    71     else:
       
    72         def runshellcommand(script, env):
       
    73             # double-fork to completely detach from the parent process
       
    74             # based on http://code.activestate.com/recipes/278731
       
    75             pid = os.fork()
       
    76             if pid:
       
    77                 # parent
       
    78                 return
       
    79             # subprocess.Popen() forks again, all we need to add is
       
    80             # flag the new process as a new session.
       
    81             if sys.version_info < (3, 2):
       
    82                 newsession = {'preexec_fn': os.setsid}
       
    83             else:
       
    84                 newsession = {'start_new_session': True}
       
    85             try:
       
    86                 # connect std* to devnull to make sure the subprocess can't
       
    87                 # muck up these stream for mercurial.
       
    88                 # Connect all the streams to be more close to Windows behavior
       
    89                 # and pager will wait for scripts to end if we don't do that
       
    90                 nullrfd = open(os.devnull, 'r')
       
    91                 nullwfd = open(os.devnull, 'w')
       
    92                 subprocess.Popen(
       
    93                     procutil.tonativestr(script),
       
    94                     shell=True, stdin=nullrfd,
       
    95                     stdout=nullwfd, stderr=nullwfd,
       
    96                     env=procutil.tonativeenv(env),
       
    97                     close_fds=True, **newsession)
       
    98             finally:
       
    99                 # mission accomplished, this child needs to exit and not
       
   100                 # continue the hg process here.
       
   101                 os._exit(0)
       
   102 
    51 
   103     class logtoprocessui(ui.__class__):
    52     class logtoprocessui(ui.__class__):
   104         def log(self, event, *msg, **opts):
    53         def log(self, event, *msg, **opts):
   105             """Map log events to external commands
    54             """Map log events to external commands
   106 
    55 
   131                     ('OPT_{0}'.format(key.upper()), str(value))
    80                     ('OPT_{0}'.format(key.upper()), str(value))
   132                     for key, value in opts.iteritems())
    81                     for key, value in opts.iteritems())
   133                 env = dict(itertools.chain(procutil.shellenviron().items(),
    82                 env = dict(itertools.chain(procutil.shellenviron().items(),
   134                                            msgpairs, optpairs),
    83                                            msgpairs, optpairs),
   135                            EVENT=event, HGPID=str(os.getpid()))
    84                            EVENT=event, HGPID=str(os.getpid()))
   136                 runshellcommand(script, env)
    85                 procutil.runbgcommand(script, env, shell=True)
   137             return super(logtoprocessui, self).log(event, *msg, **opts)
    86             return super(logtoprocessui, self).log(event, *msg, **opts)
   138 
    87 
   139     # Replace the class for this instance and all clones created from it:
    88     # Replace the class for this instance and all clones created from it:
   140     ui.__class__ = logtoprocessui
    89     ui.__class__ = logtoprocessui