which() prefers the executable found in $PATH

which() kept looking after shutil.which found a name in $PATH, so a later
fallback path (e.g. '/usr/bin/git' in which('git', '/usr/bin/git'))
always replaced it.  Its docstring says $PATH comes first; stop at the
first match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Roland 2026-10-21 08:04:21 -06:00
parent 22afb81879
commit a51ac154e2
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 15 additions and 0 deletions

View file

@ -41,6 +41,7 @@ def which(*possible_paths: str, warn: bool = False) -> Optional[Path]:
if path and (path := Path(path).absolute()) and is_executable(path):
# `path` was a name or relative path.
found = path
break
if not found and warn and not PYTEST and not DOCKERIZED:
logger.warning(f'Cannot find executable {possible_paths[0]}')

View file

@ -26,6 +26,20 @@ def test_which(test_directory):
assert cmd.which('asdf', str(foo)) == foo
def test_which_prefers_path(test_directory, monkeypatch):
"""A name found in $PATH wins over a fallback path, even when the fallback exists too."""
on_path, fallback = test_directory / 'bin', test_directory / 'fallback'
for directory in (on_path, fallback):
directory.mkdir()
(directory / 'tool').touch()
os.chmod(directory / 'tool', 0o777)
monkeypatch.setenv('PATH', str(on_path))
assert cmd.which('tool', str(fallback / 'tool')) == on_path / 'tool'
# The fallback is used when the name is not in $PATH.
assert cmd.which('missing', str(fallback / 'tool')) == fallback / 'tool'
TEST_SCRIPT = '''#!/bin/sh
echo stdout
echo stderr >&2