feat(pack): allow running update() without Internet connection

Problem: There is now way to run `update()` without Internet connection
  while there are some workflows that do not require it. Like "switch
  plugin version" and "revert latest update".

Solution: Add `opts.offline` to `update()`. This also allows now to
  treat `vim.pack.update(nil, { offline = true })` as a way to
  interactively explore currently installed plugins.
This commit is contained in:
Evgeni Chasnovski
2025-12-26 20:05:12 +02:00
parent 8c28507fcf
commit c9965491d0
4 changed files with 49 additions and 8 deletions

View File

@@ -288,12 +288,18 @@ Another approach is to utilize Git's `insteadOf` configuration:
These sources will be used verbatim in |vim.pack-lockfile|, so reusing the These sources will be used verbatim in |vim.pack-lockfile|, so reusing the
config on different machine will require the same Git configuration. config on different machine will require the same Git configuration.
Explore installed plugins ~
• `vim.pack.update(nil, { offline = true })`
• Navigate between plugins with `[[` and `]]`. List them with `gO`
(|vim.lsp.buf.document_symbol()|).
Switch plugin's version and/or source ~ Switch plugin's version and/or source ~
• Update 'init.lua' for plugin to have desired `version` and/or `src`. Let's • Update 'init.lua' for plugin to have desired `version` and/or `src`. Let's
say, the switch is for plugin named 'plugin1'. say, the switch is for plugin named 'plugin1'.
• |:restart|. The plugin's state on disk (revision and/or tracked source) is • |:restart|. The plugin's state on disk (revision and/or tracked source) is
not yet changed. Only plugin's `version` in |vim.pack-lockfile| is updated. not yet changed. Only plugin's `version` in |vim.pack-lockfile| is updated.
• Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated. • Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated. If
only switching version, use `{ offline = true }` option table.
• Review changes and either confirm or discard them. If discarded, revert • Review changes and either confirm or discard them. If discarded, revert
`version` change in 'init.lua' as well or you will be prompted again next `version` change in 'init.lua' as well or you will be prompted again next
time you run |vim.pack.update()|. time you run |vim.pack.update()|.
@@ -315,7 +321,8 @@ Revert plugin after an update ~
locate the revisions before the latest update, and (carefully) adjust locate the revisions before the latest update, and (carefully) adjust
current lockfile to have those revisions. current lockfile to have those revisions.
• |:restart|. • |:restart|.
• `vim.pack.update({ 'plugin' }, { target = 'lockfile' })`. Read and confirm. • `vim.pack.update({ 'plugin' }, { offline = true, target = 'lockfile' })`.
Read and confirm.
Synchronize config across machines ~ Synchronize config across machines ~
• On main machine: • On main machine:
@@ -491,6 +498,8 @@ update({names}, {opts}) *vim.pack.update()*
• {opts} (`table?`) A table with the following fields: • {opts} (`table?`) A table with the following fields:
• {force}? (`boolean`) Whether to skip confirmation and make • {force}? (`boolean`) Whether to skip confirmation and make
updates immediately. Default `false`. updates immediately. Default `false`.
• {offline}? (`boolean`) Whether to skip downloading new
updates. Default: `false`.
• {target}? (`string`) How to compute a new plugin revision. • {target}? (`string`) How to compute a new plugin revision.
One of: One of:
• "version" (default) - use latest revision matching • "version" (default) - use latest revision matching

View File

@@ -88,6 +88,12 @@
--- These sources will be used verbatim in |vim.pack-lockfile|, so reusing --- These sources will be used verbatim in |vim.pack-lockfile|, so reusing
--- the config on different machine will require the same Git configuration. --- the config on different machine will require the same Git configuration.
--- ---
---Explore installed plugins ~
---
---- `vim.pack.update(nil, { offline = true })`
---- Navigate between plugins with `[[` and `]]`. List them with `gO`
--- (|vim.lsp.buf.document_symbol()|).
---
---Switch plugin's version and/or source ~ ---Switch plugin's version and/or source ~
--- ---
---- Update 'init.lua' for plugin to have desired `version` and/or `src`. ---- Update 'init.lua' for plugin to have desired `version` and/or `src`.
@@ -95,6 +101,7 @@
---- |:restart|. The plugin's state on disk (revision and/or tracked source) ---- |:restart|. The plugin's state on disk (revision and/or tracked source)
--- is not yet changed. Only plugin's `version` in |vim.pack-lockfile| is updated. --- is not yet changed. Only plugin's `version` in |vim.pack-lockfile| is updated.
---- Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated. ---- Execute `vim.pack.update({ 'plugin1' })`. The plugin's source is updated.
--- If only switching version, use `{ offline = true }` option table.
---- Review changes and either confirm or discard them. If discarded, revert ---- Review changes and either confirm or discard them. If discarded, revert
--- `version` change in 'init.lua' as well or you will be prompted again next time --- `version` change in 'init.lua' as well or you will be prompted again next time
--- you run |vim.pack.update()|. --- you run |vim.pack.update()|.
@@ -119,7 +126,8 @@
--- locate the revisions before the latest update, and (carefully) adjust --- locate the revisions before the latest update, and (carefully) adjust
--- current lockfile to have those revisions. --- current lockfile to have those revisions.
---- |:restart|. ---- |:restart|.
---- `vim.pack.update({ 'plugin' }, { target = 'lockfile' })`. Read and confirm. ---- `vim.pack.update({ 'plugin' }, { offline = true, target = 'lockfile' })`.
--- Read and confirm.
--- ---
---Synchronize config across machines ~ ---Synchronize config across machines ~
--- ---
@@ -1152,6 +1160,8 @@ end
--- @inlinedoc --- @inlinedoc
--- @field force? boolean Whether to skip confirmation and make updates immediately. Default `false`. --- @field force? boolean Whether to skip confirmation and make updates immediately. Default `false`.
--- ---
--- @field offline? boolean Whether to skip downloading new updates. Default: `false`.
---
--- How to compute a new plugin revision. One of: --- How to compute a new plugin revision. One of:
--- - "version" (default) - use latest revision matching `version` from plugin specification. --- - "version" (default) - use latest revision matching `version` from plugin specification.
--- - "lockfile" - use revision from the lockfile. Useful for reverting or performing controlled --- - "lockfile" - use revision from the lockfile. Useful for reverting or performing controlled
@@ -1191,7 +1201,7 @@ end
--- @param opts? vim.pack.keyset.update --- @param opts? vim.pack.keyset.update
function M.update(names, opts) function M.update(names, opts)
vim.validate('names', names, vim.islist, true, 'list') vim.validate('names', names, vim.islist, true, 'list')
opts = vim.tbl_extend('force', { force = false, target = 'version' }, opts or {}) opts = vim.tbl_extend('force', { force = false, offline = false, target = 'version' }, opts or {})
local plug_list = plug_list_from_names(names) local plug_list = plug_list_from_names(names)
if #plug_list == 0 then if #plug_list == 0 then
@@ -1217,7 +1227,7 @@ function M.update(names, opts)
end end
-- Fetch -- Fetch
if not opts._offline then if not opts.offline then
-- Using '--tags --force' means conflicting tags will be synced with remote -- Using '--tags --force' means conflicting tags will be synced with remote
local args = { 'fetch', '--quiet', '--tags', '--force', '--recurse-submodules=yes', 'origin' } local args = { 'fetch', '--quiet', '--tags', '--force', '--recurse-submodules=yes', 'origin' }
git_cmd(args, p.path) git_cmd(args, p.path)
@@ -1237,8 +1247,8 @@ function M.update(names, opts)
trigger_event(p, 'PackChanged', 'update') trigger_event(p, 'PackChanged', 'update')
end end
end end
local progress_title = opts.force and (opts._offline and 'Applying updates' or 'Updating') local progress_title = opts.force and (opts.offline and 'Applying updates' or 'Updating')
or 'Downloading updates' or (opts.offline and 'Computing updates' or 'Downloading updates')
run_list(plug_list, do_update, progress_title) run_list(plug_list, do_update, progress_title)
if needs_lock_write then if needs_lock_write then

View File

@@ -157,7 +157,7 @@ end
local commands = { local commands = {
update_plugin = function(plug_data) update_plugin = function(plug_data)
vim.pack.update({ plug_data.name }, { force = true, _offline = true }) vim.pack.update({ plug_data.name }, { force = true, offline = true })
end, end,
skip_update_plugin = function(_) end, skip_update_plugin = function(_) end,
delete_plugin = function(plug_data) delete_plugin = function(plug_data)

View File

@@ -1765,6 +1765,28 @@ describe('vim.pack', function()
assert_origin(basic_src) assert_origin(basic_src)
end) end)
it('can do offline update', function()
local defbranch_path = pack_get_plug_path('defbranch')
local defbranch_lua_file = vim.fs.joinpath(defbranch_path, 'lua', 'defbranch.lua')
n.exec_lua(function()
vim.pack.add({ { src = repos_src.defbranch, version = 'main' } })
end)
track_nvim_echo()
eq('return "defbranch dev"', fn.readblob(defbranch_lua_file))
n.exec_lua(function()
vim.pack.update({ 'defbranch' }, { offline = true })
end)
-- There should be no progress report about downloading updates
assert_progress_report('Computing updates', { 'defbranch' })
n.exec('write')
eq('return "defbranch main"', fn.readblob(defbranch_lua_file))
end)
it('shows progress report', function() it('shows progress report', function()
track_nvim_echo() track_nvim_echo()
exec_lua(function() exec_lua(function()