diff --git a/.authors.yml b/.authors.yml index 3dc34faabff..524302962fb 100644 --- a/.authors.yml +++ b/.authors.yml @@ -1957,7 +1957,7 @@ first_commit: 2016-12-11 16:14:03 - name: Ken Odegard email: kodegard@anaconda.com - num_commits: 558 + num_commits: 566 first_commit: 2016-09-27 18:04:21 github: kenodegard aliases: @@ -2003,7 +2003,7 @@ - name: Daniel Holth email: dholth@anaconda.com github: dholth - num_commits: 68 + num_commits: 70 first_commit: 2021-11-18 08:57:14 - name: John Flavin email: flavinj@gmail.com @@ -2083,7 +2083,7 @@ github: beeankha alternate_emails: - beeankha@gmail.com - num_commits: 25 + num_commits: 29 first_commit: 2022-05-12 13:39:02 - name: Kian-Meng Ang email: kianmeng.ang@gmail.com @@ -2364,3 +2364,10 @@ github: marcoesters num_commits: 1 first_commit: 2023-07-11 05:47:23 +- name: Peter Talley + email: peterctalley@gmail.com + github: otaithleigh + aliases: + - P. Talley + num_commits: 1 + first_commit: 2023-08-25 20:43:39 diff --git a/.mailmap b/.mailmap index cd9d244d07e..6c2a1e9ce9e 100644 --- a/.mailmap +++ b/.mailmap @@ -392,6 +392,7 @@ Paul Yim Pavel Zwerschke Pete Bachant Peter Cable +Peter Talley P. Talley Peter Williams Phil Elson Philip Thomas psthomas diff --git a/AUTHORS.md b/AUTHORS.md index 82d180d931d..ecda4329f3d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -312,6 +312,7 @@ Authors are sorted alphabetically. * Pavel Zwerschke * Pete Bachant * Peter Cable +* Peter Talley * Peter Williams * Phil Elson * Philip Thomas diff --git a/CHANGELOG.md b/CHANGELOG.md index a8b4a8be5cc..86b8081b5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,26 @@ [//]: # (current developments) +## 23.7.4 (2023-09-12) + +### Enhancements + +* Use `os.scandir()` to find conda subcommands without `stat()` overhead. (#13033, #13067) + +### Bug fixes + +* Fix S3 bucket name. (#12989) +* Fix performance regression of basic commands (e.g., `conda info`) on WSL. (#13035) +* Catch `PermissionError` raised by `conda.cli.find_commands.find_commands` when user's `$PATH` contains restricted paths. (#13062, #13089) +* Fix sorting error for `conda config --show-sources --json`. (#13076) + +### Contributors + +* @beeankha +* @dholth +* @kenodegard +* @otaithleigh made their first contribution in https://github.com/conda/conda/pull/13035 + + ## 23.7.3 (2023-08-21) ### Bug fixes diff --git a/conda/cli/find_commands.py b/conda/cli/find_commands.py index cec9b2d28e6..af3586f22b3 100644 --- a/conda/cli/find_commands.py +++ b/conda/cli/find_commands.py @@ -6,7 +6,7 @@ import sys import sysconfig from functools import lru_cache -from os.path import basename, expanduser, isdir, isfile, join +from os.path import basename, expanduser, isfile, join from ..common.compat import on_win @@ -61,16 +61,20 @@ def find_commands(include_others=True): dir_paths.extend(os.environ.get("PATH", "").split(os.pathsep)) if on_win: - pat = re.compile(r"conda-([\w\-]+)\.(exe|bat)$") + pat = re.compile(r"conda-([\w\-]+)(\.(exe|bat))?$") else: pat = re.compile(r"conda-([\w\-]+)$") res = set() for dir_path in dir_paths: - if not isdir(dir_path): + try: + for entry in os.scandir(dir_path): + m = pat.match(entry.name) + if m and entry.is_file(): + res.add(m.group(1)) + except (FileNotFoundError, NotADirectoryError, PermissionError): + # FileNotFoundError: path doesn't exist + # NotADirectoryError: path is not a directory + # PermissionError: user doesn't have read access continue - for fn in os.listdir(dir_path): - m = pat.match(fn) - if m and isfile(join(dir_path, fn)): - res.add(m.group(1)) return tuple(sorted(res)) diff --git a/tests/cli/test_find_commands.py b/tests/cli/test_find_commands.py new file mode 100644 index 00000000000..df7029c41b4 --- /dev/null +++ b/tests/cli/test_find_commands.py @@ -0,0 +1,80 @@ +# Copyright (C) 2012 Anaconda, Inc +# SPDX-License-Identifier: BSD-3-Clause +import os +from pathlib import Path + +import pytest +from pytest import MonkeyPatch + +from conda.cli.find_commands import find_commands, find_executable +from conda.common.compat import on_win + + +@pytest.fixture +def faux_path(tmp_path: Path, monkeypatch: MonkeyPatch) -> Path: + if not on_win: + # make a read-only location, none of these should show up in the tests + permission = tmp_path / "permission" + permission.mkdir(mode=0o333, exist_ok=True) + (permission / "conda-permission").touch() + (permission / "conda-permission.bat").touch() + (permission / "conda-permission.exe").touch() + monkeypatch.setenv("PATH", str(permission), prepend=os.pathsep) + + # missing directory + missing_dir = tmp_path / "missing-directory" + monkeypatch.setenv("PATH", str(missing_dir), prepend=os.pathsep) + + # not directory + not_dir = tmp_path / "not-directory" + not_dir.touch() + monkeypatch.setenv("PATH", str(not_dir), prepend=os.pathsep) + + # bad executables + bad = tmp_path / "bad" + bad.mkdir(exist_ok=True) + (bad / "non-conda-bad").touch() + (bad / "non-conda-bad.bat").touch() + (bad / "non-conda-bad.exe").touch() + monkeypatch.setenv("PATH", str(bad), prepend=os.pathsep) + + # good executables + bin_ = tmp_path / "bin" + bin_.mkdir(exist_ok=True) + (bin_ / "conda-bin").touch() + monkeypatch.setenv("PATH", str(bin_), prepend=os.pathsep) + + bat = tmp_path / "bat" + bat.mkdir(exist_ok=True) + (bat / "conda-bat.bat").touch() + monkeypatch.setenv("PATH", str(bat), prepend=os.pathsep) + + exe = tmp_path / "exe" + exe.mkdir(exist_ok=True) + (exe / "conda-exe.exe").touch() + monkeypatch.setenv("PATH", str(exe), prepend=os.pathsep) + + yield tmp_path + + if not on_win: + # undo read-only for clean removal + permission.chmod(permission.stat().st_mode | 0o444) + + +def test_find_executable(faux_path: Path): + assert (faux_path / "bin" / "conda-bin").samefile(find_executable("conda-bin")) + if on_win: + assert (faux_path / "bat" / "conda-bat.bat").samefile( + find_executable("conda-bat") + ) + assert (faux_path / "exe" / "conda-exe.exe").samefile( + find_executable("conda-exe") + ) + + +def test_find_commands(faux_path: Path): + find_commands.cache_clear() + if on_win: + assert {"bin", "bat", "exe"}.issubset(find_commands()) + else: + assert {"bin"}.issubset(find_commands())