diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3654066f..fa077365 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: name-tests-test - id: requirements-txt-fixer - repo: https://github.com/asottile/setup-cfg-fmt - rev: v3.2.0 + rev: v3.1.0 hooks: - id: setup-cfg-fmt - repo: https://github.com/asottile/reorder-python-imports @@ -24,7 +24,7 @@ repos: hooks: - id: add-trailing-comma - repo: https://github.com/asottile/pyupgrade - rev: v3.21.2 + rev: v3.21.0 hooks: - id: pyupgrade args: [--py310-plus] @@ -37,7 +37,7 @@ repos: hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.19.1 + rev: v1.18.2 hooks: - id: mypy additional_dependencies: [types-pyyaml] diff --git a/CHANGELOG.md b/CHANGELOG.md index 879ae073..b27af5e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,17 +1,3 @@ -4.5.1 - 2025-12-16 -================== - -### Fixes -- Fix `language: python` with `repo: local` without `additional_dependencies`. - - #3597 PR by @asottile. - -4.5.0 - 2025-11-22 -================== - -### Features -- Add `pre-commit hazmat`. - - #3585 PR by @asottile. - 4.4.0 - 2025-11-08 ================== diff --git a/pre_commit/clientlib.py b/pre_commit/clientlib.py index 51f14d26..eb0fd4d6 100644 --- a/pre_commit/clientlib.py +++ b/pre_commit/clientlib.py @@ -270,19 +270,10 @@ class InvalidManifestError(FatalError): pass -def _load_manifest_forward_compat(contents: str) -> object: - obj = yaml_load(contents) - if isinstance(obj, dict): - check_min_version('5') - raise AssertionError('unreachable') - else: - return obj - - load_manifest = functools.partial( cfgv.load_from_filename, schema=MANIFEST_SCHEMA, - load_strategy=_load_manifest_forward_compat, + load_strategy=yaml_load, exc_tp=InvalidManifestError, ) diff --git a/pre_commit/commands/gc.py b/pre_commit/commands/gc.py index 975d5e4c..6892e097 100644 --- a/pre_commit/commands/gc.py +++ b/pre_commit/commands/gc.py @@ -12,7 +12,6 @@ from pre_commit.clientlib import load_manifest from pre_commit.clientlib import LOCAL from pre_commit.clientlib import META from pre_commit.store import Store -from pre_commit.util import rmtree def _mark_used_repos( @@ -27,8 +26,7 @@ def _mark_used_repos( for hook in repo['hooks']: deps = hook.get('additional_dependencies') unused_repos.discard(( - store.db_repo_name(repo['repo'], deps), - C.LOCAL_REPO_VERSION, + store.db_repo_name(repo['repo'], deps), C.LOCAL_REPO_VERSION, )) else: key = (repo['repo'], repo['rev']) @@ -58,41 +56,34 @@ def _mark_used_repos( )) -def _gc(store: Store) -> int: - with store.exclusive_lock(), store.connect() as db: - store._create_configs_table(db) +def _gc_repos(store: Store) -> int: + configs = store.select_all_configs() + repos = store.select_all_repos() - repos = db.execute('SELECT repo, ref, path FROM repos').fetchall() - all_repos = {(repo, ref): path for repo, ref, path in repos} - unused_repos = set(all_repos) + # delete config paths which do not exist + dead_configs = [p for p in configs if not os.path.exists(p)] + live_configs = [p for p in configs if os.path.exists(p)] - configs_rows = db.execute('SELECT path FROM configs').fetchall() - configs = [path for path, in configs_rows] + all_repos = {(repo, ref): path for repo, ref, path in repos} + unused_repos = set(all_repos) + for config_path in live_configs: + try: + config = load_config(config_path) + except InvalidConfigError: + dead_configs.append(config_path) + continue + else: + for repo in config['repos']: + _mark_used_repos(store, all_repos, unused_repos, repo) - dead_configs = [] - for config_path in configs: - try: - config = load_config(config_path) - except InvalidConfigError: - dead_configs.append(config_path) - continue - else: - for repo in config['repos']: - _mark_used_repos(store, all_repos, unused_repos, repo) - - paths = [(path,) for path in dead_configs] - db.executemany('DELETE FROM configs WHERE path = ?', paths) - - db.executemany( - 'DELETE FROM repos WHERE repo = ? and ref = ?', - sorted(unused_repos), - ) - for k in unused_repos: - rmtree(all_repos[k]) - - return len(unused_repos) + store.delete_configs(dead_configs) + for db_repo_name, ref in unused_repos: + store.delete_repo(db_repo_name, ref, all_repos[(db_repo_name, ref)]) + return len(unused_repos) def gc(store: Store) -> int: - output.write_line(f'{_gc(store)} repo(s) removed.') + with store.exclusive_lock(): + repos_removed = _gc_repos(store) + output.write_line(f'{repos_removed} repo(s) removed.') return 0 diff --git a/pre_commit/commands/hazmat.py b/pre_commit/commands/hazmat.py deleted file mode 100644 index 01b27ce6..00000000 --- a/pre_commit/commands/hazmat.py +++ /dev/null @@ -1,95 +0,0 @@ -from __future__ import annotations - -import argparse -import subprocess -from collections.abc import Sequence - -from pre_commit.parse_shebang import normalize_cmd - - -def add_parsers(parser: argparse.ArgumentParser) -> None: - subparsers = parser.add_subparsers(dest='tool') - - cd_parser = subparsers.add_parser( - 'cd', help='cd to a subdir and run the command', - ) - cd_parser.add_argument('subdir') - cd_parser.add_argument('cmd', nargs=argparse.REMAINDER) - - ignore_exit_code_parser = subparsers.add_parser( - 'ignore-exit-code', help='run the command but ignore the exit code', - ) - ignore_exit_code_parser.add_argument('cmd', nargs=argparse.REMAINDER) - - n1_parser = subparsers.add_parser( - 'n1', help='run the command once per filename', - ) - n1_parser.add_argument('cmd', nargs=argparse.REMAINDER) - - -def _cmd_filenames(cmd: tuple[str, ...]) -> tuple[ - tuple[str, ...], - tuple[str, ...], -]: - for idx, val in enumerate(reversed(cmd)): - if val == '--': - split = len(cmd) - idx - break - else: - raise SystemExit('hazmat entry must end with `--`') - - return cmd[:split - 1], cmd[split:] - - -def cd(subdir: str, cmd: tuple[str, ...]) -> int: - cmd, filenames = _cmd_filenames(cmd) - - prefix = f'{subdir}/' - new_filenames = [] - for filename in filenames: - if not filename.startswith(prefix): - raise SystemExit(f'unexpected file without {prefix=}: {filename}') - else: - new_filenames.append(filename.removeprefix(prefix)) - - cmd = normalize_cmd(cmd) - return subprocess.call((*cmd, *new_filenames), cwd=subdir) - - -def ignore_exit_code(cmd: tuple[str, ...]) -> int: - cmd = normalize_cmd(cmd) - subprocess.call(cmd) - return 0 - - -def n1(cmd: tuple[str, ...]) -> int: - cmd, filenames = _cmd_filenames(cmd) - cmd = normalize_cmd(cmd) - ret = 0 - for filename in filenames: - ret |= subprocess.call((*cmd, filename)) - return ret - - -def impl(args: argparse.Namespace) -> int: - args.cmd = tuple(args.cmd) - if args.tool == 'cd': - return cd(args.subdir, args.cmd) - elif args.tool == 'ignore-exit-code': - return ignore_exit_code(args.cmd) - elif args.tool == 'n1': - return n1(args.cmd) - else: - raise NotImplementedError(f'unexpected tool: {args.tool}') - - -def main(argv: Sequence[str] | None = None) -> int: - parser = argparse.ArgumentParser() - add_parsers(parser) - args = parser.parse_args(argv) - - return impl(args) - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/pre_commit/lang_base.py b/pre_commit/lang_base.py index 198e9365..95be7b9b 100644 --- a/pre_commit/lang_base.py +++ b/pre_commit/lang_base.py @@ -5,7 +5,6 @@ import os import random import re import shlex -import sys from collections.abc import Generator from collections.abc import Sequence from typing import Any @@ -172,10 +171,7 @@ def run_xargs( def hook_cmd(entry: str, args: Sequence[str]) -> tuple[str, ...]: - cmd = shlex.split(entry) - if cmd[:2] == ['pre-commit', 'hazmat']: - cmd = [sys.executable, '-m', 'pre_commit.commands.hazmat', *cmd[2:]] - return (*cmd, *args) + return (*shlex.split(entry), *args) def basic_run_hook( diff --git a/pre_commit/main.py b/pre_commit/main.py index 0c3eefda..c33fbfda 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -10,7 +10,6 @@ import pre_commit.constants as C from pre_commit import clientlib from pre_commit import git from pre_commit.color import add_color_option -from pre_commit.commands import hazmat from pre_commit.commands.autoupdate import autoupdate from pre_commit.commands.clean import clean from pre_commit.commands.gc import gc @@ -42,7 +41,7 @@ os.environ.pop('__PYVENV_LAUNCHER__', None) os.environ.pop('PYTHONEXECUTABLE', None) COMMANDS_NO_GIT = { - 'clean', 'gc', 'hazmat', 'init-templatedir', 'sample-config', + 'clean', 'gc', 'init-templatedir', 'sample-config', 'validate-config', 'validate-manifest', } @@ -246,11 +245,6 @@ def main(argv: Sequence[str] | None = None) -> int: _add_cmd('gc', help='Clean unused cached repos.') - hazmat_parser = _add_cmd( - 'hazmat', help='Composable tools for rare use in hook `entry`.', - ) - hazmat.add_parsers(hazmat_parser) - init_templatedir_parser = _add_cmd( 'init-templatedir', help=( @@ -395,8 +389,6 @@ def main(argv: Sequence[str] | None = None) -> int: return clean(store) elif args.command == 'gc': return gc(store) - elif args.command == 'hazmat': - return hazmat.impl(args) elif args.command == 'hook-impl': return hook_impl( store, diff --git a/pre_commit/resources/empty_template_setup.py b/pre_commit/resources/empty_template_setup.py index e8b1ff02..ef05eef8 100644 --- a/pre_commit/resources/empty_template_setup.py +++ b/pre_commit/resources/empty_template_setup.py @@ -1,4 +1,4 @@ from setuptools import setup -setup(name='pre-commit-placeholder-package', version='0.0.0', py_modules=[]) +setup(name='pre-commit-placeholder-package', version='0.0.0') diff --git a/pre_commit/store.py b/pre_commit/store.py index dc90c051..9e3b4048 100644 --- a/pre_commit/store.py +++ b/pre_commit/store.py @@ -17,6 +17,7 @@ from pre_commit.util import CalledProcessError from pre_commit.util import clean_path_on_failure from pre_commit.util import cmd_output_b from pre_commit.util import resource_text +from pre_commit.util import rmtree logger = logging.getLogger('pre_commit') @@ -95,7 +96,7 @@ class Store: ' PRIMARY KEY (repo, ref)' ');', ) - self._create_configs_table(db) + self._create_config_table(db) # Atomic file move os.replace(tmpfile, self.db_path) @@ -214,7 +215,7 @@ class Store: 'local', C.LOCAL_REPO_VERSION, deps, _make_local_repo, ) - def _create_configs_table(self, db: sqlite3.Connection) -> None: + def _create_config_table(self, db: sqlite3.Connection) -> None: db.executescript( 'CREATE TABLE IF NOT EXISTS configs (' ' path TEXT NOT NULL,' @@ -231,5 +232,28 @@ class Store: return with self.connect() as db: # TODO: eventually remove this and only create in _create - self._create_configs_table(db) + self._create_config_table(db) db.execute('INSERT OR IGNORE INTO configs VALUES (?)', (path,)) + + def select_all_configs(self) -> list[str]: + with self.connect() as db: + self._create_config_table(db) + rows = db.execute('SELECT path FROM configs').fetchall() + return [path for path, in rows] + + def delete_configs(self, configs: list[str]) -> None: + with self.connect() as db: + rows = [(path,) for path in configs] + db.executemany('DELETE FROM configs WHERE path = ?', rows) + + def select_all_repos(self) -> list[tuple[str, str, str]]: + with self.connect() as db: + return db.execute('SELECT repo, ref, path from repos').fetchall() + + def delete_repo(self, db_repo_name: str, ref: str, path: str) -> None: + with self.connect() as db: + db.execute( + 'DELETE FROM repos WHERE repo = ? and ref = ?', + (db_repo_name, ref), + ) + rmtree(path) diff --git a/setup.cfg b/setup.cfg index a95ee447..be031c3e 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,6 +1,6 @@ [metadata] name = pre_commit -version = 4.5.1 +version = 4.4.0 description = A framework for managing and maintaining multi-language pre-commit hooks. long_description = file: README.md long_description_content_type = text/markdown diff --git a/testing/zipapp/make b/testing/zipapp/make index 43bb4373..165046f6 100755 --- a/testing/zipapp/make +++ b/testing/zipapp/make @@ -107,6 +107,9 @@ def main() -> int: shebang = '/usr/bin/env python3' zipapp.create_archive(tmpdir, filename, interpreter=shebang) + with open(f'{filename}.sha256sum', 'w') as f: + subprocess.check_call(('sha256sum', filename), stdout=f) + return 0 diff --git a/tests/clientlib_test.py b/tests/clientlib_test.py index 2c42b80c..93c698f7 100644 --- a/tests/clientlib_test.py +++ b/tests/clientlib_test.py @@ -12,8 +12,6 @@ from pre_commit.clientlib import CONFIG_HOOK_DICT from pre_commit.clientlib import CONFIG_REPO_DICT from pre_commit.clientlib import CONFIG_SCHEMA from pre_commit.clientlib import DEFAULT_LANGUAGE_VERSION -from pre_commit.clientlib import InvalidManifestError -from pre_commit.clientlib import load_manifest from pre_commit.clientlib import MANIFEST_HOOK_DICT from pre_commit.clientlib import MANIFEST_SCHEMA from pre_commit.clientlib import META_HOOK_DICT @@ -590,18 +588,3 @@ def test_config_hook_stages_defaulting(): 'id': 'fake-hook', 'stages': ['commit-msg', 'pre-push', 'pre-commit', 'pre-merge-commit'], } - - -def test_manifest_v5_forward_compat(tmp_path): - manifest = tmp_path.joinpath('.pre-commit-hooks.yaml') - manifest.write_text('hooks: {}') - - with pytest.raises(InvalidManifestError) as excinfo: - load_manifest(manifest) - assert str(excinfo.value) == ( - f'\n' - f'==> File {manifest}\n' - f'=====> \n' - f'=====> pre-commit version 5 is required but version {C.VERSION} ' - f'is installed. Perhaps run `pip install --upgrade pre-commit`.' - ) diff --git a/tests/commands/gc_test.py b/tests/commands/gc_test.py index 992b02f3..95113ed5 100644 --- a/tests/commands/gc_test.py +++ b/tests/commands/gc_test.py @@ -19,13 +19,11 @@ from testing.util import git_commit def _repo_count(store): - with store.connect() as db: - return db.execute('SELECT COUNT(1) FROM repos').fetchone()[0] + return len(store.select_all_repos()) def _config_count(store): - with store.connect() as db: - return db.execute('SELECT COUNT(1) FROM configs').fetchone()[0] + return len(store.select_all_configs()) def _remove_config_assert_cleared(store, cap_out): @@ -155,8 +153,7 @@ def test_invalid_manifest_gcd(tempdir_factory, store, in_git_dir, cap_out): install_hooks(C.CONFIG_FILE, store) # we'll "break" the manifest to simulate an old version clone - with store.connect() as db: - path, = db.execute('SELECT path FROM repos').fetchone() + (_, _, path), = store.select_all_repos() os.remove(os.path.join(path, C.MANIFEST_FILE)) assert _config_count(store) == 1 @@ -165,11 +162,3 @@ def test_invalid_manifest_gcd(tempdir_factory, store, in_git_dir, cap_out): assert _config_count(store) == 1 assert _repo_count(store) == 0 assert cap_out.get().splitlines()[-1] == '1 repo(s) removed.' - - -def test_gc_pre_1_14_roll_forward(store, cap_out): - with store.connect() as db: # simulate pre-1.14.0 - db.executescript('DROP TABLE configs') - - assert not gc(store) - assert cap_out.get() == '0 repo(s) removed.\n' diff --git a/tests/commands/hazmat_test.py b/tests/commands/hazmat_test.py deleted file mode 100644 index df957e36..00000000 --- a/tests/commands/hazmat_test.py +++ /dev/null @@ -1,99 +0,0 @@ -from __future__ import annotations - -import sys - -import pytest - -from pre_commit.commands.hazmat import _cmd_filenames -from pre_commit.commands.hazmat import main -from testing.util import cwd - - -def test_cmd_filenames_no_dash_dash(): - with pytest.raises(SystemExit) as excinfo: - _cmd_filenames(('no', 'dashdash', 'here')) - msg, = excinfo.value.args - assert msg == 'hazmat entry must end with `--`' - - -def test_cmd_filenames_no_filenames(): - cmd, filenames = _cmd_filenames(('hello', 'world', '--')) - assert cmd == ('hello', 'world') - assert filenames == () - - -def test_cmd_filenames_some_filenames(): - cmd, filenames = _cmd_filenames(('hello', 'world', '--', 'f1', 'f2')) - assert cmd == ('hello', 'world') - assert filenames == ('f1', 'f2') - - -def test_cmd_filenames_multiple_dashdash(): - cmd, filenames = _cmd_filenames(('hello', '--', 'arg', '--', 'f1', 'f2')) - assert cmd == ('hello', '--', 'arg') - assert filenames == ('f1', 'f2') - - -def test_cd_unexpected_filename(): - with pytest.raises(SystemExit) as excinfo: - main(('cd', 'subdir', 'cmd', '--', 'subdir/1', 'not-subdir/2')) - msg, = excinfo.value.args - assert msg == "unexpected file without prefix='subdir/': not-subdir/2" - - -def _norm(out): - return out.replace('\r\n', '\n') - - -def test_cd(tmp_path, capfd): - subdir = tmp_path.joinpath('subdir') - subdir.mkdir() - subdir.joinpath('a').write_text('a') - subdir.joinpath('b').write_text('b') - - with cwd(tmp_path): - ret = main(( - 'cd', 'subdir', - sys.executable, '-c', - 'import os; print(os.getcwd());' - 'import sys; [print(open(f).read()) for f in sys.argv[1:]]', - '--', - 'subdir/a', 'subdir/b', - )) - - assert ret == 0 - out, err = capfd.readouterr() - assert _norm(out) == f'{subdir}\na\nb\n' - assert err == '' - - -def test_ignore_exit_code(capfd): - ret = main(( - 'ignore-exit-code', sys.executable, '-c', 'raise SystemExit("bye")', - )) - assert ret == 0 - out, err = capfd.readouterr() - assert out == '' - assert _norm(err) == 'bye\n' - - -def test_n1(capfd): - ret = main(( - 'n1', sys.executable, '-c', 'import sys; print(sys.argv[1:])', - '--', - 'foo', 'bar', 'baz', - )) - assert ret == 0 - out, err = capfd.readouterr() - assert _norm(out) == "['foo']\n['bar']\n['baz']\n" - assert err == '' - - -def test_n1_some_error_code(): - ret = main(( - 'n1', sys.executable, '-c', - 'import sys; raise SystemExit(sys.argv[1] == "error")', - '--', - 'ok', 'error', 'ok', - )) - assert ret == 1 diff --git a/tests/lang_base_test.py b/tests/lang_base_test.py index 9fac83da..da289aef 100644 --- a/tests/lang_base_test.py +++ b/tests/lang_base_test.py @@ -164,15 +164,3 @@ def test_basic_run_hook(tmp_path): assert ret == 0 out = out.replace(b'\r\n', b'\n') assert out == b'hi hello file file file\n' - - -def test_hook_cmd(): - assert lang_base.hook_cmd('echo hi', ()) == ('echo', 'hi') - - -def test_hook_cmd_hazmat(): - ret = lang_base.hook_cmd('pre-commit hazmat cd a echo -- b', ()) - assert ret == ( - sys.executable, '-m', 'pre_commit.commands.hazmat', - 'cd', 'a', 'echo', '--', 'b', - ) diff --git a/tests/languages/python_test.py b/tests/languages/python_test.py index 593634b7..565525a4 100644 --- a/tests/languages/python_test.py +++ b/tests/languages/python_test.py @@ -10,8 +10,6 @@ import pre_commit.constants as C from pre_commit.envcontext import envcontext from pre_commit.languages import python from pre_commit.prefix import Prefix -from pre_commit.store import _make_local_repo -from pre_commit.util import cmd_output_b from pre_commit.util import make_executable from pre_commit.util import win_exe from testing.auto_namedtuple import auto_namedtuple @@ -353,15 +351,3 @@ def test_python_hook_weird_setup_cfg(tmp_path): ret = run_language(tmp_path, python, 'socks', [os.devnull]) assert ret == (0, f'[{os.devnull!r}]\nhello hello\n'.encode()) - - -def test_local_repo_with_other_artifacts(tmp_path): - cmd_output_b('git', 'init', tmp_path) - _make_local_repo(str(tmp_path)) - # pretend a rust install also ran here - tmp_path.joinpath('target').mkdir() - - ret, out = run_language(tmp_path, python, 'python --version') - - assert ret == 0 - assert out.startswith(b'Python ') diff --git a/tests/main_test.py b/tests/main_test.py index fed085fc..945349fa 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -1,7 +1,6 @@ from __future__ import annotations import argparse -import contextlib import os.path from unittest import mock @@ -9,7 +8,6 @@ import pytest import pre_commit.constants as C from pre_commit import main -from pre_commit.commands import hazmat from pre_commit.errors import FatalError from pre_commit.util import cmd_output from testing.auto_namedtuple import auto_namedtuple @@ -99,9 +97,11 @@ CMDS = tuple(fn.replace('_', '-') for fn in FNS) @pytest.fixture def mock_commands(): - with contextlib.ExitStack() as ctx: - mcks = {f: ctx.enter_context(mock.patch.object(main, f)) for f in FNS} - yield auto_namedtuple(**mcks) + mcks = {fn: mock.patch.object(main, fn).start() for fn in FNS} + ret = auto_namedtuple(**mcks) + yield ret + for mck in ret: + mck.stop() @pytest.fixture @@ -158,17 +158,6 @@ def test_all_cmds(command, mock_commands, mock_store_dir): assert_only_one_mock_called(mock_commands) -def test_hazmat(mock_store_dir): - with mock.patch.object(hazmat, 'impl') as mck: - main.main(('hazmat', 'cd', 'subdir', '--', 'cmd', '--', 'f1', 'f2')) - assert mck.call_count == 1 - (arg,), dct = mck.call_args - assert dct == {} - assert arg.tool == 'cd' - assert arg.subdir == 'subdir' - assert arg.cmd == ['cmd', '--', 'f1', 'f2'] - - def test_try_repo(mock_store_dir): with mock.patch.object(main, 'try_repo') as patch: main.main(('try-repo', '.')) diff --git a/tests/repository_test.py b/tests/repository_test.py index 5d71c3e4..b1c7a002 100644 --- a/tests/repository_test.py +++ b/tests/repository_test.py @@ -506,14 +506,3 @@ def test_args_with_spaces_and_quotes(tmp_path): expected = b"['i have spaces', 'and\"\\'quotes', '$and !this']\n" assert ret == (0, expected) - - -def test_hazmat(tmp_path): - ret = run_language( - tmp_path, unsupported, - f'pre-commit hazmat ignore-exit-code {shlex.quote(sys.executable)} ' - f"-c 'import sys; raise SystemExit(sys.argv[1:])'", - ('f1', 'f2'), - ) - expected = b"['f1', 'f2']\n" - assert ret == (0, expected) diff --git a/tests/store_test.py b/tests/store_test.py index 13f198ea..7d4dffb0 100644 --- a/tests/store_test.py +++ b/tests/store_test.py @@ -22,17 +22,6 @@ from testing.util import git_commit from testing.util import xfailif_windows -def _select_all_configs(store: Store) -> list[str]: - with store.connect() as db: - rows = db.execute('SELECT * FROM configs').fetchall() - return [path for path, in rows] - - -def _select_all_repos(store: Store) -> list[tuple[str, str, str]]: - with store.connect() as db: - return db.execute('SELECT repo, ref, path FROM repos').fetchall() - - def test_our_session_fixture_works(): """There's a session fixture which makes `Store` invariantly raise to prevent writing to the home directory. @@ -102,7 +91,7 @@ def test_clone(store, tempdir_factory, caplog): assert git.head_rev(ret) == rev # Assert there's an entry in the sqlite db for this - assert _select_all_repos(store) == [(path, rev, ret)] + assert store.select_all_repos() == [(path, rev, ret)] def test_warning_for_deprecated_stages_on_init(store, tempdir_factory, caplog): @@ -228,7 +217,7 @@ def test_clone_shallow_failure_fallback_to_complete( assert git.head_rev(ret) == rev # Assert there's an entry in the sqlite db for this - assert _select_all_repos(store) == [(path, rev, ret)] + assert store.select_all_repos() == [(path, rev, ret)] def test_clone_tag_not_on_mainline(store, tempdir_factory): @@ -276,7 +265,7 @@ def test_mark_config_as_used(store, tmpdir): with tmpdir.as_cwd(): f = tmpdir.join('f').ensure() store.mark_config_used('f') - assert _select_all_configs(store) == [f.strpath] + assert store.select_all_configs() == [f.strpath] def test_mark_config_as_used_idempotent(store, tmpdir): @@ -286,12 +275,21 @@ def test_mark_config_as_used_idempotent(store, tmpdir): def test_mark_config_as_used_does_not_exist(store): store.mark_config_used('f') - assert _select_all_configs(store) == [] + assert store.select_all_configs() == [] + + +def _simulate_pre_1_14_0(store): + with store.connect() as db: + db.executescript('DROP TABLE configs') + + +def test_select_all_configs_roll_forward(store): + _simulate_pre_1_14_0(store) + assert store.select_all_configs() == [] def test_mark_config_as_used_roll_forward(store, tmpdir): - with store.connect() as db: # simulate pre-1.14.0 - db.executescript('DROP TABLE configs') + _simulate_pre_1_14_0(store) test_mark_config_as_used(store, tmpdir) @@ -316,7 +314,7 @@ def test_mark_config_as_used_readonly(tmpdir): assert store.readonly # should be skipped due to readonly store.mark_config_used(str(cfg)) - assert _select_all_configs(store) == [] + assert store.select_all_configs() == [] def test_clone_with_recursive_submodules(store, tmp_path):