Sometimes, I need to apply small patches to a plugin to fulfill a very niche use
case or to fix something without waiting for the PR to reach upstream. However,
when doing so, Lazy can't sync the repo because there are local changes. While
Lazy provides ways to handle this (like dev
or dir
), they require manually
monitoring and merging upstream changes to stay in sync...
This plugin addresses this issue by automatically saving changes in all installed plugins through git commands, reverting them before Lazy starts its lazy magic, and reapplying them afterwards.
Before:
After:
return {
"one-d-wide/lazy-patcher.nvim",
config = true,
ft = "lazy", -- for lazy loading
}
Make sure you don't accidentally use patches
directory for something else
(by default it's ~/.config/nvim/patches
).
When Lazy starts updating plugins, their repositories containing changes will
be automatically restored to upstream state, keeping your changes in patches
directory, and then reapplied after Lazy finishes it's job.
By default, all your patches are visible in patches
directory. There are two
considerations:
- Only one file per plugin.
- The name of the patch should match the repository name (more precisely, the
directory name inside the Lazy root folder) and the file extension must be
.patch
. e.g.:nvim-treesitter.patch
Options could be passed to the setup
function:
require("lazy-local-patcher").setup({
patches_path = vim.fn.stdpath("config") .. "/patches", -- Directory where patch files are stored
print_logs = true, -- Print log messages while applying changes
})
Lazy patcher comes with the following defaults:
local defaults = {
lazy_path = vim.fn.stdpath("data") .. "/lazy", -- Directory where lazy install the plugins
patches_path = vim.fn.stdpath("config") .. "/patches", -- Directory where patch files are stored
update_patches = true, -- Update patch files based on changes in plugins
apply_patches = true, -- Apply changes from existing patches if there were none before
confirm_mass_changes = true, -- Ask confirmation before triggering mass changes from command
print_logs = true, -- Print log messages while applying changes
}
You could use :LazyPatchRestore[All]
and :LazyPatchApply[All]
functions to
surgically apply/restore patches, or :LazyPatcherRestoreApplyAll
to process
all at the same time:
:LazyPatchRestore nvim-treesitter
[lazy-patcher] Stashing `nvim-treesitter`
:LazyPatchRestore nvim-treesitter
[lazy-patcher] Applying `nvim-treesitter`
You could inspect emitted errors using :LazyPatcher
.
Typically patches are created automatically at the moment you update your
plugins. Also you could manually trigger full restore-apply sequence with
:LazyPatcherRestoreApply <plugins...>
or simply
:LazyPatcherRestoreApplyAll
.
If the restore-apply sequence fails for any reason, e.g. it was interrupted mid-process. All changes are reapplied automatically next time the apply operation is triggered.
If that's not enough, saved changes for each plugin's repository are
redundantly stored in both *.patch.guard
file inside patches
directory, and
in the plugin's own git repository in a form of a stash. So to access them
manually use git apply <patch_guard_path>
and git stash pop
accordingly.
Changes to the plugin repositories are primarily manipulated by git stash
's
push
and pop
subcommands. There is also a mechanism to initially apply
existing patches to freshly installed repositories. And a bit of resilience
against accidentally loosing changes in a form of *.patch.guard
records
duplicating the contents of stashes. Overall that works as follows:
alias git="command git -C <plugin_path>"
restore() {
# Try to reapply after unclean exit
test -f <patch_guard_path> \
&& apply
# Save stash and complementary patch guard
git stash push --include-untracked \
&& git stash show --include-untracked --patch \
> <patch_guard_path>
}
apply_patch_file() {
# Validate repo is clean and apply changes
! test -f <patch_guard_path> \
&& git status --short | cmd /dev/null \
&& git apply --ignore-space-change <patch_file>
}
apply() {
# Try to apply saved patch first
apply_patch_file \
&& return 0
# Ensure that repo state is consistent
git stash show --include-untracked --patch \
| cmp <patch_guard_path> \
|| return 1
# Reapply changes from stash
git stash pop \
&& mv <patch_guard_path> <patch_file>
}
While this plugin is primarily designed for my personal use and tailored to a very specific use case, suggestions, issues, or pull requests are very welcome.
Enjoy