commit bac4cde9cd43dcb1bb962d71f3f9253813a701a3
parent 09edb145f5f3d10aadc9ba45358cdb1a0f166534
Author: Evgeni Chasnovski <evgeni.chasnovski@gmail.com>
Date: Mon, 12 Jan 2026 22:57:38 +0200
fix(pack): actually checkout proper version of submodules
Problem: Installing plugin with submodules doesn't check out their
state (due to `git clone --no-checkout` to not end up with default
branch code in case of invalid `version`).
Updating a plugin with submodules doesn't update their state.
Solution: Update `git_checkout` helper to account for submodules.
Another approach would be `git checkout --recurse-submodules ...`,
but that doesn't seem to allow `--filter=blob:none` for submodules,
which is nice to have.
Also make `git_clone` wrapper simpler since `--no-checkout` makes
`--recurse-submodules` and `--also-filter-submodules` do nothing.
Diffstat:
2 files changed, 85 insertions(+), 8 deletions(-)
diff --git a/runtime/lua/vim/pack.lua b/runtime/lua/vim/pack.lua
@@ -264,17 +264,12 @@ end
--- @param url string
--- @param path string
local function git_clone(url, path)
- local cmd = { 'clone', '--quiet', '--no-checkout', '--recurse-submodules' }
+ local cmd = { 'clone', '--quiet', '--no-checkout' }
if vim.startswith(url, 'file://') then
cmd[#cmd + 1] = '--no-hardlinks'
- else
- if git_version >= vim.version.parse('2.36') then
- cmd[#cmd + 1] = '--filter=blob:none'
- cmd[#cmd + 1] = '--also-filter-submodules'
- elseif git_version >= vim.version.parse('2.27') then
- cmd[#cmd + 1] = '--filter=blob:none'
- end
+ elseif git_version >= vim.version.parse('2.27') then
+ cmd[#cmd + 1] = '--filter=blob:none'
end
vim.list_extend(cmd, { '--origin', 'origin', url, path })
@@ -669,6 +664,12 @@ local function checkout(p, timestamp, skip_stash)
git_cmd({ 'checkout', '--quiet', p.info.sha_target }, p.path)
+ local submodule_cmd = { 'submodule', 'update', '--init', '--recursive' }
+ if git_version >= vim.version.parse('2.36') then
+ submodule_cmd[#submodule_cmd + 1] = '--filter=blob:none'
+ end
+ git_cmd(submodule_cmd, p.path)
+
plugin_lock.plugins[p.spec.name].rev = p.info.sha_target
-- (Re)Generate help tags according to the current help files.
diff --git a/test/functional/plugin/pack_spec.lua b/test/functional/plugin/pack_spec.lua
@@ -221,6 +221,34 @@ function repos_setup.semver()
add_tag('v1.0.0')
end
+function repos_setup.with_subs()
+ -- To-be-submodule repo
+ init_test_repo('sub')
+
+ repo_write_file('sub', 'sub.lua', 'return "sub init"')
+ git_add_commit('Initial commit for "sub"', 'sub')
+
+ -- With-submodules repo with submodule recorded at its initial commit
+ init_test_repo('with_subs')
+
+ repo_write_file('with_subs', 'lua/with_subs.lua', 'return "with_subs init"')
+ local sub_src = 'file://' .. repo_get_path('sub')
+ git_cmd({ '-c', 'protocol.file.allow=always', 'submodule', 'add', sub_src, 'sub' }, 'with_subs')
+ git_add_commit('Initial commit for "with_subs"', 'with_subs')
+ git_cmd({ 'tag', 'init-commit' }, 'with_subs')
+
+ -- Advance both submodule and with-submodules repos by one commit
+ repo_write_file('sub', 'sub.lua', 'return "sub main"')
+ git_add_commit('Second commit for "sub"', 'sub')
+
+ repo_write_file('with_subs', 'lua/with_subs.lua', 'return "with_subs main"')
+ git_cmd(
+ { '-c', 'protocol.file.allow=always', 'submodule', 'update', '--remote', 'sub' },
+ 'with_subs'
+ )
+ git_add_commit('Second commit for "with_subs"', 'with_subs')
+end
+
-- Utility --------------------------------------------------------------------
local function watch_events(event)
@@ -319,6 +347,25 @@ local function mock_confirm(output_value)
end)
end
+local function mock_git_file_transport()
+ -- HACK: mock `vim.system()` to have `git` commands be executed
+ -- with temporarily set 'protocol.file.allow=always' option.
+ -- Otherwise performing `git` operations with submodules from `vim.pack`
+ -- itself will fail with `fatal: transport 'file' not allowed`.
+ -- Directly adding `-c protocol.file.allow=always` to `git_cmd` in `vim.pack`
+ -- itself is too much and might be bad for security.
+ exec_lua(function()
+ local vim_system_orig = vim.system
+ vim.system = function(cmd, opts, on_exit)
+ if cmd[1] == 'git' then
+ table.insert(cmd, 2, '-c')
+ table.insert(cmd, 3, 'protocol.file.allow=always')
+ end
+ return vim_system_orig(cmd, opts, on_exit)
+ end
+ end)
+end
+
local function is_jit()
return exec_lua('return package.loaded.jit ~= nil')
end
@@ -787,6 +834,16 @@ describe('vim.pack', function()
eq(false, vim.tbl_contains(rtp, after_dir))
end)
+ it('installs with submodules', function()
+ mock_git_file_transport()
+ exec_lua(function()
+ vim.pack.add({ repos_src.with_subs })
+ end)
+
+ local sub_lua_file = vim.fs.joinpath(pack_get_plug_path('with_subs'), 'sub', 'sub.lua')
+ eq('return "sub main"', fn.readblob(sub_lua_file))
+ end)
+
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' } })
@@ -1662,6 +1719,25 @@ describe('vim.pack', function()
eq('return "fetch new 2"', fn.readblob(fetch_lua_file))
end)
+ it('works with submodules', function()
+ mock_git_file_transport()
+ exec_lua(function()
+ vim.pack.add({ { src = repos_src.with_subs, version = 'init-commit' } })
+ end)
+
+ local sub_lua_file = vim.fs.joinpath(pack_get_plug_path('with_subs'), 'sub', 'sub.lua')
+ eq('return "sub init"', fn.readblob(sub_lua_file))
+
+ n.clear()
+ mock_git_file_transport()
+ exec_lua(function()
+ vim.pack.add({ repos_src.with_subs })
+ vim.pack.update({ 'with_subs' })
+ end)
+ n.exec('write')
+ eq('return "sub main"', fn.readblob(sub_lua_file))
+ end)
+
it('can force update', function()
exec_lua(function()
vim.pack.add({ repos_src.fetch })