Skip to content

Commit ef0ca65

Browse files
sroetByron
authored andcommitted
reuse kill_after_timeout kwarg
1 parent b7cd520 commit ef0ca65

File tree

2 files changed

+71
-31
lines changed

2 files changed

+71
-31
lines changed

git/cmd.py

+46-20
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
7979
finalizer: Union[None,
8080
Callable[[Union[subprocess.Popen, 'Git.AutoInterrupt']], None]] = None,
8181
decode_streams: bool = True,
82-
timeout: Union[None, float] = None) -> None:
82+
kill_after_timeout: Union[None, float] = None) -> None:
8383
"""Registers for notifications to learn that process output is ready to read, and dispatches lines to
8484
the respective line handlers.
8585
This function returns once the finalizer returns
@@ -94,7 +94,10 @@ def handle_process_output(process: 'Git.AutoInterrupt' | Popen,
9494
their contents to handlers.
9595
Set it to False if `universal_newline == True` (then streams are in text-mode)
9696
or if decoding must happen later (i.e. for Diffs).
97-
:param timeout: float, or None timeout to pass to t.join() in case it hangs. Default = None.
97+
:param kill_after_timeout:
98+
float or None, Default = None
99+
To specify a timeout in seconds for the git command, after which the process
100+
should be killed.
98101
"""
99102
# Use 2 "pump" threads and wait for both to finish.
100103
def pump_stream(cmdline: List[str], name: str, stream: Union[BinaryIO, TextIO], is_decode: bool,
@@ -108,9 +111,12 @@ def pump_stream(cmdline: List[str], name: str, stream: Union[BinaryIO, TextIO],
108111
handler(line_str)
109112
else:
110113
handler(line)
114+
111115
except Exception as ex:
112116
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
117+
if "I/O operation on closed file" not in str(ex):
118+
# Only reraise if the error was not due to the stream closing
119+
raise CommandError([f'<{name}-pump>'] + remove_password_if_present(cmdline), ex) from ex
114120
finally:
115121
stream.close()
116122

@@ -146,9 +152,16 @@ def pump_stream(cmdline: List[str], name: str, stream: Union[BinaryIO, TextIO],
146152
## FIXME: Why Join?? Will block if `stdin` needs feeding...
147153
#
148154
for t in threads:
149-
t.join(timeout=timeout)
155+
t.join(timeout=kill_after_timeout)
150156
if t.is_alive():
151-
raise RuntimeError(f"Thread join() timed out in cmd.handle_process_output(). Timeout={timeout} seconds")
157+
if hasattr(process, 'proc'): # Assume it is a Git.AutoInterrupt:
158+
process._terminate()
159+
else: # Don't want to deal with the other case
160+
raise RuntimeError(f"Thread join() timed out in cmd.handle_process_output()."
161+
" kill_after_timeout={kill_after_timeout} seconds")
162+
if stderr_handler:
163+
stderr_handler("error: process killed because it timed out."
164+
f" kill_after_timeout={kill_after_timeout} seconds")
152165

153166
if finalizer:
154167
return finalizer(process)
@@ -386,13 +399,15 @@ class AutoInterrupt(object):
386399
The wait method was overridden to perform automatic status code checking
387400
and possibly raise."""
388401

389-
__slots__ = ("proc", "args")
402+
__slots__ = ("proc", "args", "status")
390403

391404
def __init__(self, proc: Union[None, subprocess.Popen], args: Any) -> None:
392405
self.proc = proc
393406
self.args = args
407+
self.status = None
394408

395-
def __del__(self) -> None:
409+
def _terminate(self) -> None:
410+
"""Terminate the underlying process"""
396411
if self.proc is None:
397412
return
398413

@@ -408,6 +423,7 @@ def __del__(self) -> None:
408423
# did the process finish already so we have a return code ?
409424
try:
410425
if proc.poll() is not None:
426+
self.status = proc.poll()
411427
return None
412428
except OSError as ex:
413429
log.info("Ignored error after process had died: %r", ex)
@@ -419,7 +435,7 @@ def __del__(self) -> None:
419435
# try to kill it
420436
try:
421437
proc.terminate()
422-
proc.wait() # ensure process goes away
438+
self.status = proc.wait() # ensure process goes away
423439
except OSError as ex:
424440
log.info("Ignored error after process had died: %r", ex)
425441
except AttributeError:
@@ -431,6 +447,11 @@ def __del__(self) -> None:
431447
call(("TASKKILL /F /T /PID %s 2>nul 1>nul" % str(proc.pid)), shell=True)
432448
# END exception handling
433449

450+
451+
452+
def __del__(self) -> None:
453+
self._terminate()
454+
434455
def __getattr__(self, attr: str) -> Any:
435456
return getattr(self.proc, attr)
436457

@@ -447,21 +468,26 @@ def wait(self, stderr: Union[None, str, bytes] = b'') -> int:
447468

448469
if self.proc is not None:
449470
status = self.proc.wait()
471+
p_stderr = self.proc.stderr
472+
else: #Assume the underlying proc was killed earlier or never existed
473+
status = self.status
474+
p_stderr = None
450475

451-
def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
452-
if stream:
453-
try:
454-
return stderr_b + force_bytes(stream.read())
455-
except ValueError:
456-
return stderr_b or b''
457-
else:
476+
def read_all_from_possibly_closed_stream(stream: Union[IO[bytes], None]) -> bytes:
477+
if stream:
478+
try:
479+
return stderr_b + force_bytes(stream.read())
480+
except ValueError:
458481
return stderr_b or b''
482+
else:
483+
return stderr_b or b''
459484

460-
if status != 0:
461-
errstr = read_all_from_possibly_closed_stream(self.proc.stderr)
462-
log.debug('AutoInterrupt wait stderr: %r' % (errstr,))
463-
raise GitCommandError(remove_password_if_present(self.args), status, errstr)
464485
# END status handling
486+
487+
if status != 0:
488+
errstr = read_all_from_possibly_closed_stream(p_stderr)
489+
log.debug('AutoInterrupt wait stderr: %r' % (errstr,))
490+
raise GitCommandError(remove_password_if_present(self.args), status, errstr)
465491
return status
466492

467493
# END auto interrupt
@@ -694,7 +720,7 @@ def execute(self,
694720
as_process: bool = False,
695721
output_stream: Union[None, BinaryIO] = None,
696722
stdout_as_string: bool = True,
697-
kill_after_timeout: Union[None, int] = None,
723+
kill_after_timeout: Union[None, float] = None,
698724
with_stdout: bool = True,
699725
universal_newlines: bool = False,
700726
shell: Union[None, bool] = None,

git/remote.py

+25-11
Original file line numberDiff line numberDiff line change
@@ -708,7 +708,7 @@ def update(self, **kwargs: Any) -> 'Remote':
708708

709709
def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt',
710710
progress: Union[Callable[..., Any], RemoteProgress, None],
711-
timeout: Union[None, float] = None,
711+
kill_after_timeout: Union[None, float] = None,
712712
) -> IterableList['FetchInfo']:
713713

714714
progress = to_progress_instance(progress)
@@ -726,7 +726,7 @@ def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt',
726726

727727
progress_handler = progress.new_message_handler()
728728
handle_process_output(proc, None, progress_handler, finalizer=None, decode_streams=False,
729-
timeout=timeout)
729+
kill_after_timeout=kill_after_timeout)
730730

731731
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
732732
proc.wait(stderr=stderr_text)
@@ -772,7 +772,7 @@ def _get_fetch_info_from_stderr(self, proc: 'Git.AutoInterrupt',
772772

773773
def _get_push_info(self, proc: 'Git.AutoInterrupt',
774774
progress: Union[Callable[..., Any], RemoteProgress, None],
775-
timeout: Union[None, float] = None) -> IterableList[PushInfo]:
775+
kill_after_timeout: Union[None, float] = None) -> IterableList[PushInfo]:
776776
progress = to_progress_instance(progress)
777777

778778
# read progress information from stderr
@@ -790,7 +790,7 @@ def stdout_handler(line: str) -> None:
790790
pass
791791

792792
handle_process_output(proc, stdout_handler, progress_handler, finalizer=None, decode_streams=False,
793-
timeout=timeout)
793+
kill_after_timeout=kill_after_timeout)
794794
stderr_text = progress.error_lines and '\n'.join(progress.error_lines) or ''
795795
try:
796796
proc.wait(stderr=stderr_text)
@@ -817,7 +817,8 @@ def _assert_refspec(self) -> None:
817817

818818
def fetch(self, refspec: Union[str, List[str], None] = None,
819819
progress: Union[RemoteProgress, None, 'UpdateProgress'] = None,
820-
verbose: bool = True, timeout: Union[None, float] = None,
820+
verbose: bool = True,
821+
kill_after_timeout: Union[None, float] = None,
821822
**kwargs: Any) -> IterableList[FetchInfo]:
822823
"""Fetch the latest changes for this remote
823824
@@ -838,6 +839,9 @@ def fetch(self, refspec: Union[str, List[str], None] = None,
838839
for 'refspec' will make use of this facility.
839840
:param progress: See 'push' method
840841
:param verbose: Boolean for verbose output
842+
:param kill_after_timeout:
843+
To specify a timeout in seconds for the git command, after which the process
844+
should be killed. It is set to None by default.
841845
:param kwargs: Additional arguments to be passed to git-fetch
842846
:return:
843847
IterableList(FetchInfo, ...) list of FetchInfo instances providing detailed
@@ -858,20 +862,22 @@ def fetch(self, refspec: Union[str, List[str], None] = None,
858862

859863
proc = self.repo.git.fetch(self, *args, as_process=True, with_stdout=False,
860864
universal_newlines=True, v=verbose, **kwargs)
861-
res = self._get_fetch_info_from_stderr(proc, progress, timeout=timeout)
865+
res = self._get_fetch_info_from_stderr(proc, progress,
866+
kill_after_timeout=kill_after_timeout)
862867
if hasattr(self.repo.odb, 'update_cache'):
863868
self.repo.odb.update_cache()
864869
return res
865870

866871
def pull(self, refspec: Union[str, List[str], None] = None,
867872
progress: Union[RemoteProgress, 'UpdateProgress', None] = None,
868-
timeout: Union[None, float] = None,
873+
kill_after_timeout: Union[None, float] = None,
869874
**kwargs: Any) -> IterableList[FetchInfo]:
870875
"""Pull changes from the given branch, being the same as a fetch followed
871876
by a merge of branch with your local branch.
872877
873878
:param refspec: see 'fetch' method
874879
:param progress: see 'push' method
880+
:param kill_after_timeout: see 'fetch' method
875881
:param kwargs: Additional arguments to be passed to git-pull
876882
:return: Please see 'fetch' method """
877883
if refspec is None:
@@ -880,14 +886,16 @@ def pull(self, refspec: Union[str, List[str], None] = None,
880886
kwargs = add_progress(kwargs, self.repo.git, progress)
881887
proc = self.repo.git.pull(self, refspec, with_stdout=False, as_process=True,
882888
universal_newlines=True, v=True, **kwargs)
883-
res = self._get_fetch_info_from_stderr(proc, progress, timeout=timeout)
889+
res = self._get_fetch_info_from_stderr(proc, progress,
890+
kill_after_timeout=kill_after_timeout)
884891
if hasattr(self.repo.odb, 'update_cache'):
885892
self.repo.odb.update_cache()
886893
return res
887894

888895
def push(self, refspec: Union[str, List[str], None] = None,
889896
progress: Union[RemoteProgress, 'UpdateProgress', Callable[..., RemoteProgress], None] = None,
890-
timeout: Union[None, float] = None, **kwargs: Any) -> IterableList[PushInfo]:
897+
kill_after_timeout: Union[None, float] = None,
898+
**kwargs: Any) -> IterableList[PushInfo]:
891899
"""Push changes from source branch in refspec to target branch in refspec.
892900
893901
:param refspec: see 'fetch' method
@@ -903,6 +911,9 @@ def push(self, refspec: Union[str, List[str], None] = None,
903911
overrides the ``update()`` function.
904912
905913
:note: No further progress information is returned after push returns.
914+
:param kill_after_timeout:
915+
To specify a timeout in seconds for the git command, after which the process
916+
should be killed. It is set to None by default.
906917
:param kwargs: Additional arguments to be passed to git-push
907918
:return:
908919
list(PushInfo, ...) list of PushInfo instances, each
@@ -914,8 +925,11 @@ def push(self, refspec: Union[str, List[str], None] = None,
914925
be 0."""
915926
kwargs = add_progress(kwargs, self.repo.git, progress)
916927
proc = self.repo.git.push(self, refspec, porcelain=True, as_process=True,
917-
universal_newlines=True, **kwargs)
918-
return self._get_push_info(proc, progress, timeout=timeout)
928+
universal_newlines=True,
929+
kill_after_timeout=kill_after_timeout,
930+
**kwargs)
931+
return self._get_push_info(proc, progress,
932+
kill_after_timeout=kill_after_timeout)
919933

920934
@ property
921935
def config_reader(self) -> SectionConstraint[GitConfigParser]:

0 commit comments

Comments
 (0)