Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}

Expand Down
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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 || { \
Expand Down
17 changes: 0 additions & 17 deletions autoload/plugin_manager/async.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions autoload/plugin_manager/cmd/remove.vim
Original file line number Diff line number Diff line change
Expand Up @@ -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': ''}
Expand Down
60 changes: 60 additions & 0 deletions tests/quality.vader
Original file line number Diff line number Diff line change
@@ -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
Loading