summaryrefslogtreecommitdiffstats
path: root/tests/commands/gc_test.py
blob: 95113ed5c6c368f90e93157157eea19b34134dbc (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
from __future__ import annotations

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
    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)

    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.'