Repository navigation
[Bug] GitCommandNotFound when executing repo.git.execute on macOS #2016
Description
Activity
The documentation does make it appear as if a string should be working even though I am pretty sure it's never used that way.
Maybe the documentation should be updated.
The way this should be used is like this:
repo.git.log(n=1).The documentation does make it appear as if a string should be working even though I am pretty sure it's never used that way.
Maybe the documentation should be updated.
The way this should be used is like this:
repo.git.log(n=1).git log is only an example, I use other commands, such as git merge-base
It works well on Windows, only get error on macOS. (I didn‘t test on Linux)
There is at least one bug shown here. The exception message GitPython produces is wrong and misleading, because at no point was an attempt made to execute a command called
g: no such command was intended, and GitPython also did not actually try to run a command called that. GitPython forms the exception message in such a way that assumes the first element of the command is the command name. That is correct when the command is a list, but incorrect when the command is a string. I think this could and should be fixed.But it looks like this issue is not only about the exception message, and that this is mainly reporting the inability to run
repo.git.execute("git log -n 1")successfully on macOS as a bug. But such a command is incorrect except on Windows, because it is only on Windows that automatic word splitting of command lines is well-defined as part of the semantics of running commands even in the absence of a shell.The
executemethod ofGitobjects uses the Python standard-librarysubprocessmodule to run commands. All the facilities insubprocessexhibit this same behavior. On Unix-like systems (including macOS), passing a single string as a command attempts to run an executable of that name. If the string has characters in it that would be treated specially in a shell (or informally), such as spaces, those characters are not treated specially. They are merely taken to be part of that name. For example, this works becauselsis the command name:ek@sup:~$ python3.12 Python 3.12.3 (main, Feb 4 2025, 14:48:35) [GCC 13.3.0] on linux Type "help", "copyright", "credits" or "license" for more information. >>> import subprocess >>> subprocess.run('ls') bin repos snap src CompletedProcess(args='ls', returncode=0)But this does not work, because it tries to run a command whose name is
ls -l, rather than a command whose name islswith an argument-las might be intended (and as it would mean on Windows):>>> subprocess.run('ls -l') Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/lib/python3.12/subprocess.py", line 548, in run with Popen(*popenargs, **kwargs) as process: ^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.12/subprocess.py", line 1026, in __init__ self._execute_child(args, executable, preexec_fn, close_fds, File "/usr/lib/python3.12/subprocess.py", line 1955, in _execute_child raise child_exception_type(errno_num, err_msg, err_filename) FileNotFoundError: [Errno 2] No such file or directory: 'ls -l'(The error message there is directly analogous to the
[Errno 2] No such file or directory: 'git log -n 1'part of the error message you encountered, which reveals that an attempt was made to find an executable whose name was the whole stringgit log -n 1.)And this works because it passes a list of the command and arguments rather than a string, so it unambiguously runs
lswith the argument-l:>>> subprocess.run(['ls', '-l']) total 16 drwxrwxr-x 2 ek ek 4096 Nov 3 07:58 bin drwxrwxr-x 9 ek ek 4096 Nov 3 08:12 repos drwx------ 4 ek ek 4096 Nov 28 13:04 snap drwxrwxr-x 5 ek ek 4096 Mar 17 10:32 src CompletedProcess(args=['ls', '-l'], returncode=0)The Python documentation on
subprocess.Popencovers this behavior and how it differs between Windows systems and Unix-like systems, with the most relevant part being:On POSIX, if args is a string, the string is interpreted as the name or path of the program to execute. However, this can only be done if not passing arguments to the program.
Both when using something like
subprocess.Popenorsubprocess.rundirectly, and when usingrepo.git.executeon aRepoobjectrepo, usually the best thing to do would be to use a list. As you found and showed, this works on all systems:# Working case (array argument) repo.git.execute(["git", "log", "-n", "1"]) # Executes successfully
However, with GitPython. when the command you are running is
git, usually you would use a dynamic methodrepo.git.log(n=1), as shown in #2016 (comment), instead ofexecute.(If for some reason you want to pass all arguments explicitly, this can still be done that way:
repo.git.log('-n', '1'). I think that is only infrequently needed, though. In this case it is equivalent to using then=1keyword argument.)Reacted by Sebastian Thiel- added a commit that references this issue
on May 7, 2026


Describe the bug
When executing repo.git.execute with string-type arguments on macOS, it throws a GitCommandNotFound exception:
I have installed git and it works well on other commands, only repo.git.execute doesn't work'
The error only occurs when passing a string-type argument to repo.git.execute
Works correctly when using string array arguments
Works correctly when using shell=True argument
for example:
Environment