Skip to content

Commit 45f8f20

Browse files
committed
Win, #519: FIX WinHangs: Popen() CREATE_NEW_PROCESS_GROUP to allow kill
+ FIXED most hangs BUT no more `git-daemon` un-killable! + Use logger for utils to replace stray print().
1 parent 783ad99 commit 45f8f20

File tree

4 files changed

+26
-17
lines changed

4 files changed

+26
-17
lines changed

Diff for: git/cmd.py

+14-7
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from git.odict import OrderedDict
1616
from contextlib import contextmanager
1717
import signal
18+
import subprocess
1819
from subprocess import (
1920
call,
2021
Popen,
@@ -229,6 +230,15 @@ def dict_to_slots_and__excluded_are_none(self, d, excluded=()):
229230

230231
## -- End Utilities -- @}
231232

233+
# value of Windows process creation flag taken from MSDN
234+
CREATE_NO_WINDOW = 0x08000000
235+
236+
## CREATE_NEW_PROCESS_GROUP is needed to allow killing it afterwards,
237+
# seehttps://docs.python.org/3/library/subprocess.html#subprocess.Popen.send_signal
238+
PROC_CREATIONFLAGS = (CREATE_NO_WINDOW | subprocess.CREATE_NEW_PROCESS_GROUP
239+
if sys.platform == 'win32'
240+
else 0)
241+
232242

233243
class Git(LazyMixin):
234244

@@ -267,9 +277,6 @@ def __setstate__(self, d):
267277
# Enables debugging of GitPython's git commands
268278
GIT_PYTHON_TRACE = os.environ.get("GIT_PYTHON_TRACE", False)
269279

270-
# value of Windows process creation flag taken from MSDN
271-
CREATE_NO_WINDOW = 0x08000000
272-
273280
# Provide the full path to the git executable. Otherwise it assumes git is in the path
274281
_git_exec_env_var = "GIT_PYTHON_GIT_EXECUTABLE"
275282
GIT_PYTHON_GIT_EXECUTABLE = os.environ.get(_git_exec_env_var, git_exec_name)
@@ -317,7 +324,7 @@ def __del__(self):
317324

318325
# try to kill it
319326
try:
320-
os.kill(proc.pid, 2) # interrupt signal
327+
proc.terminate()
321328
proc.wait() # ensure process goes away
322329
except (OSError, WindowsError):
323330
pass # ignore error when process already died
@@ -632,7 +639,6 @@ def execute(self, command,
632639
cmd_not_found_exception = OSError
633640
# end handle
634641

635-
creationflags = self.CREATE_NO_WINDOW if sys.platform == 'win32' else 0
636642
try:
637643
proc = Popen(command,
638644
env=env,
@@ -644,7 +650,7 @@ def execute(self, command,
644650
shell=self.USE_SHELL,
645651
close_fds=(os.name == 'posix'), # unsupported on windows
646652
universal_newlines=universal_newlines,
647-
creationflags=creationflags,
653+
creationflags=PROC_CREATIONFLAGS,
648654
**subprocess_kwargs
649655
)
650656
except cmd_not_found_exception as err:
@@ -655,7 +661,8 @@ def execute(self, command,
655661

656662
def _kill_process(pid):
657663
""" Callback method to kill a process. """
658-
p = Popen(['ps', '--ppid', str(pid)], stdout=PIPE, creationflags=creationflags)
664+
p = Popen(['ps', '--ppid', str(pid)], stdout=PIPE,
665+
creationflags=PROC_CREATIONFLAGS)
659666
child_pids = []
660667
for line in p.stdout:
661668
if len(line.split()) > 0:

Diff for: git/index/fun.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,10 @@
1212

1313
from io import BytesIO
1414
import os
15-
import sys
1615
import subprocess
1716

1817
from git.util import IndexFileSHA1Writer
19-
from git.cmd import Git
18+
from git.cmd import PROC_CREATIONFLAGS
2019
from git.exc import (
2120
UnmergedEntriesError,
2221
HookExecutionError
@@ -78,7 +77,7 @@ def run_commit_hook(name, index):
7877
cwd=index.repo.working_dir,
7978
close_fds=(os.name == 'posix'),
8079
universal_newlines=True,
81-
creationflags=Git.CREATE_NO_WINDOW if sys.platform == 'win32' else 0,)
80+
creationflags=PROC_CREATIONFLAGS,)
8281
stdout, stderr = cmd.communicate()
8382
cmd.stdout.close()
8483
cmd.stderr.close()

Diff for: git/test/lib/helper.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
# the BSD License: http://www.opensource.org/licenses/bsd-license.php
66
from __future__ import print_function
77
import os
8-
import sys
98
from unittest import TestCase
109
import time
1110
import tempfile
1211
import shutil
1312
import io
13+
import logging
1414

1515
from git import Repo, Remote, GitCommandError, Git
1616
from git.compat import string_types
@@ -25,6 +25,8 @@
2525
'with_rw_repo', 'with_rw_and_rw_remote_repo', 'TestBase', 'TestCase', 'GIT_REPO', 'GIT_DAEMON_PORT'
2626
)
2727

28+
log = logging.getLogger('git.util')
29+
2830
#{ Routines
2931

3032

@@ -120,7 +122,7 @@ def repo_creator(self):
120122
try:
121123
return func(self, rw_repo)
122124
except:
123-
print("Keeping repo after failure: %s" % repo_dir, file=sys.stderr)
125+
log.info("Keeping repo after failure: %s", repo_dir)
124126
repo_dir = None
125127
raise
126128
finally:
@@ -218,7 +220,7 @@ def remote_repo_creator(self):
218220
# on some platforms ?
219221
if gd is not None:
220222
os.kill(gd.proc.pid, 15)
221-
print(str(e))
223+
log.warning('git-ls-remote failed due to: %s(%s)', type(e), e)
222224
if os.name == 'nt':
223225
msg = "git-daemon needs to run this test, but windows does not have one. "
224226
msg += 'Otherwise, run: git-daemon "%s"' % temp_dir
@@ -239,8 +241,8 @@ def remote_repo_creator(self):
239241
try:
240242
return func(self, rw_repo, rw_remote_repo)
241243
except:
242-
print("Keeping repos after failure: repo_dir = %s, remote_repo_dir = %s"
243-
% (repo_dir, remote_repo_dir), file=sys.stderr)
244+
log.info("Keeping repos after failure: repo_dir = %s, remote_repo_dir = %s",
245+
repo_dir, remote_repo_dir)
244246
repo_dir = remote_repo_dir = None
245247
raise
246248
finally:

Diff for: git/test/test_git.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
Git,
2222
GitCommandError,
2323
GitCommandNotFound,
24-
Repo
24+
Repo,
25+
cmd
2526
)
2627
from gitdb.test.lib import with_rw_directory
2728

@@ -240,7 +241,7 @@ def counter_stderr(line):
240241
stderr=subprocess.PIPE,
241242
shell=False,
242243
universal_newlines=True,
243-
creationflags=Git.CREATE_NO_WINDOW if sys.platform == 'win32' else 0,
244+
creationflags=cmd.PROC_CREATIONFLAGS,
244245
)
245246

246247
handle_process_output(proc, counter_stdout, counter_stderr, lambda proc: proc.wait())

0 commit comments

Comments
 (0)