From a39171f5327e44162aebd17f16d4cb7ffddef1df Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 13 Nov 2025 15:49:13 +0200 Subject: fix(pack)!: make default `opts.load` in `add()` to work inside 'plugin/' Problem: Plain `vim.pack.add()` calls (with default `opts.load`) does not fully work if called inside 'plugin/' runtime directory. In particular, 'plugin/' files of newly added plugins are not sourced. This is because `opts.load` is `false` during the whole startup, which means `:packadd!` is used (modify 'runtimepath' but not force source newly added 'plugin/' files). This use case is common due to users organizing their config as separate files in '~/.config/nvim/plugin/'. Solution: Use newly added `v:vim_did_init` to decide default `opts.load` value instead of `v:vim_did_enter`. --- runtime/lua/vim/pack.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index 6180f23db5..265a366d48 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -743,8 +743,9 @@ local function pack_add(plug, load) -- NOTE: The `:packadd` specifically seems to not handle spaces in dir name vim.cmd.packadd({ vim.fn.escape(plug.spec.name, ' '), bang = not load, magic = { file = false } }) - -- Execute 'after/' scripts if not during startup (when they will be sourced - -- automatically), as `:packadd` only sources plain 'plugin/' files. + -- The `:packadd` only sources plain 'plugin/' files. Execute 'after/' scripts + -- if not during startup (when they will be sourced later, even if + -- `vim.pack.add` is inside user's 'plugin/') -- See https://github.com/vim/vim/issues/15584 -- Deliberately do so after executing all currently known 'plugin/' files. if vim.v.vim_did_enter == 1 and load then @@ -760,7 +761,7 @@ end --- @inlinedoc --- Load `plugin/` files and `ftdetect/` scripts. If `false`, works like `:packadd!`. --- If function, called with plugin data and is fully responsible for loading plugin. ---- Default `false` during startup and `true` afterwards. +--- Default `false` during |init.lua| sourcing and `true` afterwards. --- @field load? boolean|fun(plug_data: {spec: vim.pack.Spec, path: string}) --- --- @field confirm? boolean Whether to ask user to confirm initial install. Default `true`. @@ -788,7 +789,7 @@ end --- @param opts? vim.pack.keyset.add function M.add(specs, opts) vim.validate('specs', specs, vim.islist, false, 'list') - opts = vim.tbl_extend('force', { load = vim.v.vim_did_enter == 1, confirm = true }, opts or {}) + opts = vim.tbl_extend('force', { load = vim.v.vim_did_init == 1, confirm = true }, opts or {}) vim.validate('opts', opts, 'table') local plug_dir = get_plug_dir() -- cgit From 2e533e364e2d52bcfe531d8ddd4a148d3f3bfed1 Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 13 Nov 2025 15:49:16 +0200 Subject: feat(pack): update `add()` to handle source change for installed plugin Problem: Changing `src` of an existing plugin cleanly requires manual `vim.pack.del()` prior to executing `vim.pack.add()` with a new `src`. Solution: Autodetect `src` change for an existing plugin (by comparing against lockfile data). If different - properly delete immediately and treat this as new plugin installation. Alternative solution might be to update `origin` remote in the installed plugin after calling `vim.pack.update()`. Although, doable, this 1) requires more code; and 2) works only for Git plugins (which might be not the only type of plugins in the future). Automatic "delete and clean install" feels more robust. --- runtime/lua/vim/pack.lua | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index 265a366d48..793d712479 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -77,6 +77,10 @@ ---any changes in 'init.lua' as well or you will be prompted again next time ---you run |vim.pack.update()|. --- +---Switch plugin's source: +---- Update 'init.lua' for plugin to have desired `src`. +---- |:restart|. This will cleanly reinstall plugin from the new source. +--- ---Freeze plugin from being updated: ---- Update 'init.lua' for plugin to have `version` set to current revision. ---Get it from |vim.pack-lockfile| (plugin's field `rev`; looks like `abc12345`). @@ -769,9 +773,11 @@ end --- Add plugin to current session --- --- - For each specification check that plugin exists on disk in |vim.pack-directory|: ---- - If exists, do nothing in this step. +--- - If exists, check if its `src` is the same as input. If not - delete +--- 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 `git clone`) and update revision to follow `version` (via `git checkout`). +--- subdirectory (via partial blobless `git clone`) and update revision +--- to match `version` (via `git checkout`). --- - For each plugin execute |:packadd| (or customizable `load` function) making --- it reachable by Nvim. --- @@ -804,13 +810,19 @@ function M.add(specs, opts) local plugs_to_install = {} --- @type vim.pack.Plug[] local needs_lock_write = false for _, p in ipairs(plugs) do - -- TODO(echasnovski): check that lock's `src` is the same as in spec. - -- If not - cleanly reclone (delete directory and mark as not installed). + -- Allow to cleanly change `src` of an already installed plugin + if p.info.installed and plugin_lock.plugins[p.spec.name].src ~= p.spec.src then + M.del({ p.spec.name }) + p.info.installed = false + end + + -- Detect `version` change local p_lock = plugin_lock.plugins[p.spec.name] or {} needs_lock_write = needs_lock_write or p_lock.version ~= p.spec.version p_lock.version = p.spec.version plugin_lock.plugins[p.spec.name] = p_lock + -- Register for install if not p.info.installed then plugs_to_install[#plugs_to_install + 1] = p needs_lock_write = true -- cgit From 18e6ba90e25327dd8b34960902f3433cf00259dc Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Thu, 13 Nov 2025 15:49:17 +0200 Subject: docs(pack): add example workflow of how to revert after a bad update Problem: No example workflow of how to revert after a bad update. Solution: Add example workflow of how to revert after a bad update. In future this might be improved by utilizing other `vim.pack` features or via a dedicated function (like `vim.pack.restore()` that restores all installed plugins to a state from the lockfile). --- runtime/lua/vim/pack.lua | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index 793d712479..8e9693d0ff 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -91,6 +91,18 @@ ---you want it to be updated. ---- |:restart|. --- +---Revert plugin after an update: +---- Locate plugin's revision at working state. For example: +--- - If there is a previous version of |vim.pack-lockfile| (like from version +--- control history), use it to get plugin's `rev` field. +--- - If there is a log file ("nvim-pack.log" at "log" |stdpath()|), open it +--- and navigate to latest updates (at the bottom). Locate lines about plugin +--- update details and use revision from "State before". +---- Freeze plugin to target revision (set `version` and |:restart|). +---- Run `vim.pack.update({ 'plugin-name' }, { force = true })` to make plugin +--- state on disk follow target revision. |:restart|. +---- When ready to deal with updating plugin, unfreeze it. +--- ---Remove plugins from disk: ---- Use |vim.pack.del()| with a list of plugin names to remove. Make sure their specs ---are not included in |vim.pack.add()| call in 'init.lua' or they will be reinstalled. -- cgit From 474e9e9408e32bb8765a131d16111c7e0834348a Mon Sep 17 00:00:00 2001 From: Evgeni Chasnovski Date: Sun, 16 Nov 2025 22:21:23 +0200 Subject: fix(pack): show more informative error message if no `git` executable Problem: Relaxing minimal Git version did not fully preserve previous behavior in case there no `git` executable. Instead it showed the same error as if after `vim.system({ 'does_not_exist' })`. Solution: Show a more direct "No `git` executable" error message. --- runtime/lua/vim/pack.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'runtime/lua') diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua index 8e9693d0ff..9ef3d38b34 100644 --- a/runtime/lua/vim/pack.lua +++ b/runtime/lua/vim/pack.lua @@ -181,11 +181,11 @@ end local git_version = vim.version.parse('1') local function git_ensure_exec() - local sys_res = vim.system({ 'git', 'version' }):wait() - git_version = vim.version.parse(sys_res.stdout) - if sys_res.stderr ~= '' then + local ok, sys = pcall(vim.system, { 'git', 'version' }) + if not ok then error('No `git` executable') end + git_version = vim.version.parse(sys:wait().stdout) end --- @async -- cgit