From c3ac329c7ab1f57adb3c87b1bb60a3073da95e79 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 13 Nov 2025 15:45:52 +0200 Subject: [PATCH] fix(pack)!: ensure plugin is fully absent if not fully installed Problem: Currently it is possible to have plugin in a "partial install" state when `git clone` was successfull but `git checkout` was not. This was done to not checkout default branch by default in these situations (for security reasons). The problem is that it adds complexity when both dealing with lockfile (plugin's `rev` might be `nil`) and in how `src` and `version` are treated (wrong `src` - no plugin on disk; wrong `version` - "partial" plugin on disk). Solution: Treat plugin as "installed" if both `git clone` and `git checkout` are successful, while ensuring that not installed plugins are not on disk and in lockfile. This also means that if in 'init.lua' there is a `vim.pack.add()` with bad `version`, for first install there will be an informative error about it BUT next session will also try to install it. The solution is the same - adjust `version` beforehand. --- runtime/doc/pack.txt | 3 ++- runtime/lua/vim/pack.lua | 8 +++++--- test/functional/plugin/pack_spec.lua | 11 ++++------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/runtime/doc/pack.txt b/runtime/doc/pack.txt index aa3fdfb5fb..8ba0e3a68b 100644 --- a/runtime/doc/pack.txt +++ b/runtime/doc/pack.txt @@ -375,7 +375,8 @@ add({specs}, {opts}) *vim.pack.add()* nothing. • If doesn't exist, install it by downloading from `src` into `name` subdirectory (via partial blobless `git clone`) and update revision to - match `version` (via `git checkout`). + match `version` (via `git checkout`). Plugin will not be on disk if + any step resulted in an error. • For each plugin execute |:packadd| (or customizable `load` function) making it reachable by Nvim. diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index dfcc732d80..bb5b5640eb 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -677,7 +677,6 @@ local function install_list(plug_list, confirm) trigger_event(p, 'PackChangedPre', 'install') git_clone(p.spec.src, p.path) - p.info.installed = true plugin_lock.plugins[p.spec.name].src = p.spec.src @@ -685,6 +684,7 @@ local function install_list(plug_list, confirm) p.info.sha_target = (plugin_lock.plugins[p.spec.name] or {}).rev checkout(p, timestamp) + p.info.installed = true trigger_event(p, 'PackChanged', 'install') end @@ -694,10 +694,11 @@ local function install_list(plug_list, confirm) run_list(plug_list, do_install, 'Installing plugins') end - -- Ensure that not installed plugins are absent in lockfile + -- Ensure that not fully installed plugins are absent on disk and in lockfile for _, p in ipairs(plug_list) do if not p.info.installed then plugin_lock.plugins[p.spec.name] = nil + vim.fs.rm(p.path, { recursive = true, force = true }) end end end @@ -800,7 +801,8 @@ end --- immediately to clean install from the new source. Otherwise do nothing. --- - If doesn't exist, install it by downloading from `src` into `name` --- subdirectory (via partial blobless `git clone`) and update revision ---- to match `version` (via `git checkout`). +--- to match `version` (via `git checkout`). Plugin will not be on disk if +--- any step resulted in an error. --- - For each plugin execute |:packadd| (or customizable `load` function) making --- it reachable by Nvim. --- diff --git a/test/functional/plugin/pack_spec.lua b/test/functional/plugin/pack_spec.lua index e75f586d38..aad3dcddb5 100644 --- a/test/functional/plugin/pack_spec.lua +++ b/test/functional/plugin/pack_spec.lua @@ -575,10 +575,9 @@ describe('vim.pack', function() local pluginerr_hash = git_get_hash('main', 'pluginerr') local ref_lockfile = { - -- Should be no entry for `repo_not_exist` + -- Should be no entry for `repo_not_exist` and `basic` as they did not + -- fully install plugins = { - -- No `rev` because there was no relevant checkout - basic = { src = repos_src.basic, version = "'not-exist'" }, -- Error during sourcing 'plugin/' should not affect lockfile pluginerr = { rev = pluginerr_hash, src = repos_src.pluginerr, version = "'main'" }, }, @@ -605,14 +604,12 @@ describe('vim.pack', function() eq(false, vim.tbl_contains(rtp, after_dir)) end) - it('does not checkout on bad `version`', function() + it('does not install on bad `version`', function() local err = pcall_err(exec_lua, function() vim.pack.add({ { src = repos_src.basic, version = 'not-exist' } }) end) matches('`not%-exist` is not a branch/tag/commit', err) - local plug_path = pack_get_plug_path('basic') - local entries = vim.iter(vim.fs.dir(plug_path)):totable() - eq({ { '.git', 'directory' } }, entries) + eq(false, pack_exists('basic')) end) it('allows changing `src` of installed plugin', function()