From bf5abb341d776ee861667e75f51120bdfb60d3dc Mon Sep 17 00:00:00 2001 From: log0u7 <70974447+log0u7@users.noreply.github.com> Date: Sun, 20 Sep 2026 06:24:17 +0200 Subject: [PATCH] chore: drop dead on_complete, escape glob metachars, strict semver tags - plugin_manager#async#on_complete: zero call sites; the 2.2.4 sweep already claimed its removal, it survived - deleted now (issue #11) - remove: escape glob metachars in the fuzzy filesystem search so a metachar name can never match an unrelated plugin - release: validate tags with ^v[0-9]+\.[0-9]+\.[0-9]+$ in release.yml (new step before archive) and the Makefile tag target (was: ^v only) Closes #11 --- .github/workflows/release.yml | 3 ++ CHANGELOG.md | 17 ++++++++ Makefile | 4 +- autoload/plugin_manager/async.vim | 17 -------- autoload/plugin_manager/cmd/remove.vim | 6 ++- tests/quality.vader | 60 ++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 tests/quality.vader diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6de847c..dd3ec12 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,9 @@ jobs: - name: Install Make + Git run: sudo apt-get update && sudo apt-get install -y git make + - name: Validate tag format (strict semver) + run: '[[ "$GITHUB_REF_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::tag must be vX.Y.Z, got: $GITHUB_REF_NAME"; exit 1; }' + - name: Build archive run: make archive VERSION=${{ github.ref_name }} diff --git a/CHANGELOG.md b/CHANGELOG.md index f54a131..0aa3d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to the Vim Plugin Manager will be documented in this file. + +## [Unreleased] + +### Removed +- `plugin_manager#async#on_complete`: zero call sites. The 2.2.4 dead-code + sweep already claimed this removal; it survived. Deleted now. + +### Fixed +- `remove`: the fuzzy filesystem search escapes glob metachars in the user + name - `PluginManager remove 'X*'` no longer glob-matches (and removes) an + unrelated plugin; it resolves to MODULE_NOT_FOUND (#11). + +### Changed +- Release tags are validated with strict semver `^v[0-9]+.[0-9]+.[0-9]+$`: + a new "Validate tag format" step in release.yml rejects a malformed tag + before archiving, and the Makefile `tag` target enforces the same pattern + (was: any `^v`) (#11). ## [2.2.9] - 2026-09-20 ### Security diff --git a/Makefile b/Makefile index 81d29de..3b5fd21 100644 --- a/Makefile +++ b/Makefile @@ -113,8 +113,8 @@ tag: echo "Error: VERSION is required (e.g. make tag VERSION=v2.1.3)"; \ exit 1; \ fi - @echo "$(VERSION)" | grep -q '^v' || { \ - echo "Error: VERSION must start with v (e.g. v2.1.3)"; \ + @echo "$(VERSION)" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$$' || { \ + echo "Error: VERSION must be strict semver vX.Y.Z (e.g. v2.1.3)"; \ exit 1; \ } @git diff --quiet --exit-code || { \ diff --git a/autoload/plugin_manager/async.vim b/autoload/plugin_manager/async.vim index 016ef67..f99dfaa 100644 --- a/autoload/plugin_manager/async.vim +++ b/autoload/plugin_manager/async.vim @@ -207,23 +207,6 @@ endfunction " CALLBACKS AND HANDLERS " ------------------------------------------------------------------------------ -" Add a callback for when a job finishes -function! plugin_manager#async#on_complete(job_id, callback) abort - if !has_key(s:jobs, a:job_id) - " Standardized error handling - call plugin_manager#core#throw('async', 'INVALID_JOB_ID', 'Invalid job ID: ' . a:job_id) - endif - - let s:jobs[a:job_id].callback = a:callback - - " If the job is already finished, call the callback immediately - if s:jobs[a:job_id].finished - call s:process_job_completion(a:job_id) - endif - - return 1 -endfunction - " Clean up finished jobs older than a certain age. " Previously only jobs that had fired a callback were removed, leaking " finished callback-less jobs indefinitely. All finished jobs are now diff --git a/autoload/plugin_manager/cmd/remove.vim b/autoload/plugin_manager/cmd/remove.vim index 15919b2..9af006e 100644 --- a/autoload/plugin_manager/cmd/remove.vim +++ b/autoload/plugin_manager/cmd/remove.vim @@ -109,8 +109,10 @@ function! s:find_in_filesystem(name) abort endif " Fuzzy match: refuse if more than one candidate to avoid removing the - " wrong plugin. Even -f does not override this safety check. - let l:matches = glob(l:base_dir . '/*' . a:name . '*', 0, 1) + " wrong plugin. Even -f does not override this safety check. The user + " name is literal input: escape glob metachars so 'X*' can never match + " an unrelated plugin (issue #11). + let l:matches = glob(l:base_dir . '/*' . escape(a:name, '*?[]') . '*', 0, 1) if len(l:matches) == 1 let l:path = l:matches[0] return {'name': fnamemodify(l:path, ':t'), 'path': l:path, 'url': ''} diff --git a/tests/quality.vader b/tests/quality.vader new file mode 100644 index 0000000..ff5011a --- /dev/null +++ b/tests/quality.vader @@ -0,0 +1,60 @@ +" Quality regressions (issue #11): dead async#on_complete, glob metachars +" in the remove filesystem search, strict release tag validation. + +Before: + let g:_pm_q_saved = { + \ 'vim_dir': get(g:, 'plugin_manager_vim_dir', ''), + \ 'plugins_dir': get(g:, 'plugin_manager_plugins_dir', ''), + \ 'cwd': getcwd(), + \ 'logging': get(g:, 'plugin_manager_enable_logging', 0), + \ } + let g:_pm_q_vim = '/tmp/pm-q-test/vim' + let g:plugin_manager_vim_dir = g:_pm_q_vim + let g:plugin_manager_plugins_dir = g:_pm_q_vim . '/pack/plugins' + call delete('/tmp/pm-q-test', 'rf') + call mkdir(g:_pm_q_vim . '/pack/plugins/start', 'p') + let g:plugin_manager_enable_logging = 1 + let g:plugin_manager_test_force_sync = 1 + execute 'cd ' . fnameescape(g:_pm_q_vim) + call plugin_manager#git#refresh_modules_cache() + +After: + execute 'cd ' . fnameescape(g:_pm_q_saved.cwd) + call delete('/tmp/pm-q-test', 'rf') + let g:plugin_manager_vim_dir = g:_pm_q_saved.vim_dir + let g:plugin_manager_plugins_dir = g:_pm_q_saved.plugins_dir + let g:plugin_manager_enable_logging = g:_pm_q_saved.logging + unlet! g:plugin_manager_test_force_sync + unlet g:_pm_q_saved g:_pm_q_vim + call plugin_manager#git#refresh_modules_cache() + +Execute (async#on_complete no longer exists): + " Force the autoload so exists() sees the real state of async.vim. + call plugin_manager#async#supported() + Assert !exists('*plugin_manager#async#on_complete'), + \ 'async#on_complete is dead code (zero callers) and must be gone' + +Execute (glob metachars in a remove name must not match other plugins): + " Two directories; the search name 'X*' must NOT glob-match plainXplugin. + " remove#execute swallows the throw internally (handle_error): assert the + " log line and the untouched directories instead. + call mkdir(g:_pm_q_vim . '/pack/plugins/start/plainplugin', 'p') + call mkdir(g:_pm_q_vim . '/pack/plugins/start/plainXplugin', 'p') + let g:_pm_q_r = plugin_manager#cmd#remove#execute('X*', '-f') + AssertEqual 0, g:_pm_q_r, 'the metachar name must not remove anything' + Assert isdirectory(g:_pm_q_vim . '/pack/plugins/start/plainXplugin'), + \ 'the glob must not have matched an unrelated plugin' + Assert isdirectory(g:_pm_q_vim . '/pack/plugins/start/plainplugin'), + \ 'unrelated plugins must be untouched' + unlet g:_pm_q_r + +Execute (release tags must be strict semver): + " The exact pattern enforced in release.yml and the Makefile tag target. + let g:_pm_q_pat = '^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*$' + Assert 'v2.2.10' =~# g:_pm_q_pat + Assert 'v0.0.0' =~# g:_pm_q_pat + Assert 'v2.2' !~# g:_pm_q_pat, 'two components must be rejected' + Assert 'vX.Y.Z' !~# g:_pm_q_pat, 'non-numeric must be rejected' + Assert 'v2.2.10-beta' !~# g:_pm_q_pat, 'suffix must be rejected' + Assert '2.2.10' !~# g:_pm_q_pat, 'missing v prefix must be rejected' + unlet g:_pm_q_pat