mirror of
https://github.com/folke/lazy.nvim.git
synced 2025-04-23 14:46:45 +00:00
## Description
As I described in
https://github.com/folke/lazy.nvim/pull/1512#issuecomment-2212474372,
this makes it so that local plugins will only show as needing updates if
the local branch is behind the upstream branch. This is done by checking
the output of the `git log` command, and only setting `plugin._.updates`
if the output is not empty.
This seems to solve my issue where local plugins with unpushed changes
always show as needing updates, but if there's a easier/better way of
doing it then please feel free to edit/close this. Or if you don't agree
that the current behaviour is a bug, then that's also fine - it's not a
big deal and I can easily just ignore the "updates available" notice.
I also came across a minor issue where the plugin diff view (press `d`)
compares the wrong commits for local plugins, because
[lua/lazy/view/init.lua](c771cf4928/lua/lazy/view/init.lua (L268)
)
always uses `get_target`. I fixed this by moving `get_local_target` into
`get_target` - I think this is simpler and more straightforward than the
alternative of adding a ternary everywhere `get_target` is called.
This second bugfix is a very small change, so I've just included it
here, but I'm happy to make a second PR if you'd like.
## Related Issue(s)
Related PR: #1512
334 lines
8.7 KiB
Lua
334 lines
8.7 KiB
Lua
local Config = require("lazy.core.config")
|
|
local Git = require("lazy.manage.git")
|
|
local Lock = require("lazy.manage.lock")
|
|
local Util = require("lazy.util")
|
|
|
|
---@type table<string, LazyTaskDef>
|
|
local M = {}
|
|
|
|
M.log = {
|
|
---@param opts {updated?:boolean, check?: boolean}
|
|
skip = function(plugin, opts)
|
|
if opts.check and plugin.pin then
|
|
return true
|
|
end
|
|
if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then
|
|
return true
|
|
end
|
|
local stat = vim.uv.fs_stat(plugin.dir .. "/.git")
|
|
return not (stat and stat.type == "directory")
|
|
end,
|
|
---@async
|
|
---@param opts {args?: string[], updated?:boolean, check?:boolean}
|
|
run = function(self, opts)
|
|
-- self:spawn({ "sleep", "5" })
|
|
local args = {
|
|
"log",
|
|
"--pretty=format:%h %s (%cr)",
|
|
"--abbrev-commit",
|
|
"--decorate",
|
|
"--date=short",
|
|
"--color=never",
|
|
"--no-show-signature",
|
|
}
|
|
|
|
local info, target
|
|
|
|
if opts.updated then
|
|
table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD"))
|
|
elseif opts.check then
|
|
info = assert(Git.info(self.plugin.dir))
|
|
target = assert(Git.get_target(self.plugin))
|
|
if not target.commit then
|
|
for k, v in pairs(target) do
|
|
error(k .. " '" .. v .. "' not found")
|
|
end
|
|
error("no target commit found")
|
|
end
|
|
assert(target.commit, self.plugin.name .. " " .. target.branch)
|
|
if not self.plugin._.is_local then
|
|
if Git.eq(info, target) then
|
|
if Config.options.checker.check_pinned then
|
|
local last_commit = Git.get_commit(self.plugin.dir, target.branch, true)
|
|
if not Git.eq(info, { commit = last_commit }) then
|
|
self.plugin._.outdated = true
|
|
end
|
|
end
|
|
else
|
|
self.plugin._.updates = { from = info, to = target }
|
|
end
|
|
end
|
|
table.insert(args, info.commit .. ".." .. target.commit)
|
|
else
|
|
vim.list_extend(args, opts.args or Config.options.git.log)
|
|
end
|
|
|
|
self:spawn("git", {
|
|
args = args,
|
|
cwd = self.plugin.dir,
|
|
})
|
|
|
|
-- for local plugins, mark as needing updates only if local is
|
|
-- behind upstream, i.e. if git log gave no output
|
|
if opts.check and self.plugin._.is_local then
|
|
if not vim.tbl_isempty(self:get_log()) then
|
|
self.plugin._.updates = { from = info, to = target }
|
|
end
|
|
end
|
|
end,
|
|
}
|
|
|
|
M.clone = {
|
|
skip = function(plugin)
|
|
return plugin._.installed or plugin._.is_local
|
|
end,
|
|
---@async
|
|
run = function(self)
|
|
local args = {
|
|
"clone",
|
|
self.plugin.url,
|
|
}
|
|
|
|
if Config.options.git.filter then
|
|
args[#args + 1] = "--filter=blob:none"
|
|
end
|
|
|
|
if self.plugin.submodules ~= false then
|
|
args[#args + 1] = "--recurse-submodules"
|
|
end
|
|
|
|
args[#args + 1] = "--origin=origin"
|
|
|
|
-- If git config --global core.autocrlf is true on a Unix/Linux system, then the git clone
|
|
-- process will lead to files with CRLF endings. Vi / vim / neovim cannot handle this.
|
|
-- Force git to clone with core.autocrlf=false.
|
|
args[#args + 1] = "-c"
|
|
args[#args + 1] = "core.autocrlf=false"
|
|
|
|
args[#args + 1] = "--progress"
|
|
|
|
if self.plugin.branch then
|
|
vim.list_extend(args, { "-b", self.plugin.branch })
|
|
end
|
|
|
|
table.insert(args, self.plugin.dir)
|
|
|
|
if vim.fn.isdirectory(self.plugin.dir) == 1 then
|
|
require("lazy.manage.task.fs").clean.run(self, {})
|
|
end
|
|
|
|
local marker = self.plugin.dir .. ".cloning"
|
|
Util.write_file(marker, "")
|
|
|
|
self:spawn("git", {
|
|
args = args,
|
|
on_exit = function(ok)
|
|
if ok then
|
|
self.plugin._.cloned = true
|
|
self.plugin._.installed = true
|
|
self.plugin._.dirty = true
|
|
vim.uv.fs_unlink(marker)
|
|
end
|
|
end,
|
|
})
|
|
end,
|
|
}
|
|
|
|
-- setup origin branches if needed
|
|
-- fetch will retrieve the data
|
|
M.branch = {
|
|
skip = function(plugin)
|
|
if not plugin._.installed or plugin._.is_local then
|
|
return true
|
|
end
|
|
local branch = assert(Git.get_branch(plugin))
|
|
return Git.get_commit(plugin.dir, branch, true)
|
|
end,
|
|
---@async
|
|
run = function(self)
|
|
local args = {
|
|
"remote",
|
|
"set-branches",
|
|
"--add",
|
|
"origin",
|
|
assert(Git.get_branch(self.plugin)),
|
|
}
|
|
|
|
self:spawn("git", {
|
|
args = args,
|
|
cwd = self.plugin.dir,
|
|
})
|
|
end,
|
|
}
|
|
|
|
-- check and switch origin
|
|
M.origin = {
|
|
skip = function(plugin)
|
|
if not plugin._.installed or plugin._.is_local then
|
|
return true
|
|
end
|
|
local origin = Git.get_origin(plugin.dir)
|
|
return origin == plugin.url
|
|
end,
|
|
---@async
|
|
---@param opts {check?:boolean}
|
|
run = function(self, opts)
|
|
if opts.check then
|
|
local origin = Git.get_origin(self.plugin.dir)
|
|
self:error({
|
|
"Origin has changed:",
|
|
" * old: " .. origin,
|
|
" * new: " .. self.plugin.url,
|
|
"Please run update to fix",
|
|
})
|
|
return
|
|
end
|
|
require("lazy.manage.task.fs").clean.run(self, opts)
|
|
M.clone.run(self, opts)
|
|
end,
|
|
}
|
|
|
|
M.status = {
|
|
skip = function(plugin)
|
|
return not plugin._.installed or plugin._.is_local
|
|
end,
|
|
---@async
|
|
run = function(self)
|
|
self:spawn("git", {
|
|
args = { "ls-files", "-d", "-m" },
|
|
cwd = self.plugin.dir,
|
|
on_exit = function(ok, output)
|
|
if ok then
|
|
local lines = vim.split(output, "\n")
|
|
---@type string[]
|
|
lines = vim.tbl_filter(function(line)
|
|
-- Fix doc/tags being marked as modified
|
|
if line:gsub("[\\/]", "/") == "doc/tags" then
|
|
local Process = require("lazy.manage.process")
|
|
Process.exec({ "git", "checkout", "--", "doc/tags" }, { cwd = self.plugin.dir })
|
|
return false
|
|
end
|
|
return line ~= ""
|
|
end, lines)
|
|
if #lines > 0 then
|
|
local msg = { "You have local changes in `" .. self.plugin.dir .. "`:" }
|
|
for _, line in ipairs(lines) do
|
|
msg[#msg + 1] = " * " .. line
|
|
end
|
|
msg[#msg + 1] = "Please remove them to update."
|
|
msg[#msg + 1] = "You can also press `x` to remove the plugin and then `I` to install it again."
|
|
self:error(msg)
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
end,
|
|
}
|
|
|
|
-- fetches all needed origin branches
|
|
M.fetch = {
|
|
skip = function(plugin)
|
|
return not plugin._.installed or plugin._.is_local
|
|
end,
|
|
|
|
---@async
|
|
run = function(self)
|
|
local args = {
|
|
"fetch",
|
|
"--recurse-submodules",
|
|
"--tags", -- also fetch remote tags
|
|
"--force", -- overwrite existing tags if needed
|
|
"--progress",
|
|
}
|
|
|
|
if self.plugin.submodules == false then
|
|
table.remove(args, 2)
|
|
end
|
|
|
|
self:spawn("git", {
|
|
args = args,
|
|
cwd = self.plugin.dir,
|
|
})
|
|
end,
|
|
}
|
|
|
|
-- checkout to the target commit
|
|
-- branches will exists at this point, so so will the commit
|
|
M.checkout = {
|
|
skip = function(plugin)
|
|
return not plugin._.installed or plugin._.is_local
|
|
end,
|
|
|
|
---@async
|
|
---@param opts {lockfile?:boolean}
|
|
run = function(self, opts)
|
|
local info = assert(Git.info(self.plugin.dir))
|
|
local target = assert(Git.get_target(self.plugin))
|
|
|
|
-- if the plugin is pinned and we did not just clone it,
|
|
-- then don't update
|
|
if self.plugin.pin and not self.plugin._.cloned then
|
|
target = info
|
|
end
|
|
|
|
local lock
|
|
if opts.lockfile then
|
|
-- restore to the lock if it exists
|
|
lock = Lock.get(self.plugin)
|
|
if lock then
|
|
---@diagnostic disable-next-line: cast-local-type
|
|
target = lock
|
|
end
|
|
end
|
|
|
|
-- dont run checkout if target is already reached.
|
|
-- unless we just cloned, since then we won't have any data yet
|
|
if Git.eq(info, target) and info.branch == target.branch then
|
|
self.plugin._.updated = {
|
|
from = info.commit,
|
|
to = info.commit,
|
|
}
|
|
return
|
|
end
|
|
|
|
local args = {
|
|
"checkout",
|
|
"--progress",
|
|
"--recurse-submodules",
|
|
}
|
|
|
|
if self.plugin.submodules == false then
|
|
table.remove(args, 3)
|
|
end
|
|
|
|
if lock then
|
|
table.insert(args, lock.commit)
|
|
elseif target.tag then
|
|
table.insert(args, "tags/" .. target.tag)
|
|
elseif self.plugin.commit then
|
|
table.insert(args, self.plugin.commit)
|
|
else
|
|
table.insert(args, target.commit)
|
|
end
|
|
|
|
self:spawn("git", {
|
|
args = args,
|
|
cwd = self.plugin.dir,
|
|
on_exit = function(ok)
|
|
if ok then
|
|
local new_info = assert(Git.info(self.plugin.dir))
|
|
if not self.plugin._.cloned then
|
|
self.plugin._.updated = {
|
|
from = info.commit,
|
|
to = new_info.commit,
|
|
}
|
|
if self.plugin._.updated.from ~= self.plugin._.updated.to then
|
|
self.plugin._.dirty = true
|
|
end
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
end,
|
|
}
|
|
return M
|