feat(vim.pack): lockfile support #35827

This commit is contained in:
Justin M. Keyes
2025-10-04 12:48:29 -04:00
committed by GitHub
3 changed files with 366 additions and 68 deletions

View File

@@ -221,6 +221,13 @@ Uses Git to manage plugins and requires present `git` executable of at least
version 2.36. Target plugins should be Git repositories with versions as named
tags following semver convention `v<major>.<minor>.<patch>`.
The latest state of all managed plugins is stored inside a *vim.pack-lockfile*
located at `$XDG_CONFIG_HOME/nvim/nvim-pack-lock.json`. It is a JSON file that
is used to persistently track data about plugins. For a more robust config
treat lockfile like its part: put under version control, etc. In this case
initial install prefers revision from the lockfile instead of inferring from
`version`. Should not be edited by hand or deleted.
Example workflows ~
Basic install and management:
@@ -254,17 +261,20 @@ Basic install and management:
plugin1 = require('plugin1')
<
• Restart Nvim (for example, with |:restart|). Plugins that were not yet
installed will be available on disk in target state after `add()` call.
installed will be available on disk after `add()` call. Their revision is
taken from |vim.pack-lockfile| (if present) or inferred from the `version`.
• To update all plugins with new changes:
• Execute |vim.pack.update()|. This will download updates from source and
show confirmation buffer in a separate tabpage.
• Review changes. To confirm all updates execute |:write|. To discard
updates execute |:quit|.
• (Optionally) |:restart| to start using code from updated plugins.
Switch plugin's version:
• Update 'init.lua' for plugin to have desired `version`. Let's say, plugin
named 'plugin1' has changed to `vim.version.range('*')`.
• |:restart|. The plugin's actual state on disk is not yet changed.
• |:restart|. The plugin's actual state on disk is not yet changed. Only
plugin's `version` in |vim.pack-lockfile| is updated.
• Execute `vim.pack.update({ 'plugin1' })`.
• Review changes and either confirm or discard them. If discarded, revert any
changes in 'init.lua' as well or you will be prompted again next time you
@@ -272,7 +282,7 @@ Switch plugin's version:
Freeze plugin from being updated:
• Update 'init.lua' for plugin to have `version` set to current revision. Get
it with `:=vim.pack.get({ 'plug-name' })[1].rev` (looks like `abc12345`).
it from |vim.pack-lockfile| (plugin's field `rev`; looks like `abc12345`).
• |:restart|.
Unfreeze plugin to start receiving updates:
@@ -371,7 +381,7 @@ get({names}, {opts}) *vim.pack.get()*
• {branches}? (`string[]`) Available Git branches (first is default).
Missing if `info=false`.
• {path} (`string`) Plugin's path on disk.
• {rev}? (`string`) Current Git revision. Missing if `info=false`.
• {rev} (`string`) Current Git revision.
• {spec} (`vim.pack.SpecResolved`) A |vim.pack.Spec| with resolved
`name`.
• {tags}? (`string[]`) Available Git tags. Missing if `info=false`.
@@ -402,8 +412,7 @@ update({names}, {opts}) *vim.pack.update()*
Parameters: ~
• {names} (`string[]?`) List of plugin names to update. Must be managed
by |vim.pack|, not necessarily already added to current
session. Default: names of all plugins added to current
session via |vim.pack.add()|.
session. Default: names of all plugins managed by |vim.pack|.
• {opts} (`table?`) A table with the following fields:
• {force}? (`boolean`) Whether to skip confirmation and make
updates immediately. Default `false`.

View File

@@ -14,6 +14,13 @@
---least version 2.36. Target plugins should be Git repositories with versions
---as named tags following semver convention `v<major>.<minor>.<patch>`.
---
---The latest state of all managed plugins is stored inside a [vim.pack-lockfile]()
---located at `$XDG_CONFIG_HOME/nvim/nvim-pack-lock.json`. It is a JSON file that
---is used to persistently track data about plugins.
---For a more robust config treat lockfile like its part: put under version control, etc.
---In this case initial install prefers revision from the lockfile instead of
---inferring from `version`. Should not be edited by hand or deleted.
---
---Example workflows ~
---
---Basic install and management:
@@ -50,18 +57,21 @@
---```
---
---- Restart Nvim (for example, with |:restart|). Plugins that were not yet
---installed will be available on disk in target state after `add()` call.
---installed will be available on disk after `add()` call. Their revision is
---taken from |vim.pack-lockfile| (if present) or inferred from the `version`.
---
---- To update all plugins with new changes:
--- - Execute |vim.pack.update()|. This will download updates from source and
--- show confirmation buffer in a separate tabpage.
--- - Review changes. To confirm all updates execute |:write|.
--- To discard updates execute |:quit|.
--- - (Optionally) |:restart| to start using code from updated plugins.
---
---Switch plugin's version:
---- Update 'init.lua' for plugin to have desired `version`. Let's say, plugin
---named 'plugin1' has changed to `vim.version.range('*')`.
---- |:restart|. The plugin's actual state on disk is not yet changed.
--- Only plugin's `version` in |vim.pack-lockfile| is updated.
---- Execute `vim.pack.update({ 'plugin1' })`.
---- Review changes and either confirm or discard them. If discarded, revert
---any changes in 'init.lua' as well or you will be prompted again next time
@@ -69,7 +79,7 @@
---
---Freeze plugin from being updated:
---- Update 'init.lua' for plugin to have `version` set to current revision.
---Get it with `:=vim.pack.get({ 'plug-name' })[1].rev` (looks like `abc12345`).
---Get it from |vim.pack-lockfile| (plugin's field `rev`; looks like `abc12345`).
---- |:restart|.
---
---Unfreeze plugin to start receiving updates:
@@ -190,13 +200,72 @@ local function git_get_tags(cwd)
return tags == '' and {} or vim.split(tags, '\n')
end
-- Plugin operations ----------------------------------------------------------
-- Lockfile -------------------------------------------------------------------
--- @return string
local function get_plug_dir()
return vim.fs.joinpath(vim.fn.stdpath('data'), 'site', 'pack', 'core', 'opt')
end
--- @class (private) vim.pack.LockData
--- @field rev string Latest recorded revision.
--- @field src string Plugin source.
--- @field version? string|vim.VersionRange Plugin `version`, as supplied in `spec`.
--- @class (private) vim.pack.Lock
--- @field plugins table<string, vim.pack.LockData> Map from plugin name to its lock data.
--- @type vim.pack.Lock
local plugin_lock
local function lock_get_path()
return vim.fs.joinpath(vim.fn.stdpath('config'), 'nvim-pack-lock.json')
end
local function lock_read()
if plugin_lock then
return
end
local fd = uv.fs_open(lock_get_path(), 'r', 438)
if not fd then
plugin_lock = { plugins = {} }
return
end
local stat = assert(uv.fs_fstat(fd))
local data = assert(uv.fs_read(fd, stat.size, 0))
assert(uv.fs_close(fd))
plugin_lock = vim.json.decode(data) --- @type vim.pack.Lock
-- Deserialize `version`
for _, l_data in pairs(plugin_lock.plugins) do
local version = l_data.version
if type(version) == 'string' then
l_data.version = version:match("^'(.+)'$") or vim.version.range(version)
end
end
end
local function lock_write()
-- Serialize `version`
local lock = vim.deepcopy(plugin_lock)
for _, l_data in pairs(lock.plugins) do
local version = l_data.version
if version then
l_data.version = type(version) == 'string' and ("'%s'"):format(version) or tostring(version)
end
end
local path = lock_get_path()
vim.fn.mkdir(vim.fs.dirname(path), 'p')
local fd = assert(uv.fs_open(path, 'w', 438))
local data = vim.json.encode(lock, { indent = ' ', sort_keys = true })
assert(uv.fs_write(fd, data))
assert(uv.fs_close(fd))
end
-- Plugin operations ----------------------------------------------------------
--- @param msg string|string[]
--- @param level ('DEBUG'|'TRACE'|'INFO'|'WARN'|'ERROR')?
local function notify(msg, level)
@@ -334,15 +403,8 @@ local function plug_list_from_names(names)
local plug_dir = get_plug_dir()
local plugs = {} --- @type vim.pack.Plug[]
for _, p_data in ipairs(p_data_list) do
-- NOTE: By default include only active plugins (and not all on disk). Using
-- not active plugins might lead to a confusion as default `version` and
-- user's desired one might mismatch.
-- TODO(echasnovski): Change this when there is lockfile.
if names ~= nil or p_data.active then
plugs[#plugs + 1] = new_plug(p_data.spec, plug_dir)
end
plugs[#plugs + 1] = new_plug(p_data.spec, plug_dir)
end
return plugs
end
@@ -532,6 +594,8 @@ local function checkout(p, timestamp, skip_same_sha)
git_cmd({ 'checkout', '--quiet', p.info.sha_target }, p.path)
plugin_lock.plugins[p.spec.name].rev = p.info.sha_target
trigger_event(p, 'PackChanged', 'update')
-- (Re)Generate help tags according to the current help files.
@@ -561,6 +625,11 @@ local function install_list(plug_list, confirm)
git_clone(p.spec.src, p.path)
p.info.installed = true
plugin_lock.plugins[p.spec.name].src = p.spec.src
-- Prefer revision from the lockfile instead of using `version`
p.info.sha_target = (plugin_lock.plugins[p.spec.name] or {}).rev
-- Do not skip checkout even if HEAD and target have same commit hash to
-- have new repo in expected detached HEAD state and generated help files.
checkout(p, timestamp, false)
@@ -698,17 +767,34 @@ function M.add(specs, opts)
end
plugs = normalize_plugs(plugs)
-- Install
--- @param p vim.pack.Plug
local plugs_to_install = vim.tbl_filter(function(p)
return not p.info.installed
end, plugs)
-- Pre-process
lock_read()
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).
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
if not p.info.installed then
plugs_to_install[#plugs_to_install + 1] = p
needs_lock_write = true
end
end
-- Install
if #plugs_to_install > 0 then
git_ensure_exec()
install_list(plugs_to_install, opts.confirm)
end
if needs_lock_write then
lock_write()
end
-- Register and load those actually on disk while collecting errors
-- Delay showing all errors to have "good" plugins added first
local errors = {} --- @type string[]
@@ -887,7 +973,7 @@ end
---
--- @param names? string[] List of plugin names to update. Must be managed
--- by |vim.pack|, not necessarily already added to current session.
--- Default: names of all plugins added to current session via |vim.pack.add()|.
--- Default: names of all plugins managed by |vim.pack|.
--- @param opts? vim.pack.keyset.update
function M.update(names, opts)
vim.validate('names', names, vim.islist, true, 'list')
@@ -899,6 +985,7 @@ function M.update(names, opts)
return
end
git_ensure_exec()
lock_read()
-- Perform update
local timestamp = get_timestamp()
@@ -925,6 +1012,7 @@ function M.update(names, opts)
run_list(plug_list, do_update, progress_title)
if opts.force then
lock_write()
feedback_log(plug_list)
return
end
@@ -950,6 +1038,7 @@ function M.update(names, opts)
end
run_list(plugs_to_checkout, do_checkout, 'Applying updates')
lock_write()
feedback_log(plugs_to_checkout)
end)
end
@@ -967,6 +1056,8 @@ function M.del(names)
return
end
lock_read()
for _, p in ipairs(plug_list) do
trigger_event(p, 'PackChangedPre', 'delete')
@@ -974,8 +1065,12 @@ function M.del(names)
active_plugins[p.path] = nil
notify(("Removed plugin '%s'"):format(p.spec.name), 'INFO')
plugin_lock.plugins[p.spec.name] = nil
trigger_event(p, 'PackChanged', 'delete')
end
lock_write()
end
--- @inlinedoc
@@ -983,7 +1078,7 @@ end
--- @field active boolean Whether plugin was added via |vim.pack.add()| to current session.
--- @field branches? string[] Available Git branches (first is default). Missing if `info=false`.
--- @field path string Plugin's path on disk.
--- @field rev? string Current Git revision. Missing if `info=false`.
--- @field rev string Current Git revision.
--- @field spec vim.pack.SpecResolved A |vim.pack.Spec| with resolved `name`.
--- @field tags? string[] Available Git tags. Missing if `info=false`.
@@ -999,7 +1094,6 @@ local function add_p_data_info(p_data_list)
--- @async
funs[i] = function()
p_data.branches = git_get_branches(path)
p_data.rev = git_get_hash('HEAD', path)
p_data.tags = git_get_tags(path)
end
end
@@ -1025,30 +1119,29 @@ function M.get(names, opts)
active[p_active.id] = p_active.plug
end
lock_read()
local res = {} --- @type vim.pack.PlugData[]
local used_names = {} --- @type table<string,boolean>
for i = 1, n_active_plugins do
if active[i] and (not names or vim.tbl_contains(names, active[i].spec.name)) then
res[#res + 1] = { spec = vim.deepcopy(active[i].spec), path = active[i].path, active = true }
used_names[active[i].spec.name] = true
local name = active[i].spec.name
local spec = vim.deepcopy(active[i].spec)
local rev = (plugin_lock.plugins[name] or {}).rev
res[#res + 1] = { spec = spec, path = active[i].path, rev = rev, active = true }
used_names[name] = true
end
end
--- @async
local function do_get()
-- Process not active plugins
local plug_dir = get_plug_dir()
for n, t in vim.fs.dir(plug_dir, { depth = 1 }) do
local path = vim.fs.joinpath(plug_dir, n)
local is_in_names = not names or vim.tbl_contains(names, n)
if t == 'directory' and not active_plugins[path] and is_in_names then
local spec = { name = n, src = git_cmd({ 'remote', 'get-url', 'origin' }, path) }
res[#res + 1] = { spec = spec, path = path, active = false }
used_names[n] = true
end
local plug_dir = get_plug_dir()
for name, l_data in vim.spairs(plugin_lock.plugins) do
local path = vim.fs.joinpath(plug_dir, name)
local is_in_names = not names or vim.tbl_contains(names, name)
if not active_plugins[path] and is_in_names then
local spec = { name = name, src = l_data.src, version = l_data.version }
res[#res + 1] = { spec = spec, path = path, rev = l_data.rev, active = false }
used_names[name] = true
end
end
async.run(do_get):wait()
if names ~= nil then
-- Align result with input

View File

@@ -135,6 +135,12 @@ end
function repos_setup.plugindirs()
init_test_repo('plugindirs')
-- Add semver tag
repo_write_file('plugindirs', 'lua/plugindirs.lua', 'return "plugindirs v0.0.1"')
git_add_commit('Add version v0.0.1', 'plugindirs')
git_cmd({ 'tag', 'v0.0.1' }, 'plugindirs')
-- Add various 'plugin/' files
repo_write_file('plugindirs', 'lua/plugindirs.lua', 'return "plugindirs main"')
repo_write_file('plugindirs', 'plugin/dirs.lua', 'vim.g._plugin = true')
repo_write_file('plugindirs', 'plugin/dirs_log.lua', '_G.DL = _G.DL or {}; DL[#DL+1] = "p"')
@@ -310,6 +316,14 @@ local function is_jit()
return exec_lua('return package.loaded.jit ~= nil')
end
local function get_lock_path()
return vim.fs.joinpath(fn.stdpath('config'), 'nvim-pack-lock.json')
end
local function get_lock_tbl()
return vim.json.decode(fn.readblob(get_lock_path()))
end
-- Tests ======================================================================
describe('vim.pack', function()
@@ -326,6 +340,9 @@ describe('vim.pack', function()
after_each(function()
vim.fs.rm(pack_get_dir(), { force = true, recursive = true })
vim.fs.rm(get_lock_path(), { force = true })
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
pcall(vim.fs.rm, log_path, { force = true })
end)
teardown(function()
@@ -413,6 +430,117 @@ describe('vim.pack', function()
eq('plugindirs main', exec_lua('return require("plugindirs")'))
end)
it('creates lockfile', function()
local helptags_rev = git_get_hash('HEAD', 'helptags')
exec_lua(function()
vim.pack.add({
{ src = repos_src.basic, version = 'some-tag' },
{ src = repos_src.defbranch, version = 'main' },
{ src = repos_src.helptags, version = helptags_rev },
{ src = repos_src.plugindirs },
{ src = repos_src.semver, version = vim.version.range('*') },
})
end)
local basic_rev = git_get_hash('some-tag', 'basic')
local defbranch_rev = git_get_hash('main', 'defbranch')
local plugindirs_rev = git_get_hash('HEAD', 'plugindirs')
local semver_rev = git_get_hash('v1.0.0', 'semver')
-- Should properly format as indented JSON
local ref_lockfile_lines = {
'{',
' "plugins": {',
' "basic": {',
' "rev": "' .. basic_rev .. '",',
' "src": "' .. repos_src.basic .. '",',
-- Branch, tag, and commit should be serialized like `'value'` to be
-- distinguishable from version ranges
' "version": "\'some-tag\'"',
' },',
' "defbranch": {',
' "rev": "' .. defbranch_rev .. '",',
' "src": "' .. repos_src.defbranch .. '",',
' "version": "\'main\'"',
' },',
' "helptags": {',
' "rev": "' .. helptags_rev .. '",',
' "src": "' .. repos_src.helptags .. '",',
' "version": "\'' .. helptags_rev .. '\'"',
' },',
' "plugindirs": {',
' "rev": "' .. plugindirs_rev .. '",',
' "src": "' .. repos_src.plugindirs .. '"',
-- Absent `version` should be missing and not autoresolved
' },',
' "semver": {',
' "rev": "' .. semver_rev .. '",',
' "src": "' .. repos_src.semver .. '",',
' "version": ">=0.0.0"',
' }',
' }',
'}',
}
eq(ref_lockfile_lines, fn.readfile(get_lock_path()))
end)
it('updates lockfile', function()
exec_lua(function()
vim.pack.add({ repos_src.basic })
end)
local ref_lockfile = {
plugins = {
basic = { rev = git_get_hash('main', 'basic'), src = repos_src.basic },
},
}
eq(ref_lockfile, get_lock_tbl())
n.clear()
exec_lua(function()
vim.pack.add({ { src = repos_src.basic, version = 'main' } })
end)
ref_lockfile.plugins.basic.version = "'main'"
eq(ref_lockfile, get_lock_tbl())
end)
it('uses lockfile revision during install', function()
exec_lua(function()
vim.pack.add({ { src = repos_src.basic, version = 'feat-branch' } })
end)
-- Mock clean initial install, but with lockfile present
n.clear()
local basic_plug_path = vim.fs.joinpath(pack_get_dir(), 'basic')
vim.fs.rm(basic_plug_path, { force = true, recursive = true })
local basic_rev = git_get_hash('feat-branch', 'basic')
local ref_lockfile = {
plugins = {
basic = { rev = basic_rev, src = repos_src.basic, version = "'feat-branch'" },
},
}
eq(ref_lockfile, get_lock_tbl())
exec_lua(function()
-- Should use revision from lockfile (pointing at latest 'feat-branch'
-- commit) and not use latest `main` commit
vim.pack.add({ { src = repos_src.basic, version = 'main' } })
end)
local basic_lua_file = vim.fs.joinpath(pack_get_plug_path('basic'), 'lua', 'basic.lua')
eq({ 'return "basic feat-branch"' }, fn.readfile(basic_lua_file))
-- Running `update()` should still update to use `main`
exec_lua(function()
vim.pack.update(nil, { force = true })
end)
eq({ 'return "basic main"' }, fn.readfile(basic_lua_file))
ref_lockfile.plugins.basic.rev = git_get_hash('main', 'basic')
ref_lockfile.plugins.basic.version = "'main'"
eq(ref_lockfile, get_lock_tbl())
end)
it('installs at proper version', function()
local out = exec_lua(function()
vim.pack.add({
@@ -597,9 +725,8 @@ describe('vim.pack', function()
eq({}, n.exec_lua('return { vim.g._plugin, vim.g._after_plugin }'))
-- Plugins should still be marked as "active", since they were added
plugindirs_data.active = true
basic_data.active = true
eq({ plugindirs_data, basic_data }, exec_lua('return vim.pack.get(nil, { info = false })'))
eq(true, exec_lua('return vim.pack.get({ "plugindirs" })[1].active'))
eq(true, exec_lua('return vim.pack.get({ "basic" })[1].active'))
end
-- Works on initial install
@@ -759,7 +886,7 @@ describe('vim.pack', function()
-- Install initial versions of tested plugins
exec_lua(function()
vim.pack.add({
repos_src.fetch,
{ src = repos_src.fetch, version = 'main' },
{ src = repos_src.semver, version = 'v0.3.0' },
repos_src.defbranch,
})
@@ -777,6 +904,11 @@ describe('vim.pack', function()
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch new 2"')
git_add_commit('Commit to be added 2', 'fetch')
-- Make `dev` default remote branch to check that `version` is respected
git_cmd({ 'checkout', '-b', 'dev' }, 'fetch')
repo_write_file('fetch', 'lua/fetch.lua', 'return "fetch dev"')
git_add_commit('Commit from default `dev` branch', 'fetch')
end)
after_each(function()
@@ -854,8 +986,8 @@ describe('vim.pack', function()
local screen
screen = Screen.new(85, 35)
hashes.fetch_new = git_get_hash('HEAD', 'fetch')
hashes.fetch_new_prev = git_get_hash('HEAD~', 'fetch')
hashes.fetch_new = git_get_hash('main', 'fetch')
hashes.fetch_new_prev = git_get_hash('main~', 'fetch')
hashes.semver_head = git_get_hash('v0.3.0', 'semver')
local tab_name = 'n' .. (t.is_os('win') and ':' or '') .. '//2/confirm-update'
@@ -1087,12 +1219,27 @@ describe('vim.pack', function()
local confirm_text = table.concat(api.nvim_buf_get_lines(0, 0, -1, false), '\n')
matches('Available newer versions:\n• v1%.0%.0\n• v0%.4\n• 0%.3%.1$', confirm_text)
end)
it('updates lockfile', function()
exec_lua(function()
vim.pack.add({ repos_src.fetch })
end)
local ref_fetch_lock = { rev = hashes.fetch_head, src = repos_src.fetch }
eq(ref_fetch_lock, get_lock_tbl().plugins.fetch)
exec_lua('vim.pack.update()')
n.exec('write')
ref_fetch_lock.rev = git_get_hash('main', 'fetch')
eq(ref_fetch_lock, get_lock_tbl().plugins.fetch)
end)
end)
it('works with not active plugins', function()
-- No plugins are added, but they are installed in `before_each()`
exec_lua(function()
-- No plugins are added, but they are installed in `before_each()`
vim.pack.update({ 'fetch' })
-- By default should also include not active plugins
vim.pack.update()
end)
eq({ 'return "fetch main"' }, fn.readfile(fetch_lua_file))
n.exec('write')
@@ -1116,8 +1263,8 @@ describe('vim.pack', function()
eq('', api.nvim_get_option_value('filetype', {}))
-- Write to log file
hashes.fetch_new = git_get_hash('HEAD', 'fetch')
hashes.fetch_new_prev = git_get_hash('HEAD~', 'fetch')
hashes.fetch_new = git_get_hash('main', 'fetch')
hashes.fetch_new_prev = git_get_hash('main~', 'fetch')
local log_path = vim.fs.joinpath(fn.stdpath('log'), 'nvim-pack.log')
local log_lines = fn.readfile(log_path)
@@ -1138,17 +1285,21 @@ describe('vim.pack', function()
'',
}
eq(ref_log_lines, vim.list_slice(log_lines, 2))
-- Should update lockfile
eq(hashes.fetch_new, get_lock_tbl().plugins.fetch.rev)
end)
it('shows progress report', function()
track_nvim_echo()
exec_lua(function()
vim.pack.add({ repos_src.fetch, repos_src.defbranch })
-- Should also include updates from not active plugins
vim.pack.update()
end)
-- During initial download
validate_progress_report('Downloading updates', { 'fetch', 'defbranch' })
validate_progress_report('Downloading updates', { 'fetch', 'defbranch', 'semver' })
exec_lua('_G.echo_log = {}')
-- During application (only for plugins that have updates)
@@ -1165,7 +1316,7 @@ describe('vim.pack', function()
vim.pack.add({ repos_src.fetch, repos_src.defbranch })
vim.pack.update(nil, { force = true })
end)
validate_progress_report('Updating', { 'fetch', 'defbranch' })
validate_progress_report('Updating', { 'fetch', 'defbranch', 'semver' })
end)
it('triggers relevant events', function()
@@ -1246,10 +1397,10 @@ describe('vim.pack', function()
local make_basic_data = function(active, info)
local spec = { name = 'basic', src = repos_src.basic, version = 'feat-branch' }
local path = pack_get_plug_path('basic')
local res = { active = active, path = path, spec = spec }
local rev = git_get_hash('feat-branch', 'basic')
local res = { active = active, path = path, spec = spec, rev = rev }
if info then
res.branches = { 'main', 'feat-branch' }
res.rev = git_get_hash('feat-branch', 'basic')
res.tags = { 'some-tag' }
end
return res
@@ -1258,50 +1409,74 @@ describe('vim.pack', function()
local make_defbranch_data = function(active, info)
local spec = { name = 'defbranch', src = repos_src.defbranch }
local path = pack_get_plug_path('defbranch')
local res = { active = active, path = path, spec = spec }
local rev = git_get_hash('dev', 'defbranch')
local res = { active = active, path = path, spec = spec, rev = rev }
if info then
res.branches = { 'dev', 'main' }
res.rev = git_get_hash('dev', 'defbranch')
res.tags = {}
end
return res
end
local make_plugindirs_data = function(active, info)
local spec =
{ name = 'plugindirs', src = repos_src.plugindirs, version = vim.version.range('*') }
local path = pack_get_plug_path('plugindirs')
local rev = git_get_hash('v0.0.1', 'plugindirs')
local res = { active = active, path = path, spec = spec, rev = rev }
if info then
res.branches = { 'main' }
res.tags = { 'v0.0.1' }
end
return res
end
it('returns list with necessary data', function()
local basic_data, defbranch_data
local basic_data, defbranch_data, plugindirs_data
-- Should work just after installation
exec_lua(function()
vim.pack.add({ repos_src.defbranch, { src = repos_src.basic, version = 'feat-branch' } })
vim.pack.add({
repos_src.defbranch,
{ src = repos_src.basic, version = 'feat-branch' },
{ src = repos_src.plugindirs, version = vim.version.range('*') },
})
end)
defbranch_data = make_defbranch_data(true, true)
basic_data = make_basic_data(true, true)
plugindirs_data = make_plugindirs_data(true, true)
-- Should preserve order in which plugins were `vim.pack.add()`ed
eq({ defbranch_data, basic_data }, exec_lua('return vim.pack.get()'))
eq({ defbranch_data, basic_data, plugindirs_data }, exec_lua('return vim.pack.get()'))
-- Should also list non-active plugins
n.clear()
exec_lua(function()
vim.pack.add({ { src = repos_src.basic, version = 'feat-branch' } })
vim.pack.add({ repos_src.defbranch })
end)
defbranch_data = make_defbranch_data(false, true)
basic_data = make_basic_data(true, true)
-- Should first list active, then non-active
eq({ basic_data, defbranch_data }, exec_lua('return vim.pack.get()'))
defbranch_data = make_defbranch_data(true, true)
basic_data = make_basic_data(false, true)
plugindirs_data = make_plugindirs_data(false, true)
-- Should first list active, then non-active (including their latest
-- set `version` which is inferred from lockfile)
eq({ defbranch_data, basic_data, plugindirs_data }, exec_lua('return vim.pack.get()'))
-- Should respect `names` for both active and not active plugins
eq({ basic_data }, exec_lua('return vim.pack.get({ "basic" })'))
eq({ defbranch_data }, exec_lua('return vim.pack.get({ "defbranch" })'))
eq({ defbranch_data, basic_data }, exec_lua('return vim.pack.get({ "defbranch", "basic" })'))
eq({ basic_data, defbranch_data }, exec_lua('return vim.pack.get({ "basic", "defbranch" })'))
local bad_get_cmd = 'return vim.pack.get({ "ccc", "basic", "aaa" })'
matches('Plugin `ccc` is not installed', pcall_err(exec_lua, bad_get_cmd))
-- Should respect `opts.info`
defbranch_data = make_defbranch_data(false, false)
basic_data = make_basic_data(true, false)
eq({ basic_data, defbranch_data }, exec_lua('return vim.pack.get(nil, { info = false })'))
defbranch_data = make_defbranch_data(true, false)
basic_data = make_basic_data(false, false)
plugindirs_data = make_plugindirs_data(false, false)
eq(
{ defbranch_data, basic_data, plugindirs_data },
exec_lua('return vim.pack.get(nil, { info = false })')
)
eq({ basic_data }, exec_lua('return vim.pack.get({ "basic" }, { info = false })'))
eq({ defbranch_data }, exec_lua('return vim.pack.get({ "defbranch" }, { info = false })'))
end)
@@ -1350,6 +1525,10 @@ describe('vim.pack', function()
eq(true, pack_exists('basic'))
eq(true, pack_exists('plugindirs'))
local locked_plugins = vim.tbl_keys(get_lock_tbl().plugins)
table.sort(locked_plugins)
eq({ 'basic', 'plugindirs' }, locked_plugins)
watch_events({ 'PackChangedPre', 'PackChanged' })
n.exec('messages clear')
@@ -1371,6 +1550,23 @@ describe('vim.pack', function()
eq(3, find_in_log(log, 'PackChangedPre', 'delete', 'plugindirs', nil))
eq(4, find_in_log(log, 'PackChanged', 'delete', 'plugindirs', nil))
eq(4, #log)
-- Should update lockfile
eq({ plugins = {} }, get_lock_tbl())
end)
it('works without prior `add()`', function()
exec_lua(function()
vim.pack.add({ repos_src.basic })
end)
n.clear()
eq(true, pack_exists('basic'))
exec_lua(function()
vim.pack.del({ 'basic' })
end)
eq(false, pack_exists('basic'))
eq({ plugins = {} }, get_lock_tbl())
end)
it('validates input', function()