On a Linux machine I opened Neovim, ran :Lazy update, and it stopped with Failed (2).
The problem was not only doc/tags-ja in vimdoc-ja. .gitattributes and .github/workflows/format.yml in zbirenbaum/copilot.lua
were also treated as “local changes”.
I had seen the same error on vimdoc-ja alone before, and I had put in a workaround that ran git checkout -- doc/tags-ja on LazyUpdatePre.
That was not enough this time.
The symptom
The message that showed up at startup was the following.
Failed (2)
● copilot.lua 7.63ms CopilotChat.nvim
You have local changes in `~/.local/share/nvim/lazy/copilot.lua`:
* .gitattributes
* .github/workflows/format.yml
Please remove them to update.
You can also press `x` to remove the plugin and then `I` to install it again.
● vimdoc-ja 0.05ms start
You have local changes in `~/.local/share/nvim/lazy/vimdoc-ja`:
* doc/tags-ja
Please remove them to update.
You can also press `x` to remove the plugin and then `I` to install it again.I had no memory of editing the plugin sources themselves.lazy.nvim refuses the update once it decides the git working tree is dirty.
Why the earlier checkout was not enough
Before an update, lazy.nvim runs git ls-files -d -m and will not pull if any tracked file has a diff.
It discards doc/tags automatically, but doc/tags-ja and the copilot.lua files are outside that exception.
Looking at the Linux machine, three things were stacked on top of each other.
- The live config was a separate copy under
~/.config/nvim, and theLazyUpdatePrehook I had put in the Mac dotfiles was not there - Neovim on that Linux box was v0.7.2, so the
vim.systemI had written last time never ran; it is 0.10 and later only - Even if I checked out
doc/tags-ja, startup:helptagsdirtied it again right away
Number 3 was the messiest.
Even after a manual git checkout -- doc/tags-ja, opening Neovim again made it dirty.
The checkout was not failing. The file was being regenerated immediately after I restored it.
The cause is different for each plugin
vimdoc-ja tracks tags-ja in git
vimdoc-ja commits the generated doc/tags-ja (the helptags file) into the repository.
Because helplang = 'ja', the :helptags that lazy.nvim runs on every install or update regenerates doc/tags-ja locally.
If that regenerated file differs from the committed content by even one byte, lazy.nvim concludes there are local changes in the git repository and stops the update.
The diff I saw this time was that the leading !_TAG_FILE_ENCODING line was gone.
The maintainer (folke) has stated clearly that there are no plans to add this kind of special-case handling on the lazy.nvim side.
The root issue is the structure of vimdoc-ja tracking generated files in git.
In the past, version differences in the Vim used to generate helptags were also a factor.
Vim 9.0.0110 changed how pseudo-tags in code examples inside the help were handled, which made the generated result more likely to drift.
After vimdoc-ja-working#1434
(in Japanese) (merged 2024-02-07) updated the generating Vim to 9.1.0065, it happens less often.
Even so, as long as :helptags runs every time, the drift itself can still come back.
The history is collected in vim-jp/vimdoc-ja#279 (in Japanese).
copilot.lua is not a generated file
.gitattributes and .github/workflows/format.yml are not files that Neovim or lazy.nvim writes.
The same plugin directory was clean on the Mac. Something on the Linux side had rewritten them.
Line-ending conversion, a YAML formatter, or an editor auto-format are candidates, but I have not pinned down which one touched them.
It was not an intentional patch, so I decided to discard all local changes on tracked files.
Workaround: clear the error that is showing now
If you just want the update to go through, reset the working tree to HEAD.
# reset the target plugins to HEAD
git -C ~/.local/share/nvim/lazy/vimdoc-ja reset --hard HEAD
git -C ~/.local/share/nvim/lazy/copilot.lua reset --hard HEADPressing x (remove) then I (install again) on the :Lazy screen gets you back to the same state.
That said, tags-ja will drift again the next time :helptags runs.
Permanent fix: reset the working tree to HEAD before the update
Doing reset by hand every time is a pain, so I decided to discard the diffs right before lazy.nvim starts the update.
It sits just before the require('lazy').setup(...) call in ~/dotfiles/nvim/lua/plugins.lua.
The changes from last time are the following.
git reset --hard HEADinstead ofgit checkout -- doc/tags-ja- Cover both vimdoc-ja and copilot.lua
- Widen the events from
LazyUpdatePreonly toLazySyncPre/LazyRestorePre/LazyCheckPreas well - Use
vim.fn.system, which still works on Neovim 0.7, instead ofvim.system
-- Reset the given plugin's working tree to HEAD.
---@param pluginName string plugin directory name (under stdpath('data')/lazy)
---@return nil
local function discardPluginLocalChanges(pluginName)
local pluginDir = vim.fn.stdpath('data') .. '/lazy/' .. pluginName
if vim.fn.isdirectory(pluginDir) ~= 1 then
return
end
-- vim.system is Neovim 0.10+, so 0.7 environments use vim.fn.system
local output = vim.fn.system({ 'git', '-C', pluginDir, 'reset', '--hard', 'HEAD' })
if vim.v.shell_error ~= 0 then
vim.notify(
string.format(
'discardPluginLocalChanges: git reset --hard に失敗しました。pluginName=%s cwd=%s code=%s output=%s',
pluginName,
pluginDir,
tostring(vim.v.shell_error),
output
),
vim.log.levels.WARN
)
return
end
-- If it is still dirty after reset, lazy's git ls-files -d -m will stop the update
local dirtyFiles = vim.fn.system({ 'git', '-C', pluginDir, 'ls-files', '-d', '-m' })
if vim.v.shell_error == 0 and dirtyFiles ~= '' then
vim.notify(
string.format(
'discardPluginLocalChanges: reset 後も差分が残っています。pluginName=%s cwd=%s dirtyFiles=%s',
pluginName,
pluginDir,
dirtyFiles
),
vim.log.levels.WARN
)
end
end
-- Discard known local diffs before Lazy update-related operations.
---@return nil
local function discardLazyUpdateBlockers()
-- vimdoc-ja tracks generated doc/tags-ja in git, so a local regenerate via :helptags
-- that differs makes lazy.nvim refuse the update with "local changes"
-- (https://github.com/vim-jp/vimdoc-ja/issues/279)
discardPluginLocalChanges('vimdoc-ja')
-- also discard diffs such as .gitattributes / workflow in copilot.lua
discardPluginLocalChanges('copilot.lua')
end
vim.api.nvim_create_autocmd('User', {
pattern = { 'LazyUpdatePre', 'LazySyncPre', 'LazyRestorePre', 'LazyCheckPre' },
group = vim.api.nvim_create_augroup('lazy_discard_local_changes', { clear = true }),
callback = discardLazyUpdateBlockers,
})Because it goes through stdpath('data'), I do not have to hard-code the plugin’s real path.
On failure it sends the function name, target plugin, path, exit code, and output to vim.notify.
The limitation is clear.
Intentional local patches on the target plugins disappear on every update.tags-ja becoming dirty again after an update is expected; the hook discards it before the next one.
I took a workaround that is actually used in the community (mimikun/dotfiles ), and widened it for my own config.
Summary
| Approach | What it does | Recurs |
|---|---|---|
git reset --hard HEAD | Discard the diff that is showing now | tags-ja does |
x then I in :Lazy | Reinstall the plugin from scratch | tags-ja does |
| reset in the pre-update hook | Discard right before update / sync / restore / check | The update itself goes through |
The cause was not a bug in lazy.nvim.
vimdoc-ja tracks generated doc/tags-ja in git, and on the Linux side the tracked files in copilot.lua were dirty.
Both get caught by git ls-files -d -m, so I decided to reset them to HEAD before the update.
I hope this is useful for anyone whose updates have stopped on the same error.