2025-02-09 21:32:53 +01:00
|
|
|
from __future__ import annotations
|
|
|
|
|
2025-02-09 21:10:22 +01:00
|
|
|
import os
|
|
|
|
|
|
|
|
import pre_commit.constants as C
|
|
|
|
from pre_commit import git
|
|
|
|
from pre_commit.clientlib import load_config
|
|
|
|
from pre_commit.commands.autoupdate import autoupdate
|
|
|
|
from pre_commit.commands.gc import gc
|
|
|
|
from pre_commit.commands.install_uninstall import install_hooks
|
|
|
|
from pre_commit.repository import all_hooks
|
|
|
|
from testing.fixtures import make_config_from_repo
|
|
|
|
from testing.fixtures import make_repo
|
|
|
|
from testing.fixtures import modify_config
|
|
|
|
from testing.fixtures import sample_local_config
|
|
|
|
from testing.fixtures import sample_meta_config
|
|
|
|
from testing.fixtures import write_config
|
|
|
|
from testing.util import git_commit
|
|
|
|
|
|
|
|
|
|
|
|
def _repo_count(store):
|
|
|
|
return len(store.select_all_repos())
|
|
|
|
|
|
|
|
|
|
|
|
def _config_count(store):
|
|
|
|
return len(store.select_all_configs())
|
|
|
|
|
|
|
|
|
|
|
|
def _remove_config_assert_cleared(store, cap_out):
|
|
|
|
os.remove(C.CONFIG_FILE)
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 0
|
|
|
|
assert _repo_count(store) == 0
|
|
|
|
assert cap_out.get().splitlines()[-1] == '1 repo(s) removed.'
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc(tempdir_factory, store, in_git_dir, cap_out):
|
|
|
|
path = make_repo(tempdir_factory, 'script_hooks_repo')
|
|
|
|
old_rev = git.head_rev(path)
|
|
|
|
git_commit(cwd=path)
|
|
|
|
|
|
|
|
write_config('.', make_config_from_repo(path, rev=old_rev))
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
|
|
|
|
# update will clone both the old and new repo, making the old one gc-able
|
2025-02-09 21:41:42 +01:00
|
|
|
assert not install_hooks(C.CONFIG_FILE, store)
|
|
|
|
assert not autoupdate(C.CONFIG_FILE, freeze=False, tags_only=False)
|
|
|
|
assert not install_hooks(C.CONFIG_FILE, store)
|
2025-02-09 21:10:22 +01:00
|
|
|
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 2
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 1
|
|
|
|
assert cap_out.get().splitlines()[-1] == '1 repo(s) removed.'
|
|
|
|
|
|
|
|
_remove_config_assert_cleared(store, cap_out)
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc_repo_not_cloned(tempdir_factory, store, in_git_dir, cap_out):
|
|
|
|
path = make_repo(tempdir_factory, 'script_hooks_repo')
|
|
|
|
write_config('.', make_config_from_repo(path))
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 0
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 0
|
|
|
|
assert cap_out.get().splitlines()[-1] == '0 repo(s) removed.'
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc_meta_repo_does_not_crash(store, in_git_dir, cap_out):
|
|
|
|
write_config('.', sample_meta_config())
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
assert not gc(store)
|
|
|
|
assert cap_out.get().splitlines()[-1] == '0 repo(s) removed.'
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc_local_repo_does_not_crash(store, in_git_dir, cap_out):
|
|
|
|
write_config('.', sample_local_config())
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
assert not gc(store)
|
|
|
|
assert cap_out.get().splitlines()[-1] == '0 repo(s) removed.'
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc_unused_local_repo_with_env(store, in_git_dir, cap_out):
|
|
|
|
config = {
|
|
|
|
'repo': 'local',
|
|
|
|
'hooks': [{
|
|
|
|
'id': 'flake8', 'name': 'flake8', 'entry': 'flake8',
|
|
|
|
# a `language: python` local hook will create an environment
|
|
|
|
'types': ['python'], 'language': 'python',
|
|
|
|
}],
|
|
|
|
}
|
|
|
|
write_config('.', config)
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
|
|
|
|
# this causes the repositories to be created
|
|
|
|
all_hooks(load_config(C.CONFIG_FILE), store)
|
|
|
|
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 1
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 1
|
|
|
|
assert cap_out.get().splitlines()[-1] == '0 repo(s) removed.'
|
|
|
|
|
|
|
|
_remove_config_assert_cleared(store, cap_out)
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc_config_with_missing_hook(
|
|
|
|
tempdir_factory, store, in_git_dir, cap_out,
|
|
|
|
):
|
|
|
|
path = make_repo(tempdir_factory, 'script_hooks_repo')
|
|
|
|
write_config('.', make_config_from_repo(path))
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
# to trigger a clone
|
|
|
|
all_hooks(load_config(C.CONFIG_FILE), store)
|
|
|
|
|
|
|
|
with modify_config() as config:
|
|
|
|
# add a hook which does not exist, make sure we don't crash
|
|
|
|
config['repos'][0]['hooks'].append({'id': 'does-not-exist'})
|
|
|
|
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 1
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 1
|
|
|
|
assert cap_out.get().splitlines()[-1] == '0 repo(s) removed.'
|
|
|
|
|
|
|
|
_remove_config_assert_cleared(store, cap_out)
|
|
|
|
|
|
|
|
|
|
|
|
def test_gc_deletes_invalid_configs(store, in_git_dir, cap_out):
|
|
|
|
config = {'i am': 'invalid'}
|
|
|
|
write_config('.', config)
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 0
|
|
|
|
assert cap_out.get().splitlines()[-1] == '0 repo(s) removed.'
|
|
|
|
|
|
|
|
|
|
|
|
def test_invalid_manifest_gcd(tempdir_factory, store, in_git_dir, cap_out):
|
|
|
|
# clean up repos from old pre-commit versions
|
|
|
|
path = make_repo(tempdir_factory, 'script_hooks_repo')
|
|
|
|
write_config('.', make_config_from_repo(path))
|
|
|
|
store.mark_config_used(C.CONFIG_FILE)
|
|
|
|
|
|
|
|
# trigger a clone
|
|
|
|
install_hooks(C.CONFIG_FILE, store)
|
|
|
|
|
|
|
|
# we'll "break" the manifest to simulate an old version clone
|
|
|
|
(_, _, path), = store.select_all_repos()
|
|
|
|
os.remove(os.path.join(path, C.MANIFEST_FILE))
|
|
|
|
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 1
|
|
|
|
assert not gc(store)
|
|
|
|
assert _config_count(store) == 1
|
|
|
|
assert _repo_count(store) == 0
|
|
|
|
assert cap_out.get().splitlines()[-1] == '1 repo(s) removed.'
|