GitPython supports and can be installed on native Windows systems (not just Cygwin), but the TestInstallation.test_installation test in test/test_installation.py always fails with a FileNotFoundError. This is not caught on CI because, for Windows, only Cygwin currently has CI coverage. The test failure happens for two reasons:
The following output is produced on Windows 10 using Python 3.11.5, installing GitPython from the tip of the main branch (c8e303f), but I believe the test would fail on all Windows systems with all versions of Python in the same way:
(.venv) C:\Users\ek\source\repos\GitPython [main ≡]> pytest --no-cov -k test_installation
Test session starts (platform: win32, Python 3.11.5, pytest 7.4.2, pytest-sugar 0.9.7)
rootdir: C:\Users\ek\source\repos\GitPython
configfile: pyproject.toml
testpaths: test
plugins: cov-4.1.0, sugar-0.9.7
collected 504 items / 503 deselected / 1 selected
――――――――――――――――――――――――――――――――――――――――― TestInstallation.test_installation ――――――――――――――――――――――――――――――――――――――――――
self = <test.test_installation.TestInstallation testMethod=test_installation>
rw_dir = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma'
@with_rw_directory
def test_installation(self, rw_dir):
self.setUp_venv(rw_dir)
> result = subprocess.run(
[self.pip, "install", "-r", "requirements.txt"],
stdout=subprocess.PIPE,
cwd=self.sources,
)
test\test_installation.py:24:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:548: in run
with Popen(*popenargs, **kwargs) as process:
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1026: in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Popen: returncode: None args: ['C:\\Users\\ek\\AppData\\Local\\Temp\\test_i...>
args = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma\\bin/pip3 install -r requirements.txt'
executable = None, preexec_fn = None, close_fds = False, pass_fds = ()
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma\\src', env = None
startupinfo = <subprocess.STARTUPINFO object at 0x000001B4D625B390>, creationflags = 0, shell = False
p2cread = Handle(1292), p2cwrite = -1, c2pread = 15, c2pwrite = Handle(1396), errread = -1, errwrite = Handle(1416)
unused_restore_signals = True, unused_gid = None, unused_gids = None, unused_uid = None, unused_umask = -1
unused_start_new_session = False, unused_process_group = -1
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
startupinfo, creationflags, shell,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite,
unused_restore_signals,
unused_gid, unused_gids, unused_uid,
unused_umask,
unused_start_new_session, unused_process_group):
"""Execute program (MS Windows version)"""
assert not pass_fds, "pass_fds not supported on Windows."
if isinstance(args, str):
pass
elif isinstance(args, bytes):
if shell:
raise TypeError('bytes args is not allowed on Windows')
args = list2cmdline([args])
elif isinstance(args, os.PathLike):
if shell:
raise TypeError('path-like args is not allowed when '
'shell is true')
args = list2cmdline([args])
else:
args = list2cmdline(args)
if executable is not None:
executable = os.fsdecode(executable)
# Process startup details
if startupinfo is None:
startupinfo = STARTUPINFO()
else:
# bpo-34044: Copy STARTUPINFO since it is modified above,
# so the caller can reuse it multiple times.
startupinfo = startupinfo.copy()
use_std_handles = -1 not in (p2cread, c2pwrite, errwrite)
if use_std_handles:
startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES
startupinfo.hStdInput = p2cread
startupinfo.hStdOutput = c2pwrite
startupinfo.hStdError = errwrite
attribute_list = startupinfo.lpAttributeList
have_handle_list = bool(attribute_list and
"handle_list" in attribute_list and
attribute_list["handle_list"])
# If we were given an handle_list or need to create one
if have_handle_list or (use_std_handles and close_fds):
if attribute_list is None:
attribute_list = startupinfo.lpAttributeList = {}
handle_list = attribute_list["handle_list"] = \
list(attribute_list.get("handle_list", []))
if use_std_handles:
handle_list += [int(p2cread), int(c2pwrite), int(errwrite)]
handle_list[:] = self._filter_handle_list(handle_list)
if handle_list:
if not close_fds:
warnings.warn("startupinfo.lpAttributeList['handle_list'] "
"overriding close_fds", RuntimeWarning)
# When using the handle_list we always request to inherit
# handles but the only handles that will be inherited are
# the ones in the handle_list
close_fds = False
if shell:
startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW
startupinfo.wShowWindow = _winapi.SW_HIDE
if not executable:
# gh-101283: without a fully-qualified path, before Windows
# checks the system directories, it first looks in the
# application directory, and also the current directory if
# NeedCurrentDirectoryForExePathW(ExeName) is true, so try
# to avoid executing unqualified "cmd.exe".
comspec = os.environ.get('ComSpec')
if not comspec:
system_root = os.environ.get('SystemRoot', '')
comspec = os.path.join(system_root, 'System32', 'cmd.exe')
if not os.path.isabs(comspec):
raise FileNotFoundError('shell not found: neither %ComSpec% nor %SystemRoot% is set')
if os.path.isabs(comspec):
executable = comspec
else:
comspec = executable
args = '{} /c "{}"'.format (comspec, args)
if cwd is not None:
cwd = os.fsdecode(cwd)
sys.audit("subprocess.Popen", executable, args, cwd, env)
# Start the process
try:
> hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
# no special security
None, None,
int(not close_fds),
creationflags,
env,
cwd,
startupinfo)
E FileNotFoundError: [WinError 2] The system cannot find the file specified
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1538: FileNotFoundError
test/test_installation.py ⨯ 100% ██████████
=============================================== short test summary info ===============================================
FAILED test/test_installation.py::TestInstallation::test_installation - FileNotFoundError: [WinError 2] The system cannot find the file specified
Results (7.00s):
1 failed
- test/test_installation.py:21 TestInstallation.test_installation
503 deselected
GitPython supports and can be installed on native Windows systems (not just Cygwin), but the TestInstallation.test_installation test in test/test_installation.py always fails with a FileNotFoundError. This is not caught on CI because, for Windows, only Cygwin currently has CI coverage. The test failure happens for two reasons:
The following output is produced on Windows 10 using Python 3.11.5, installing GitPython from the tip of the main branch (c8e303f), but I believe the test would fail on all Windows systems with all versions of Python in the same way:
(.venv) C:\Users\ek\source\repos\GitPython [main ≡]> pytest --no-cov -k test_installation Test session starts (platform: win32, Python 3.11.5, pytest 7.4.2, pytest-sugar 0.9.7) rootdir: C:\Users\ek\source\repos\GitPython configfile: pyproject.toml testpaths: test plugins: cov-4.1.0, sugar-0.9.7 collected 504 items / 503 deselected / 1 selected ――――――――――――――――――――――――――――――――――――――――― TestInstallation.test_installation ―――――――――――――――――――――――――――――――――――――――――― self = <test.test_installation.TestInstallation testMethod=test_installation> rw_dir = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma' @with_rw_directory def test_installation(self, rw_dir): self.setUp_venv(rw_dir) > result = subprocess.run( [self.pip, "install", "-r", "requirements.txt"], stdout=subprocess.PIPE, cwd=self.sources, ) test\test_installation.py:24: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:548: in run with Popen(*popenargs, **kwargs) as process: C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1026: in __init__ self._execute_child(args, executable, preexec_fn, close_fds, _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ self = <Popen: returncode: None args: ['C:\\Users\\ek\\AppData\\Local\\Temp\\test_i...> args = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma\\bin/pip3 install -r requirements.txt' executable = None, preexec_fn = None, close_fds = False, pass_fds = () cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\test_installatione9rg39ma\\src', env = None startupinfo = <subprocess.STARTUPINFO object at 0x000001B4D625B390>, creationflags = 0, shell = False p2cread = Handle(1292), p2cwrite = -1, c2pread = 15, c2pwrite = Handle(1396), errread = -1, errwrite = Handle(1416) unused_restore_signals = True, unused_gid = None, unused_gids = None, unused_uid = None, unused_umask = -1 unused_start_new_session = False, unused_process_group = -1 def _execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, unused_restore_signals, unused_gid, unused_gids, unused_uid, unused_umask, unused_start_new_session, unused_process_group): """Execute program (MS Windows version)""" assert not pass_fds, "pass_fds not supported on Windows." if isinstance(args, str): pass elif isinstance(args, bytes): if shell: raise TypeError('bytes args is not allowed on Windows') args = list2cmdline([args]) elif isinstance(args, os.PathLike): if shell: raise TypeError('path-like args is not allowed when ' 'shell is true') args = list2cmdline([args]) else: args = list2cmdline(args) if executable is not None: executable = os.fsdecode(executable) # Process startup details if startupinfo is None: startupinfo = STARTUPINFO() else: # bpo-34044: Copy STARTUPINFO since it is modified above, # so the caller can reuse it multiple times. startupinfo = startupinfo.copy() use_std_handles = -1 not in (p2cread, c2pwrite, errwrite) if use_std_handles: startupinfo.dwFlags |= _winapi.STARTF_USESTDHANDLES startupinfo.hStdInput = p2cread startupinfo.hStdOutput = c2pwrite startupinfo.hStdError = errwrite attribute_list = startupinfo.lpAttributeList have_handle_list = bool(attribute_list and "handle_list" in attribute_list and attribute_list["handle_list"]) # If we were given an handle_list or need to create one if have_handle_list or (use_std_handles and close_fds): if attribute_list is None: attribute_list = startupinfo.lpAttributeList = {} handle_list = attribute_list["handle_list"] = \ list(attribute_list.get("handle_list", [])) if use_std_handles: handle_list += [int(p2cread), int(c2pwrite), int(errwrite)] handle_list[:] = self._filter_handle_list(handle_list) if handle_list: if not close_fds: warnings.warn("startupinfo.lpAttributeList['handle_list'] " "overriding close_fds", RuntimeWarning) # When using the handle_list we always request to inherit # handles but the only handles that will be inherited are # the ones in the handle_list close_fds = False if shell: startupinfo.dwFlags |= _winapi.STARTF_USESHOWWINDOW startupinfo.wShowWindow = _winapi.SW_HIDE if not executable: # gh-101283: without a fully-qualified path, before Windows # checks the system directories, it first looks in the # application directory, and also the current directory if # NeedCurrentDirectoryForExePathW(ExeName) is true, so try # to avoid executing unqualified "cmd.exe". comspec = os.environ.get('ComSpec') if not comspec: system_root = os.environ.get('SystemRoot', '') comspec = os.path.join(system_root, 'System32', 'cmd.exe') if not os.path.isabs(comspec): raise FileNotFoundError('shell not found: neither %ComSpec% nor %SystemRoot% is set') if os.path.isabs(comspec): executable = comspec else: comspec = executable args = '{} /c "{}"'.format (comspec, args) if cwd is not None: cwd = os.fsdecode(cwd) sys.audit("subprocess.Popen", executable, args, cwd, env) # Start the process try: > hp, ht, pid, tid = _winapi.CreateProcess(executable, args, # no special security None, None, int(not close_fds), creationflags, env, cwd, startupinfo) E FileNotFoundError: [WinError 2] The system cannot find the file specified C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.1520.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1538: FileNotFoundError test/test_installation.py ⨯ 100% ██████████ =============================================== short test summary info =============================================== FAILED test/test_installation.py::TestInstallation::test_installation - FileNotFoundError: [WinError 2] The system cannot find the file specified Results (7.00s): 1 failed - test/test_installation.py:21 TestInstallation.test_installation 503 deselectedI have also verified that this occurs on Python 3.12.0rc2 on Windows. #1640 affects Windows as much as other systems, but this specific issue causes the test to fail first.
I think it would make sense to fix this together with #1640, since a fix for that would include related changes to test_installation. I've opened #1654 for this (which also fixes #1652 and #1653).