33#
44# This module is part of GitPython and is released under
55# the BSD License: http://www.opensource.org/licenses/bsd-license.php
6-
6+ from __future__ import annotations
77from contextlib import contextmanager
88import io
99import logging
6868# Documentation
6969## @{
7070
71- def handle_process_output (process :Union [ subprocess . Popen , 'Git.AutoInterrupt' ] ,
71+ def handle_process_output (process :'Git.AutoInterrupt' | Popen ,
7272stdout_handler :Union [None ,
7373Callable [[AnyStr ],None ],
7474Callable [[List [AnyStr ]],None ],
@@ -78,7 +78,8 @@ def handle_process_output(process: Union[subprocess.Popen, 'Git.AutoInterrupt'],
7878Callable [[List [AnyStr ]],None ]],
7979finalizer :Union [None ,
8080Callable [[Union [subprocess .Popen ,'Git.AutoInterrupt' ]],None ]]= None ,
81- decode_streams :bool = True )-> None :
81+ decode_streams :bool = True ,
82+ timeout :float = 10.0 )-> None :
8283"""Registers for notifications to learn that process output is ready to read, and dispatches lines to
8384 the respective line handlers.
8485 This function returns once the finalizer returns
@@ -93,9 +94,10 @@ def handle_process_output(process: Union[subprocess.Popen, 'Git.AutoInterrupt'],
9394 their contents to handlers.
9495 Set it to False if `universal_newline == True` (then streams are in text-mode)
9596 or if decoding must happen later (i.e. for Diffs).
97+ :param timeout: float, timeout to pass to t.join() in case it hangs. Default = 10.0 seconds
9698 """
9799# Use 2 "pump" threads and wait for both to finish.
98- def pump_stream (cmdline :str ,name :str ,stream :Union [BinaryIO ,TextIO ],is_decode :bool ,
100+ def pump_stream (cmdline :List [ str ] ,name :str ,stream :Union [BinaryIO ,TextIO ],is_decode :bool ,
99101handler :Union [None ,Callable [[Union [bytes ,str ]],None ]])-> None :
100102try :
101103for line in stream :
@@ -107,22 +109,34 @@ def pump_stream(cmdline: str, name: str, stream: Union[BinaryIO, TextIO], is_dec
107109else :
108110handler (line )
109111except Exception as ex :
110- log .error ("Pumping%r of cmd(%s) failed due to:%r" , name , remove_password_if_present ( cmdline ), ex )
111- raise CommandError (['<%s -pump>'% name ]+ remove_password_if_present (cmdline ),ex )from ex
112+ log .error (f "Pumping{ name !r } of cmd({ remove_password_if_present ( cmdline ) } )} failed due to :{ ex !r } " )
113+ raise CommandError ([f'< { name } -pump>' ]+ remove_password_if_present (cmdline ),ex )from ex
112114finally :
113115stream .close ()
114116
115- cmdline = getattr (process ,'args' ,'' )# PY3+ only
117+
118+
119+ if hasattr (process ,'proc' ):
120+ process = cast ('Git.AutoInterrupt' ,process )
121+ cmdline :str | Tuple [str , ...]| List [str ]= getattr (process .proc ,'args' ,'' )
122+ p_stdout = process .proc .stdout
123+ p_stderr = process .proc .stderr
124+ else :
125+ process = cast (Popen ,process )
126+ cmdline = getattr (process ,'args' ,'' )
127+ p_stdout = process .stdout
128+ p_stderr = process .stderr
129+
116130if not isinstance (cmdline , (tuple ,list )):
117131cmdline = cmdline .split ()
118132
119- pumps = []
120- if process . stdout :
121- pumps .append (('stdout' ,process . stdout ,stdout_handler ))
122- if process . stderr :
123- pumps .append (('stderr' ,process . stderr ,stderr_handler ))
133+ pumps : List [ Tuple [ str , IO , Callable [..., None ] | None ]] = []
134+ if p_stdout :
135+ pumps .append (('stdout' ,p_stdout ,stdout_handler ))
136+ if p_stderr :
137+ pumps .append (('stderr' ,p_stderr ,stderr_handler ))
124138
125- threads = []
139+ threads : List [ threading . Thread ] = []
126140
127141for name ,stream ,handler in pumps :
128142t = threading .Thread (target = pump_stream ,
@@ -134,7 +148,7 @@ def pump_stream(cmdline: str, name: str, stream: Union[BinaryIO, TextIO], is_dec
134148## FIXME: Why Join?? Will block if `stdin` needs feeding...
135149#
136150for t in threads :
137- t .join ()
151+ t .join (timeout = timeout )
138152
139153if finalizer :
140154return finalizer (process )