Skip to content

Commit 66278a9

Browse files
committed
move logic for gc back to commands.gc
1 parent 1b32c50 commit 66278a9

File tree

4 files changed

+100
-92
lines changed

4 files changed

+100
-92
lines changed

pre_commit/commands/gc.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,98 @@
11
from __future__ import annotations
22

3+
import os.path
4+
from typing import Any
5+
6+
import pre_commit.constants as C
37
from pre_commit import output
8+
from pre_commit.clientlib import InvalidConfigError
9+
from pre_commit.clientlib import InvalidManifestError
10+
from pre_commit.clientlib import load_config
11+
from pre_commit.clientlib import load_manifest
12+
from pre_commit.clientlib import LOCAL
13+
from pre_commit.clientlib import META
414
from pre_commit.store import Store
15+
from pre_commit.util import rmtree
16+
17+
18+
def _mark_used_repos(
19+
store: Store,
20+
all_repos: dict[tuple[str, str], str],
21+
unused_repos: set[tuple[str, str]],
22+
repo: dict[str, Any],
23+
) -> None:
24+
if repo['repo'] == META:
25+
return
26+
elif repo['repo'] == LOCAL:
27+
for hook in repo['hooks']:
28+
deps = hook.get('additional_dependencies')
29+
unused_repos.discard((
30+
store.db_repo_name(repo['repo'], deps),
31+
C.LOCAL_REPO_VERSION,
32+
))
33+
else:
34+
key = (repo['repo'], repo['rev'])
35+
path = all_repos.get(key)
36+
# can't inspect manifest if it isn't cloned
37+
if path is None:
38+
return
39+
40+
try:
41+
manifest = load_manifest(os.path.join(path, C.MANIFEST_FILE))
42+
except InvalidManifestError:
43+
return
44+
else:
45+
unused_repos.discard(key)
46+
by_id = {hook['id']: hook for hook in manifest}
47+
48+
for hook in repo['hooks']:
49+
if hook['id'] not in by_id:
50+
continue
51+
52+
deps = hook.get(
53+
'additional_dependencies',
54+
by_id[hook['id']]['additional_dependencies'],
55+
)
56+
unused_repos.discard((
57+
store.db_repo_name(repo['repo'], deps), repo['rev'],
58+
))
59+
60+
61+
def _gc(store: Store) -> int:
62+
with store.exclusive_lock(), store.connect() as db:
63+
store._create_configs_table(db)
64+
65+
repos = db.execute('SELECT repo, ref, path FROM repos').fetchall()
66+
all_repos = {(repo, ref): path for repo, ref, path in repos}
67+
unused_repos = set(all_repos)
68+
69+
configs_rows = db.execute('SELECT path FROM configs').fetchall()
70+
configs = [path for path, in configs_rows]
71+
72+
dead_configs = []
73+
for config_path in configs:
74+
try:
75+
config = load_config(config_path)
76+
except InvalidConfigError:
77+
dead_configs.append(config_path)
78+
continue
79+
else:
80+
for repo in config['repos']:
81+
_mark_used_repos(store, all_repos, unused_repos, repo)
82+
83+
paths = [(path,) for path in dead_configs]
84+
db.executemany('DELETE FROM configs WHERE path = ?', paths)
85+
86+
db.executemany(
87+
'DELETE FROM repos WHERE repo = ? and ref = ?',
88+
sorted(unused_repos),
89+
)
90+
for k in unused_repos:
91+
rmtree(all_repos[k])
92+
93+
return len(unused_repos)
594

695

796
def gc(store: Store) -> int:
8-
output.write_line(f'{store.gc()} repo(s) removed.')
97+
output.write_line(f'{_gc(store)} repo(s) removed.')
998
return 0

pre_commit/store.py

Lines changed: 0 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
from collections.abc import Callable
99
from collections.abc import Generator
1010
from collections.abc import Sequence
11-
from typing import Any
1211

1312
import pre_commit.constants as C
1413
from pre_commit import clientlib
@@ -18,7 +17,6 @@
1817
from pre_commit.util import clean_path_on_failure
1918
from pre_commit.util import cmd_output_b
2019
from pre_commit.util import resource_text
21-
from pre_commit.util import rmtree
2220

2321

2422
logger = logging.getLogger('pre_commit')
@@ -235,81 +233,3 @@ def mark_config_used(self, path: str) -> None:
235233
# TODO: eventually remove this and only create in _create
236234
self._create_configs_table(db)
237235
db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,))
238-
239-
def _mark_used_repos(
240-
self,
241-
all_repos: dict[tuple[str, str], str],
242-
unused_repos: set[tuple[str, str]],
243-
repo: dict[str, Any],
244-
) -> None:
245-
if repo['repo'] == clientlib.META:
246-
return
247-
elif repo['repo'] == clientlib.LOCAL:
248-
for hook in repo['hooks']:
249-
deps = hook.get('additional_dependencies')
250-
unused_repos.discard((
251-
self.db_repo_name(repo['repo'], deps),
252-
C.LOCAL_REPO_VERSION,
253-
))
254-
else:
255-
key = (repo['repo'], repo['rev'])
256-
path = all_repos.get(key)
257-
# can't inspect manifest if it isn't cloned
258-
if path is None:
259-
return
260-
261-
try:
262-
manifest = clientlib.load_manifest(
263-
os.path.join(path, C.MANIFEST_FILE),
264-
)
265-
except clientlib.InvalidManifestError:
266-
return
267-
else:
268-
unused_repos.discard(key)
269-
by_id = {hook['id']: hook for hook in manifest}
270-
271-
for hook in repo['hooks']:
272-
if hook['id'] not in by_id:
273-
continue
274-
275-
deps = hook.get(
276-
'additional_dependencies',
277-
by_id[hook['id']]['additional_dependencies'],
278-
)
279-
unused_repos.discard((
280-
self.db_repo_name(repo['repo'], deps), repo['rev'],
281-
))
282-
283-
def gc(self) -> int:
284-
with self.exclusive_lock(), self.connect() as db:
285-
self._create_configs_table(db)
286-
287-
repos = db.execute('SELECT repo, ref, path FROM repos').fetchall()
288-
all_repos = {(repo, ref): path for repo, ref, path in repos}
289-
unused_repos = set(all_repos)
290-
291-
configs_rows = db.execute('SELECT path FROM configs').fetchall()
292-
configs = [path for path, in configs_rows]
293-
294-
dead_configs = []
295-
for config_path in configs:
296-
try:
297-
config = clientlib.load_config(config_path)
298-
except clientlib.InvalidConfigError:
299-
dead_configs.append(config_path)
300-
continue
301-
else:
302-
for repo in config['repos']:
303-
self._mark_used_repos(all_repos, unused_repos, repo)
304-
305-
paths = [(path,) for path in dead_configs]
306-
db.executemany('DELETE FROM configs WHERE path = ?', paths)
307-
308-
db.executemany(
309-
'DELETE FROM repos WHERE repo = ? and ref = ?',
310-
sorted(unused_repos),
311-
)
312-
for k in unused_repos:
313-
rmtree(all_repos[k])
314-
315-
return len(unused_repos)

tests/commands/gc_test.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,11 @@ def test_invalid_manifest_gcd(tempdir_factory, store, in_git_dir, cap_out):
165165
assert _config_count(store) == 1
166166
assert _repo_count(store) == 0
167167
assert cap_out.get().splitlines()[-1] == '1 repo(s) removed.'
168+
169+
170+
def test_gc_pre_1_14_roll_forward(store, cap_out):
171+
with store.connect() as db: # simulate pre-1.14.0
172+
db.executescript('DROP TABLE configs')
173+
174+
assert not gc(store)
175+
assert cap_out.get() == '0 repo(s) removed.\n'

tests/store_test.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -289,18 +289,9 @@ def test_mark_config_as_used_does_not_exist(store):
289289
assert _select_all_configs(store) == []
290290

291291

292-
def _simulate_pre_1_14_0(store):
293-
with store.connect() as db:
294-
db.executescript('DROP TABLE configs')
295-
296-
297-
def test_gc_roll_forward(store):
298-
_simulate_pre_1_14_0(store)
299-
assert store.gc() == 0
300-
301-
302292
def test_mark_config_as_used_roll_forward(store, tmpdir):
303-
_simulate_pre_1_14_0(store)
293+
with store.connect() as db: # simulate pre-1.14.0
294+
db.executescript('DROP TABLE configs')
304295
test_mark_config_as_used(store, tmpdir)
305296

306297

0 commit comments

Comments
 (0)