From 958233330932de008df181d514b93625f1d2affd Mon Sep 17 00:00:00 2001 From: Jon Zeolla Date: Fri, 11 Oct 2024 20:28:07 +0300 Subject: [PATCH] feat(autoupdate): add tags prefix matching --- pre_commit/commands/autoupdate.py | 26 ++++++++++--- pre_commit/main.py | 10 ++++- tests/commands/autoupdate_test.py | 65 +++++++++++++++++++++++++++++++ tests/main_test.py | 22 +++++++++++ 4 files changed, 116 insertions(+), 7 deletions(-) diff --git a/pre_commit/commands/autoupdate.py b/pre_commit/commands/autoupdate.py index aa0c5e25..09ed6e45 100644 --- a/pre_commit/commands/autoupdate.py +++ b/pre_commit/commands/autoupdate.py @@ -35,15 +35,19 @@ class RevInfo(NamedTuple): def from_config(cls, config: dict[str, Any]) -> RevInfo: return cls(config['repo'], config['rev']) - def update(self, tags_only: bool, freeze: bool) -> RevInfo: + def update( + self, tags_only: bool, freeze: bool, tags_prefix: str = '*', + ) -> RevInfo: with tempfile.TemporaryDirectory() as tmp: _git = ('git', *git.NO_FS_MONITOR, '-C', tmp) if tags_only: - tag_opt = '--abbrev=0' + tag_opts = ['--abbrev=0'] + if tags_prefix: + tag_opts.append(f'--match={tags_prefix}*') else: - tag_opt = '--exact' - tag_cmd = (*_git, 'describe', 'FETCH_HEAD', '--tags', tag_opt) + tag_opts = ['--exact'] + tag_cmd = (*_git, 'describe', 'FETCH_HEAD', '--tags', *tag_opts) git.init_repo(tmp, self.repo) cmd_output_b(*_git, 'config', 'extensions.partialClone', 'true') @@ -106,9 +110,14 @@ def _update_one( *, tags_only: bool, freeze: bool, + tags_prefix: str, ) -> tuple[int, RevInfo, RevInfo]: old = RevInfo.from_config(repo) - new = old.update(tags_only=tags_only, freeze=freeze) + new = old.update( + tags_only=tags_only, + freeze=freeze, + tags_prefix=tags_prefix, + ) _check_hooks_still_exist_at_rev(repo, new) return i, old, new @@ -163,6 +172,7 @@ def autoupdate( config_file: str, tags_only: bool, freeze: bool, + tags_prefix: str = '*', repos: Sequence[str] = (), jobs: int = 1, ) -> int: @@ -184,7 +194,11 @@ def autoupdate( futures = [ exe.submit( _update_one, - i, repo, tags_only=tags_only, freeze=freeze, + i, + repo, + tags_only=tags_only, + freeze=freeze, + tags_prefix=tags_prefix, ) for i, repo in enumerate(config_repos) if not repos or repo['repo'] in repos diff --git a/pre_commit/main.py b/pre_commit/main.py index 559c927c..2e2ddd4f 100644 --- a/pre_commit/main.py +++ b/pre_commit/main.py @@ -217,13 +217,20 @@ def main(argv: Sequence[str] | None = None) -> int: help="Auto-update pre-commit config to the latest repos' versions.", ) _add_config_option(autoupdate_parser) - autoupdate_parser.add_argument( + + autoupdate_group = autoupdate_parser.add_mutually_exclusive_group() + autoupdate_group.add_argument( '--bleeding-edge', action='store_true', help=( 'Update to the bleeding edge of `HEAD` instead of the latest ' 'tagged version (the default behavior).' ), ) + autoupdate_group.add_argument( + '--tags-prefix', + help='Filter tags based on the provided prefix.', + ) + autoupdate_parser.add_argument( '--freeze', action='store_true', help='Store "frozen" hashes in `rev` instead of tag names', @@ -380,6 +387,7 @@ def main(argv: Sequence[str] | None = None) -> int: freeze=args.freeze, repos=args.repos, jobs=args.jobs, + tags_prefix=args.tags_prefix, ) elif args.command == 'clean': return clean(store) diff --git a/tests/commands/autoupdate_test.py b/tests/commands/autoupdate_test.py index 71bd0444..ff181268 100644 --- a/tests/commands/autoupdate_test.py +++ b/tests/commands/autoupdate_test.py @@ -406,6 +406,71 @@ def test_autoupdate_tags_only(tagged, in_tmpdir): assert 'v1.2.3' in f.read() +def test_autoupdate_tags_only_with_v_tag_prefix(tagged, in_tmpdir): + # add some commits after the tag + git_commit(cwd=tagged.path) + + config = make_config_from_repo(tagged.path, rev=tagged.original_rev) + write_config('.', config) + + assert autoupdate( + C.CONFIG_FILE, + freeze=False, + tags_only=True, + tags_prefix='v', + ) == 0 + with open(C.CONFIG_FILE) as f: + assert 'v1.2.3' in f.read() + + +def test_autoupdate_tags_only_with_empty_tag_prefix(tagged, in_tmpdir): + # add some commits after the tag + git_commit(cwd=tagged.path) + + config = make_config_from_repo(tagged.path, rev=tagged.original_rev) + write_config('.', config) + + assert autoupdate( + C.CONFIG_FILE, + freeze=False, + tags_only=True, + tags_prefix='', + ) == 0 + with open(C.CONFIG_FILE) as f: + assert 'v1.2.3' in f.read() + + +def test_autoupdate_tags_only_and_freeze_with_v_tag_prefix(tagged, in_tmpdir): + # add some commits after the tag + git_commit(cwd=tagged.path) + + config = make_config_from_repo(tagged.path, rev=tagged.original_rev) + write_config('.', config) + + info = RevInfo.from_config(config) + new_info = info.update(tags_only=True, freeze=True, tags_prefix='v') + assert new_info.rev == tagged.head_rev + assert new_info.frozen == 'v1.2.3' + + +def test_autoupdate_tags_only_with_invalid_tag_prefix(tagged, in_tmpdir): + # add some commits after the tag + git_commit(cwd=tagged.path) + + config = make_config_from_repo(tagged.path, rev=tagged.original_rev) + write_config('.', config) + + assert autoupdate( + C.CONFIG_FILE, + freeze=False, + tags_only=True, + tags_prefix='thisisinvalid', + ) == 0 + with open(C.CONFIG_FILE) as f: + # This will be the same as tags_only=False + assert 'v1.2.3' not in f.read() + + def test_autoupdate_latest_no_config(out_of_date, in_tmpdir): config = make_config_from_repo( out_of_date.path, rev=out_of_date.original_rev, diff --git a/tests/main_test.py b/tests/main_test.py index 945349fa..e994dfa1 100644 --- a/tests/main_test.py +++ b/tests/main_test.py @@ -222,3 +222,25 @@ def test_hook_stage_migration(mock_store_dir): with mock.patch.object(main, 'run') as mck: main.main(('run', '--hook-stage', 'commit')) assert mck.call_args[0][2].hook_stage == 'pre-commit' + + +def test_expected_mutually_exclusive(capsys, mock_store_dir): + args = ( + 'autoupdate', + '--tags-prefix', + 'abc123', + '--bleeding-edge', + ) + with pytest.raises(SystemExit) as excinfo: + main.main(args) + + captured = capsys.readouterr() + cap_out_lines = captured.err.splitlines() # Get the error output in lines + # cap_out_lines = cap_out.get().splitlines() + + assert ( + cap_out_lines[-1] == + 'pre-commit autoupdate: error: argument --bleeding-edge: ' + 'not allowed with argument --tags-prefix' + ) + assert excinfo.value.code == 2