From 8dacdb6b6eead67af625a553f91cab9afd7ca949 Mon Sep 17 00:00:00 2001 From: zerafachris Date: Thu, 6 Aug 2026 16:48:33 +0200 Subject: [PATCH] fix: keep absolute config path when os.path.relpath raises ValueError on Windows On Windows, os.path.relpath raises ValueError when the path and the current working directory are on different drives (e.g. config on C:\ but the git repository root is on D:\). _adjust_args_and_chdir called os.path.relpath unconditionally after chdir-ing to the git root, so running pre-commit with --config pointing to a different drive crashed with a ValueError instead of falling back gracefully. Add a _relpath() helper that catches ValueError and returns the original (absolute) path. Replace all four os.path.relpath() call-sites in _adjust_args_and_chdir with _relpath() so that cross-drive paths are kept as absolute paths rather than raising an unhandled exception. Fixes #2530 Co-Authored-By: Claude Sonnet 4.6 --- pre_commit/main.py | 21 +++++++++++++++------ tests/main_test.py | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/pre_commit/main.py b/pre_commit/main.py index d46c0d5b7..5f259dfda 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -172,6 +172,17 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None: ) +def _relpath(path: str) -> str: + # On Windows, os.path.relpath raises ValueError when `path` and the + # current directory are on different drives (e.g. config on C:\ but the + # git repo is on D:\). Fall back to the absolute path in that case so + # pre-commit still works cross-drive. + try: + return os.path.relpath(path) + except ValueError: + return path + + def _adjust_args_and_chdir(args: argparse.Namespace) -> None: # `--config` was specified relative to the non-root working directory if os.path.exists(args.config): @@ -188,15 +199,13 @@ def _adjust_args_and_chdir(args: argparse.Namespace) -> None: toplevel = git.get_root() os.chdir(toplevel) - args.config = os.path.relpath(args.config) + args.config = _relpath(args.config) if args.command in {'run', 'try-repo'}: - args.files = [os.path.relpath(filename) for filename in args.files] + args.files = [_relpath(filename) for filename in args.files] if args.commit_msg_filename is not None: - args.commit_msg_filename = os.path.relpath( - args.commit_msg_filename, - ) + args.commit_msg_filename = _relpath(args.commit_msg_filename) if args.command == 'try-repo' and os.path.exists(args.repo): - args.repo = os.path.relpath(args.repo) + args.repo = _relpath(args.repo) def main(argv: Sequence[str] | None = None) -> int: diff --git a/tests/main_test.py b/tests/main_test.py index 5194e9ea8..205de4093 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -89,6 +89,20 @@ def test_adjust_args_try_repo_repo_relative(in_git_dir): assert args.repo == 'foo' +def test_relpath_falls_back_on_cross_drive_valueerror(): + # On Windows, os.path.relpath raises ValueError when path and cwd are on + # different drives. _relpath must return the original path unchanged. + abs_path = 'C:\\configs\\pre-commit-config.yaml' + with mock.patch('os.path.relpath', side_effect=ValueError('different drives')): + assert main._relpath(abs_path) == abs_path + + +def test_relpath_returns_relative_path_on_same_drive(): + # Normal case: os.path.relpath succeeds and the result is returned. + with mock.patch('os.path.relpath', return_value=os.path.join('foo', 'bar')): + assert main._relpath('/any/path') == os.path.join('foo', 'bar') + + FNS = ( 'autoupdate', 'clean', 'gc', 'install', 'install_hooks', 'migrate_config', 'run', 'sample_config', 'uninstall',