Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions pre_commit/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ def _add_run_options(parser: argparse.ArgumentParser) -> None:
)


def _try_relpath(path: str) -> str:
"""Return os.path.relpath(path), or path unchanged on Windows when path
and the current working directory are on different drives (which would
raise ValueError: path is on mount 'C:', start on mount 'D:')."""
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):
Expand All @@ -188,15 +198,15 @@ 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 = _try_relpath(args.config)
if args.command in {'run', 'try-repo'}:
args.files = [os.path.relpath(filename) for filename in args.files]
args.files = [_try_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 = _try_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 = _try_relpath(args.repo)


def main(argv: Sequence[str] | None = None) -> int:
Expand Down
29 changes: 29 additions & 0 deletions tests/main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,35 @@ def test_adjust_args_try_repo_repo_relative(in_git_dir):
assert args.repo == 'foo'


def test_try_relpath_returns_relpath_when_same_drive():
# Normal case: same drive / same filesystem -- should behave like relpath
result = main._try_relpath(os.path.abspath('.'))
assert result == os.path.relpath(os.path.abspath('.'))


def test_try_relpath_returns_original_on_cross_drive_valueerror():
# On Windows, os.path.relpath raises ValueError when paths are on
# different drives (#2530). _try_relpath must return the path unchanged.
abs_path = 'C:\\Users\\me\\.pre-commit-config.yaml'
with mock.patch('os.path.relpath', side_effect=ValueError('different mount')):
assert main._try_relpath(abs_path) == abs_path


@pytest.mark.skipif(os.name != 'nt', reason='windows cross-drive only')
def test_adjust_args_config_on_different_drive(in_git_dir): # pragma: posix no cover
# Simulate a config whose absolute path is on a drive other than the git
# root so that os.path.relpath would raise ValueError.
abs_config = 'C:\\Users\\me\\.pre-commit-config.yaml'
with mock.patch('pre_commit.main.os.path.relpath', side_effect=ValueError('x')):
args = _args(config=abs_config)
# abspath is called before chdir, so mock exists to avoid a real chdir
with mock.patch('os.path.exists', return_value=False):
with mock.patch('pre_commit.git.get_root', return_value=str(in_git_dir)):
main._adjust_args_and_chdir(args)
# config must be preserved (not explode with ValueError)
assert args.config == abs_config


FNS = (
'autoupdate', 'clean', 'gc', 'hook_impl', 'install', 'install_hooks',
'migrate_config', 'run', 'sample_config', 'uninstall',
Expand Down
Loading