From 16f7c46de5822d0c3cb2f30da8fb483e07e73255 Mon Sep 17 00:00:00 2001 From: Daisuke Sato Date: Wed, 11 Mar 2026 19:11:07 +0900 Subject: [PATCH] fix `uninstall` without `-t` to remove all pre-commit managed hooks Previously, `pre-commit uninstall` without `-t` only removed hooks listed in `default_install_hook_types` from the config. This meant hooks installed with e.g. `-t pre-push` would not be removed unless `-t pre-push` was explicitly passed to uninstall. Now, when `-t` is not specified, uninstall scans all known hook types and removes any that are managed by pre-commit (via `is_our_script`). Fixes #364 Co-Authored-By: Claude Opus 4.6 --- pre_commit/commands/install_uninstall.py | 7 ++++++- tests/commands/install_uninstall_test.py | 23 +++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/pre_commit/commands/install_uninstall.py b/pre_commit/commands/install_uninstall.py index d19e0d47..e5ac701e 100644 --- a/pre_commit/commands/install_uninstall.py +++ b/pre_commit/commands/install_uninstall.py @@ -8,6 +8,7 @@ import sys from pre_commit import git from pre_commit import output +from pre_commit.clientlib import HOOK_TYPES from pre_commit.clientlib import InvalidConfigError from pre_commit.clientlib import load_config from pre_commit.repository import all_hooks @@ -162,6 +163,10 @@ def _uninstall_hook_script(hook_type: str) -> None: def uninstall(config_file: str, hook_types: list[str] | None) -> int: - for hook_type in _hook_types(config_file, hook_types): + if hook_types is not None: + actual_hook_types = hook_types + else: + actual_hook_types = list(HOOK_TYPES) + for hook_type in actual_hook_types: _uninstall_hook_script(hook_type) return 0 diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py index 9eb0e741..9e6df665 100644 --- a/tests/commands/install_uninstall_test.py +++ b/tests/commands/install_uninstall_test.py @@ -1102,3 +1102,26 @@ def test_install_uninstall_default_hook_types(in_git_dir, store): assert not uninstall(C.CONFIG_FILE, hook_types=None) assert not in_git_dir.join('.git/hooks/pre-commit').exists() assert not in_git_dir.join('.git/hooks/pre-push').exists() + + +def test_uninstall_without_t_removes_all_hooks(in_git_dir, store): + install(C.CONFIG_FILE, store, hook_types=['pre-commit']) + install(C.CONFIG_FILE, store, hook_types=['pre-push']) + assert in_git_dir.join('.git/hooks/pre-commit').exists() + assert in_git_dir.join('.git/hooks/pre-push').exists() + + assert not uninstall(C.CONFIG_FILE, hook_types=None) + assert not in_git_dir.join('.git/hooks/pre-commit').exists() + assert not in_git_dir.join('.git/hooks/pre-push').exists() + + +def test_uninstall_without_t_ignores_non_precommit_hooks(in_git_dir, store): + install(C.CONFIG_FILE, store, hook_types=['pre-commit']) + # write a non-pre-commit hook + in_git_dir.join('.git/hooks/pre-push').write('#!/bin/sh\necho custom\n') + assert in_git_dir.join('.git/hooks/pre-push').exists() + + assert not uninstall(C.CONFIG_FILE, hook_types=None) + assert not in_git_dir.join('.git/hooks/pre-commit').exists() + # non-pre-commit hook should be preserved + assert in_git_dir.join('.git/hooks/pre-push').exists()