Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a929ab2

Browse filesBrowse files
committed
Merge pull request #354 from dpursehouse/execute-timeout
Include 'timeout' parameter in Git execute
2 parents ed8939d + dbbcaf7 commit a929ab2
Copy full SHA for a929ab2

1 file changed

+50-1Lines changed: 50 additions & 1 deletion

File tree

Expand file treeCollapse file tree
Open diff view settings
Filter options
Expand file treeCollapse file tree
Open diff view settings
Collapse file

‎git/cmd.py‎

Copy file name to clipboardExpand all lines: git/cmd.py
+50-1Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import mmap
1515

1616
from contextlib import contextmanager
17+
from signal import SIGKILL
1718
from subprocess import (
1819
call,
1920
Popen,
@@ -41,7 +42,7 @@
4142

4243
execute_kwargs = ('istream', 'with_keep_cwd', 'with_extended_output',
4344
'with_exceptions', 'as_process', 'stdout_as_string',
44-
'output_stream', 'with_stdout')
45+
'output_stream', 'with_stdout', 'kill_after_timeout')
4546

4647
log = logging.getLogger('git.cmd')
4748
log.addHandler(logging.NullHandler())
@@ -476,6 +477,7 @@ def execute(self, command,
476477
as_process=False,
477478
output_stream=None,
478479
stdout_as_string=True,
480+
kill_after_timeout=None,
479481
with_stdout=True,
480482
**subprocess_kwargs
481483
):
@@ -532,6 +534,16 @@ def execute(self, command,
532534
533535
:param with_stdout: If True, default True, we open stdout on the created process
534536
537+
:param kill_after_timeout:
538+
To specify a timeout in seconds for the git command, after which the process
539+
should be killed. This will have no effect if as_process is set to True. It is
540+
set to None by default and will let the process run until the timeout is
541+
explicitly specified. This feature is not supported on Windows. It's also worth
542+
noting that kill_after_timeout uses SIGKILL, which can have negative side
543+
effects on a repository. For example, stale locks in case of git gc could
544+
render the repository incapable of accepting changes until the lock is manually
545+
removed.
546+
535547
:return:
536548
* str(output) if extended_output = False (Default)
537549
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
@@ -569,6 +581,8 @@ def execute(self, command,
569581

570582
if sys.platform == 'win32':
571583
cmd_not_found_exception = WindowsError
584+
if kill_after_timeout:
585+
raise GitCommandError('"kill_after_timeout" feature is not supported on Windows.')
572586
else:
573587
if sys.version_info[0] > 2:
574588
cmd_not_found_exception = FileNotFoundError # NOQA # this is defined, but flake8 doesn't know
@@ -593,13 +607,48 @@ def execute(self, command,
593607
if as_process:
594608
return self.AutoInterrupt(proc, command)
595609

610+
def _kill_process(pid):
611+
""" Callback method to kill a process. """
612+
p = Popen(['ps', '--ppid', str(pid)], stdout=PIPE)
613+
child_pids = []
614+
for line in p.stdout:
615+
if len(line.split()) > 0:
616+
local_pid = (line.split())[0]
617+
if local_pid.isdigit():
618+
child_pids.append(int(local_pid))
619+
try:
620+
os.kill(pid, SIGKILL)
621+
for child_pid in child_pids:
622+
try:
623+
os.kill(child_pid, SIGKILL)
624+
except OSError:
625+
pass
626+
kill_check.set() # tell the main routine that the process was killed
627+
except OSError:
628+
# It is possible that the process gets completed in the duration after timeout
629+
# happens and before we try to kill the process.
630+
pass
631+
return
632+
# end
633+
634+
if kill_after_timeout:
635+
kill_check = threading.Event()
636+
watchdog = threading.Timer(kill_after_timeout, _kill_process, args=(proc.pid, ))
637+
596638
# Wait for the process to return
597639
status = 0
598640
stdout_value = b''
599641
stderr_value = b''
600642
try:
601643
if output_stream is None:
644+
if kill_after_timeout:
645+
watchdog.start()
602646
stdout_value, stderr_value = proc.communicate()
647+
if kill_after_timeout:
648+
watchdog.cancel()
649+
if kill_check.isSet():
650+
stderr_value = 'Timeout: the command "%s" did not complete in %d ' \
651+
'secs.' % (" ".join(command), kill_after_timeout)
603652
# strip trailing "\n"
604653
if stdout_value.endswith(b"\n"):
605654
stdout_value = stdout_value[:-1]

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.