Skip to content

Instantly share code, notes, and snippets.

@EliahKagan
Created October 7, 2023 15:38
Show Gist options
  • Save EliahKagan/53d00e28515cf2374c5501b03f7eb266 to your computer and use it in GitHub Desktop.
Save EliahKagan/53d00e28515cf2374c5501b03f7eb266 to your computer and use it in GitHub Desktop.
Finding git-daemon
(.venv) C:\Users\ek\source\repos\GitPython [find-daemon +0 ~2 -0 !]> pytest --no-cov -vv test/test_base.py::TestBase::test_with_rw_remote_and_rw_repo test/test_remote.py::TestRemote::test_base
Test session starts (platform: win32, Python 3.12.0, pytest 7.4.2, pytest-sugar 0.9.7)
cachedir: .pytest_cache
rootdir: C:\Users\ek\source\repos\GitPython
configfile: pyproject.toml
plugins: cov-4.1.0, instafail-0.5.0, sugar-0.9.7
collected 2 items
―――――――――――――――――――――――――――――――――――――― TestBase.test_with_rw_remote_and_rw_repo ―――――――――――――――――――――――――――――――――――――――
test\lib\helper.py:310: in remote_repo_creator
rw_repo.git.ls_remote(d_remote)
git\cmd.py:736: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git\cmd.py:1316: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x0000026DA7AEC6A0>, command = ['git', 'ls-remote', 'daemon_origin'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git', 'ls-remote', 'daemon_origin']
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\daemon_cloned_repo-test_with_rw_remote_and_rw_repo-37bs783t'
inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x0000026DA7AC5BB0>, stdout_sink = -1
proc = <Popen: returncode: 128 args: ['git', 'ls-remote', 'daemon_origin']>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
def kill_process(pid: int) -> None:
"""Callback to kill a process."""
p = Popen(
["ps", "--ppid", str(pid)],
stdout=PIPE,
creationflags=PROC_CREATIONFLAGS,
)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
# Wait for the process to return
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
if kill_after_timeout is not None:
watchdog.start()
stdout_value, stderr_value = proc.communicate()
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
kill_after_timeout,
)
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# end
if stderr_value:
log.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git ls-remote daemon_origin
E stderr: 'fatal: Could not read from remote repository.
E
E Please make sure you have the correct access rights
E and the repository exists.'
git\cmd.py:1111: GitCommandError
test\test_base.py::TestBase.test_with_rw_remote_and_rw_repo ⨯ 50% █████
―――――――――――――――――――――――――――――――――――――――――――――――― TestRemote.test_base ―――――――――――――――――――――――――――――――――――――――――――――――――
test\lib\helper.py:310: in remote_repo_creator
rw_repo.git.ls_remote(d_remote)
git\cmd.py:736: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git\cmd.py:1316: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x0000026DA7B761A0>, command = ['git', 'ls-remote', 'daemon_origin'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git', 'ls-remote', 'daemon_origin']
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\daemon_cloned_repo-test_base-472frov7', inline_env = None
cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x0000026DA7AC5430>, stdout_sink = -1
proc = <Popen: returncode: 128 args: ['git', 'ls-remote', 'daemon_origin']>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
def kill_process(pid: int) -> None:
"""Callback to kill a process."""
p = Popen(
["ps", "--ppid", str(pid)],
stdout=PIPE,
creationflags=PROC_CREATIONFLAGS,
)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
# Wait for the process to return
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
if kill_after_timeout is not None:
watchdog.start()
stdout_value, stderr_value = proc.communicate()
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
kill_after_timeout,
)
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# end
if stderr_value:
log.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git ls-remote daemon_origin
E stderr: 'fatal: Could not read from remote repository.
E
E Please make sure you have the correct access rights
E and the repository exists.'
git\cmd.py:1111: GitCommandError
test\test_remote.py::TestRemote.test_base ⨯ 100% ██████████
=============================================== short test summary info ===============================================
FAILED test/test_base.py::TestBase::test_with_rw_remote_and_rw_repo - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_remote.py::TestRemote::test_base - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
Results (8.99s):
2 failed
- test/test_base.py:112 TestBase.test_with_rw_remote_and_rw_repo
- test/test_remote.py:433 TestRemote.test_base
(.venv) C:\Users\ek\source\repos\GitPython [find-daemon +0 ~1 -0 !]> pytest --no-cov -vv test/test_base.py::TestBase::test_with_rw_remote_and_rw_repo test/test_remote.py::TestRemote::test_base
Test session starts (platform: win32, Python 3.12.0, pytest 7.4.2, pytest-sugar 0.9.7)
cachedir: .pytest_cache
rootdir: C:\Users\ek\source\repos\GitPython
configfile: pyproject.toml
plugins: cov-4.1.0, instafail-0.5.0, sugar-0.9.7
collected 2 items
―――――――――――――――――――――――――――――――――――――― TestBase.test_with_rw_remote_and_rw_repo ―――――――――――――――――――――――――――――――――――――――
self = <git.cmd.Git object at 0x000001BAEACD8B80>
command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
istream = None, with_extended_output = False, with_exceptions = True, as_process = True, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
cwd = 'C:\\Users\\ek\\source\\repos\\GitPython', inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000001BAEACCEF30>, stdout_sink = -1
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
> proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
git\cmd.py:988:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1026: in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Popen: returncode: None args: ['git-daemon', '--enable=receive-pack', '--li...>
args = 'git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp'
executable = None, preexec_fn = None, close_fds = False, pass_fds = (), cwd = 'C:\\Users\\ek\\source\\repos\\GitPython'
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
startupinfo = <subprocess.STARTUPINFO object at 0x000001BAEACCEC30>, creationflags = 134218240, shell = False
p2cread = Handle(1320), p2cwrite = -1, c2pread = 19, c2pwrite = Handle(1420), errread = 20, errwrite = Handle(1344)
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.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1538: FileNotFoundError
The above exception was the direct cause of the following exception:
base_path = 'C:/Users/ek/AppData/Local/Temp', ip = '127.0.0.1', port = '19418'
@contextlib.contextmanager
def git_daemon_launched(base_path, ip, port):
from git import Git # Avoid circular deps.
gd = None
try:
if is_win:
# On MINGW-git, daemon exists in Git\mingw64\libexec\git-core\,
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
# and then CANNOT DIE!
# So, invoke it as a single command.
daemon_cmd = [
"git-daemon",
"--enable=receive-pack",
"--listen=%s" % ip,
"--port=%s" % port,
"--base-path=%s" % base_path,
base_path,
]
> gd = Git().execute(daemon_cmd, as_process=True)
test\lib\helper.py:192:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x000001BAEACD8B80>
command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
istream = None, with_extended_output = False, with_exceptions = True, as_process = True, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
cwd = 'C:\\Users\\ek\\source\\repos\\GitPython', inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000001BAEACCEF30>, stdout_sink = -1
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
> raise GitCommandNotFound(redacted_command, err) from err
E git.exc.GitCommandNotFound: Cmd('git-daemon') not found due to: FileNotFoundError('[WinError 2] The system cannot find the file specified')
E cmdline: git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
git\cmd.py:1003: GitCommandNotFound
During handling of the above exception, another exception occurred:
test\lib\helper.py:310: in remote_repo_creator
rw_repo.git.ls_remote(d_remote)
git\cmd.py:736: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git\cmd.py:1316: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x000001BAEACD86A0>, command = ['git', 'ls-remote', 'daemon_origin'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git', 'ls-remote', 'daemon_origin']
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\daemon_cloned_repo-test_with_rw_remote_and_rw_repo-2ht1j2fy'
inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000001BAEA7E4CB0>, stdout_sink = -1
proc = <Popen: returncode: 128 args: ['git', 'ls-remote', 'daemon_origin']>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
def kill_process(pid: int) -> None:
"""Callback to kill a process."""
p = Popen(
["ps", "--ppid", str(pid)],
stdout=PIPE,
creationflags=PROC_CREATIONFLAGS,
)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
# Wait for the process to return
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
if kill_after_timeout is not None:
watchdog.start()
stdout_value, stderr_value = proc.communicate()
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
kill_after_timeout,
)
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# end
if stderr_value:
log.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git ls-remote daemon_origin
E stderr: 'fatal: Could not read from remote repository.
E
E Please make sure you have the correct access rights
E and the repository exists.'
git\cmd.py:1111: GitCommandError
-------------------------------------------------- Captured log call --------------------------------------------------
WARNING test.lib.helper:helper.py:224
Launching git-daemon failed due to: Cmd('git-daemon') not found due to: FileNotFoundError('[WinError 2] The system cannot find the file specified')
cmdline: git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
Probably test will fail subsequently.
BUT you may start *git-daemon* manually with this command:"
git daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
You may also run the daemon on a different port by passing --port=<port>"
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
On Windows,
the `git-daemon.exe` must be in PATH.
For MINGW, look into \Git\mingw64\libexec\git-core\, but problems with paths might appear.
Traceback (most recent call last):
File "C:\Users\ek\source\repos\GitPython\git\cmd.py", line 988, in execute
proc = Popen(
^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py", line 1026, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py", line 1538, in _execute_child
hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [WinError 2] The system cannot find the file specified
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\ek\source\repos\GitPython\test\lib\helper.py", line 192, in git_daemon_launched
gd = Git().execute(daemon_cmd, as_process=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ek\source\repos\GitPython\git\cmd.py", line 1003, in execute
raise GitCommandNotFound(redacted_command, err) from err
git.exc.GitCommandNotFound: Cmd('git-daemon') not found due to: FileNotFoundError('[WinError 2] The system cannot find the file specified')
cmdline: git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
test\test_base.py::TestBase.test_with_rw_remote_and_rw_repo ⨯ 50% █████
―――――――――――――――――――――――――――――――――――――――――――――――― TestRemote.test_base ―――――――――――――――――――――――――――――――――――――――――――――――――
self = <git.cmd.Git object at 0x000001BAEACD89A0>
command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
istream = None, with_extended_output = False, with_exceptions = True, as_process = True, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
cwd = 'C:\\Users\\ek\\source\\repos\\GitPython', inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000001BAEACCE690>, stdout_sink = -1
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
> proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
git\cmd.py:988:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1026: in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Popen: returncode: None args: ['git-daemon', '--enable=receive-pack', '--li...>
args = 'git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp'
executable = None, preexec_fn = None, close_fds = False, pass_fds = (), cwd = 'C:\\Users\\ek\\source\\repos\\GitPython'
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
startupinfo = <subprocess.STARTUPINFO object at 0x000001BAEACCF920>, creationflags = 134218240, shell = False
p2cread = Handle(1396), p2cwrite = -1, c2pread = 19, c2pwrite = Handle(1364), errread = 20, errwrite = Handle(1228)
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.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py:1538: FileNotFoundError
The above exception was the direct cause of the following exception:
base_path = 'C:/Users/ek/AppData/Local/Temp', ip = '127.0.0.1', port = '19418'
@contextlib.contextmanager
def git_daemon_launched(base_path, ip, port):
from git import Git # Avoid circular deps.
gd = None
try:
if is_win:
# On MINGW-git, daemon exists in Git\mingw64\libexec\git-core\,
# but if invoked as 'git daemon', it detaches from parent `git` cmd,
# and then CANNOT DIE!
# So, invoke it as a single command.
daemon_cmd = [
"git-daemon",
"--enable=receive-pack",
"--listen=%s" % ip,
"--port=%s" % port,
"--base-path=%s" % base_path,
base_path,
]
> gd = Git().execute(daemon_cmd, as_process=True)
test\lib\helper.py:192:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x000001BAEACD89A0>
command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
istream = None, with_extended_output = False, with_exceptions = True, as_process = True, output_stream = None
stdout_as_string = True, kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git-daemon', '--enable=receive-pack', '--listen=127.0.0.1', '--port=19418', '--base-path=C:/Users/ek/AppData/Local/Temp', 'C:/Users/ek/AppData/Local/Temp']
cwd = 'C:\\Users\\ek\\source\\repos\\GitPython', inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000001BAEACCE690>, stdout_sink = -1
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
> raise GitCommandNotFound(redacted_command, err) from err
E git.exc.GitCommandNotFound: Cmd('git-daemon') not found due to: FileNotFoundError('[WinError 2] The system cannot find the file specified')
E cmdline: git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
git\cmd.py:1003: GitCommandNotFound
During handling of the above exception, another exception occurred:
test\lib\helper.py:310: in remote_repo_creator
rw_repo.git.ls_remote(d_remote)
git\cmd.py:736: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git\cmd.py:1316: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x000001BAEAD5E740>, command = ['git', 'ls-remote', 'daemon_origin'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git', 'ls-remote', 'daemon_origin']
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\daemon_cloned_repo-test_base-ju4zfpwo', inline_env = None
cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000001BAEACCE270>, stdout_sink = -1
proc = <Popen: returncode: 128 args: ['git', 'ls-remote', 'daemon_origin']>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
def kill_process(pid: int) -> None:
"""Callback to kill a process."""
p = Popen(
["ps", "--ppid", str(pid)],
stdout=PIPE,
creationflags=PROC_CREATIONFLAGS,
)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
# Wait for the process to return
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
if kill_after_timeout is not None:
watchdog.start()
stdout_value, stderr_value = proc.communicate()
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
kill_after_timeout,
)
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# end
if stderr_value:
log.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git ls-remote daemon_origin
E stderr: 'fatal: Could not read from remote repository.
E
E Please make sure you have the correct access rights
E and the repository exists.'
git\cmd.py:1111: GitCommandError
-------------------------------------------------- Captured log call --------------------------------------------------
WARNING test.lib.helper:helper.py:224
Launching git-daemon failed due to: Cmd('git-daemon') not found due to: FileNotFoundError('[WinError 2] The system cannot find the file specified')
cmdline: git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
Probably test will fail subsequently.
BUT you may start *git-daemon* manually with this command:"
git daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
You may also run the daemon on a different port by passing --port=<port>"
and setting the environment variable GIT_PYTHON_TEST_GIT_DAEMON_PORT to <port>
On Windows,
the `git-daemon.exe` must be in PATH.
For MINGW, look into \Git\mingw64\libexec\git-core\, but problems with paths might appear.
Traceback (most recent call last):
File "C:\Users\ek\source\repos\GitPython\git\cmd.py", line 988, in execute
proc = Popen(
^^^^^^
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py", line 1026, in __init__
self._execute_child(args, executable, preexec_fn, close_fds,
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.12_3.12.240.0_x64__qbz5n2kfra8p0\Lib\subprocess.py", line 1538, in _execute_child
hp, ht, pid, tid = _winapi.CreateProcess(executable, args,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [WinError 2] The system cannot find the file specified
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "C:\Users\ek\source\repos\GitPython\test\lib\helper.py", line 192, in git_daemon_launched
gd = Git().execute(daemon_cmd, as_process=True)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\Users\ek\source\repos\GitPython\git\cmd.py", line 1003, in execute
raise GitCommandNotFound(redacted_command, err) from err
git.exc.GitCommandNotFound: Cmd('git-daemon') not found due to: FileNotFoundError('[WinError 2] The system cannot find the file specified')
cmdline: git-daemon --enable=receive-pack --listen=127.0.0.1 --port=19418 --base-path=C:/Users/ek/AppData/Local/Temp C:/Users/ek/AppData/Local/Temp
test\test_remote.py::TestRemote.test_base ⨯ 100% ██████████
=============================================== short test summary info ===============================================
FAILED test/test_base.py::TestBase::test_with_rw_remote_and_rw_repo - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_remote.py::TestRemote::test_base - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
Results (6.93s):
2 failed
- test/test_base.py:112 TestBase.test_with_rw_remote_and_rw_repo
- test/test_remote.py:433 TestRemote.test_base
(.venv) C:\Users\ek\source\repos\GitPython [find-daemon +0 ~1 -0 !]> pytest --no-cov -vv test/test_base.py::TestBase::test_with_rw_remote_and_rw_repo test/test_remote.py::TestRemote::test_base
Test session starts (platform: win32, Python 3.12.0, pytest 7.4.2, pytest-sugar 0.9.7)
cachedir: .pytest_cache
rootdir: C:\Users\ek\source\repos\GitPython
configfile: pyproject.toml
plugins: cov-4.1.0, instafail-0.5.0, sugar-0.9.7
collected 2 items
―――――――――――――――――――――――――――――――――――――― TestBase.test_with_rw_remote_and_rw_repo ―――――――――――――――――――――――――――――――――――――――
test\lib\helper.py:310: in remote_repo_creator
rw_repo.git.ls_remote(d_remote)
git\cmd.py:736: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git\cmd.py:1316: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x000002E24A9D86A0>, command = ['git', 'ls-remote', 'daemon_origin'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git', 'ls-remote', 'daemon_origin']
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\daemon_cloned_repo-test_with_rw_remote_and_rw_repo-fza_6rur'
inline_env = None, cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000002E24A9D2690>, stdout_sink = -1
proc = <Popen: returncode: 128 args: ['git', 'ls-remote', 'daemon_origin']>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
def kill_process(pid: int) -> None:
"""Callback to kill a process."""
p = Popen(
["ps", "--ppid", str(pid)],
stdout=PIPE,
creationflags=PROC_CREATIONFLAGS,
)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
# Wait for the process to return
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
if kill_after_timeout is not None:
watchdog.start()
stdout_value, stderr_value = proc.communicate()
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
kill_after_timeout,
)
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# end
if stderr_value:
log.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git ls-remote daemon_origin
E stderr: 'fatal: Could not read from remote repository.
E
E Please make sure you have the correct access rights
E and the repository exists.'
git\cmd.py:1111: GitCommandError
test\test_base.py::TestBase.test_with_rw_remote_and_rw_repo ⨯ 50% █████
―――――――――――――――――――――――――――――――――――――――――――――――― TestRemote.test_base ―――――――――――――――――――――――――――――――――――――――――――――――――
test\lib\helper.py:310: in remote_repo_creator
rw_repo.git.ls_remote(d_remote)
git\cmd.py:736: in <lambda>
return lambda *args, **kwargs: self._call_process(name, *args, **kwargs)
git\cmd.py:1316: in _call_process
return self.execute(call, **exec_kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <git.cmd.Git object at 0x000002E24AC720E0>, command = ['git', 'ls-remote', 'daemon_origin'], istream = None
with_extended_output = False, with_exceptions = True, as_process = False, output_stream = None, stdout_as_string = True
kill_after_timeout = None, with_stdout = True, universal_newlines = False, shell = False
env = {'ALLUSERSPROFILE': 'C:\\ProgramData', 'APPDATA': 'C:\\Users\\ek\\AppData\\Roaming', 'COLUMNS': '120', 'COMMONPROGRAMFILES': 'C:\\Program Files\\Common Files', ...}
max_chunk_size = 8192, strip_newline_in_stdout = True, subprocess_kwargs = {}
redacted_command = ['git', 'ls-remote', 'daemon_origin']
cwd = 'C:\\Users\\ek\\AppData\\Local\\Temp\\daemon_cloned_repo-test_base-1j1j812g', inline_env = None
cmd_not_found_exception = <class 'OSError'>
maybe_patch_caller_env = <contextlib._GeneratorContextManager object at 0x000002E24A9D3620>, stdout_sink = -1
proc = <Popen: returncode: 128 args: ['git', 'ls-remote', 'daemon_origin']>
def execute(
self,
command: Union[str, Sequence[Any]],
istream: Union[None, BinaryIO] = None,
with_extended_output: bool = False,
with_exceptions: bool = True,
as_process: bool = False,
output_stream: Union[None, BinaryIO] = None,
stdout_as_string: bool = True,
kill_after_timeout: Union[None, float] = None,
with_stdout: bool = True,
universal_newlines: bool = False,
shell: Union[None, bool] = None,
env: Union[None, Mapping[str, str]] = None,
max_chunk_size: int = io.DEFAULT_BUFFER_SIZE,
strip_newline_in_stdout: bool = True,
**subprocess_kwargs: Any,
) -> Union[str, bytes, Tuple[int, Union[str, bytes], str], AutoInterrupt]:
"""Handles executing the command and consumes and returns the returned
information (stdout).
:param command:
The command argument list to execute.
It should be a sequence of program arguments, or a string. The
program to execute is the first item in the args sequence or string.
:param istream:
Standard input filehandle passed to `subprocess.Popen`.
:param with_extended_output:
Whether to return a (status, stdout, stderr) tuple.
:param with_exceptions:
Whether to raise an exception when git returns a non-zero status.
:param as_process:
Whether to return the created process instance directly from which
streams can be read on demand. This will render with_extended_output and
with_exceptions ineffective - the caller will have to deal with the details.
It is important to note that the process will be placed into an AutoInterrupt
wrapper that will interrupt the process once it goes out of scope. If you
use the command in iterators, you should pass the whole process instance
instead of a single stream.
:param output_stream:
If set to a file-like object, data produced by the git command will be
output to the given stream directly.
This feature only has any effect if as_process is False. Processes will
always be created with a pipe due to issues with subprocess.
This merely is a workaround as data will be copied from the
output pipe to the given output stream directly.
Judging from the implementation, you shouldn't use this parameter!
:param stdout_as_string:
If False, the command's standard output will be bytes. Otherwise, it will be
decoded into a string using the default encoding (usually UTF-8).
The latter can fail, if the output contains binary data.
:param kill_after_timeout:
Specifies a timeout in seconds for the git command, after which the process
should be killed. This will have no effect if as_process is set to True. It is
set to None by default and will let the process run until the timeout is
explicitly specified. This feature is not supported on Windows. It's also worth
noting that kill_after_timeout uses SIGKILL, which can have negative side
effects on a repository. For example, stale locks in case of ``git gc`` could
render the repository incapable of accepting changes until the lock is manually
removed.
:param with_stdout:
If True, default True, we open stdout on the created process.
:param universal_newlines:
if True, pipes will be opened as text, and lines are split at
all known line endings.
:param shell:
Whether to invoke commands through a shell (see `Popen(..., shell=True)`).
It overrides :attr:`USE_SHELL` if it is not `None`.
:param env:
A dictionary of environment variables to be passed to `subprocess.Popen`.
:param max_chunk_size:
Maximum number of bytes in one chunk of data passed to the output_stream in
one invocation of write() method. If the given number is not positive then
the default value is used.
:param strip_newline_in_stdout:
Whether to strip the trailing ``\\n`` of the command stdout.
:param subprocess_kwargs:
Keyword arguments to be passed to `subprocess.Popen`. Please note that
some of the valid kwargs are already set by this method; the ones you
specify may not be the same ones.
:return:
* str(output) if extended_output = False (Default)
* tuple(int(status), str(stdout), str(stderr)) if extended_output = True
If output_stream is True, the stdout value will be your output stream:
* output_stream if extended_output = False
* tuple(int(status), output_stream, str(stderr)) if extended_output = True
Note that git is executed with ``LC_MESSAGES="C"`` to ensure consistent
output regardless of system language.
:raise GitCommandError:
:note:
If you add additional keyword arguments to the signature of this method,
you must update the execute_kwargs tuple housed in this module."""
# Remove password for the command if present
redacted_command = remove_password_if_present(command)
if self.GIT_PYTHON_TRACE and (self.GIT_PYTHON_TRACE != "full" or as_process):
log.info(" ".join(redacted_command))
# Allow the user to have the command executed in their working dir.
try:
cwd = self._working_dir or os.getcwd() # type: Union[None, str]
if not os.access(str(cwd), os.X_OK):
cwd = None
except FileNotFoundError:
cwd = None
# Start the process
inline_env = env
env = os.environ.copy()
# Attempt to force all output to plain ascii english, which is what some parsing code
# may expect.
# According to stackoverflow (http://goo.gl/l74GC8), we are setting LANGUAGE as well
# just to be sure.
env["LANGUAGE"] = "C"
env["LC_ALL"] = "C"
env.update(self._environment)
if inline_env is not None:
env.update(inline_env)
if is_win:
cmd_not_found_exception = OSError
if kill_after_timeout is not None:
raise GitCommandError(
redacted_command,
'"kill_after_timeout" feature is not supported on Windows.',
)
# Only search PATH, not CWD. This must be in the *caller* environment. The "1" can be any value.
maybe_patch_caller_env = patch_env("NoDefaultCurrentDirectoryInExePath", "1")
else:
cmd_not_found_exception = FileNotFoundError # NOQA # exists, flake8 unknown @UndefinedVariable
maybe_patch_caller_env = contextlib.nullcontext()
# end handle
stdout_sink = PIPE if with_stdout else getattr(subprocess, "DEVNULL", None) or open(os.devnull, "wb")
if shell is None:
shell = self.USE_SHELL
log.debug(
"Popen(%s, cwd=%s, stdin=%s, shell=%s, universal_newlines=%s)",
redacted_command,
cwd,
"<valid stream>" if istream else "None",
shell,
universal_newlines,
)
try:
with maybe_patch_caller_env:
proc = Popen(
command,
env=env,
cwd=cwd,
bufsize=-1,
stdin=istream or DEVNULL,
stderr=PIPE,
stdout=stdout_sink,
shell=shell,
close_fds=is_posix, # unsupported on windows
universal_newlines=universal_newlines,
creationflags=PROC_CREATIONFLAGS,
**subprocess_kwargs,
)
except cmd_not_found_exception as err:
raise GitCommandNotFound(redacted_command, err) from err
else:
# replace with a typeguard for Popen[bytes]?
proc.stdout = cast(BinaryIO, proc.stdout)
proc.stderr = cast(BinaryIO, proc.stderr)
if as_process:
return self.AutoInterrupt(proc, command)
def kill_process(pid: int) -> None:
"""Callback to kill a process."""
p = Popen(
["ps", "--ppid", str(pid)],
stdout=PIPE,
creationflags=PROC_CREATIONFLAGS,
)
child_pids = []
if p.stdout is not None:
for line in p.stdout:
if len(line.split()) > 0:
local_pid = (line.split())[0]
if local_pid.isdigit():
child_pids.append(int(local_pid))
try:
# Windows does not have SIGKILL, so use SIGTERM instead
sig = getattr(signal, "SIGKILL", signal.SIGTERM)
os.kill(pid, sig)
for child_pid in child_pids:
try:
os.kill(child_pid, sig)
except OSError:
pass
kill_check.set() # tell the main routine that the process was killed
except OSError:
# It is possible that the process gets completed in the duration after timeout
# happens and before we try to kill the process.
pass
return
# end
if kill_after_timeout is not None:
kill_check = threading.Event()
watchdog = threading.Timer(kill_after_timeout, kill_process, args=(proc.pid,))
# Wait for the process to return
status = 0
stdout_value: Union[str, bytes] = b""
stderr_value: Union[str, bytes] = b""
newline = "\n" if universal_newlines else b"\n"
try:
if output_stream is None:
if kill_after_timeout is not None:
watchdog.start()
stdout_value, stderr_value = proc.communicate()
if kill_after_timeout is not None:
watchdog.cancel()
if kill_check.is_set():
stderr_value = 'Timeout: the command "%s" did not complete in %d ' "secs." % (
" ".join(redacted_command),
kill_after_timeout,
)
if not universal_newlines:
stderr_value = stderr_value.encode(defenc)
# strip trailing "\n"
if stdout_value.endswith(newline) and strip_newline_in_stdout: # type: ignore
stdout_value = stdout_value[:-1]
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.returncode
else:
max_chunk_size = max_chunk_size if max_chunk_size and max_chunk_size > 0 else io.DEFAULT_BUFFER_SIZE
stream_copy(proc.stdout, output_stream, max_chunk_size)
stdout_value = proc.stdout.read()
stderr_value = proc.stderr.read()
# strip trailing "\n"
if stderr_value.endswith(newline): # type: ignore
stderr_value = stderr_value[:-1]
status = proc.wait()
# END stdout handling
finally:
proc.stdout.close()
proc.stderr.close()
if self.GIT_PYTHON_TRACE == "full":
cmdstr = " ".join(redacted_command)
def as_text(stdout_value: Union[bytes, str]) -> str:
return not output_stream and safe_decode(stdout_value) or "<OUTPUT_STREAM>"
# end
if stderr_value:
log.info(
"%s -> %d; stdout: '%s'; stderr: '%s'",
cmdstr,
status,
as_text(stdout_value),
safe_decode(stderr_value),
)
elif stdout_value:
log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
if with_exceptions and status != 0:
> raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
E git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
E cmdline: git ls-remote daemon_origin
E stderr: 'fatal: Could not read from remote repository.
E
E Please make sure you have the correct access rights
E and the repository exists.'
git\cmd.py:1111: GitCommandError
test\test_remote.py::TestRemote.test_base ⨯ 100% ██████████
=============================================== short test summary info ===============================================
FAILED test/test_base.py::TestBase::test_with_rw_remote_and_rw_repo - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
FAILED test/test_remote.py::TestRemote::test_base - git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
Results (8.70s):
2 failed
- test/test_base.py:112 TestBase.test_with_rw_remote_and_rw_repo
- test/test_remote.py:433 TestRemote.test_base
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment