mirror of
https://github.com/folke/lazy.nvim.git
synced 2025-04-04 06:57:34 +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
249 lines
6.3 KiB
Lua
249 lines
6.3 KiB
Lua
local Config = require("lazy.core.config")
|
|
local Process = require("lazy.manage.process")
|
|
local Semver = require("lazy.manage.semver")
|
|
local Util = require("lazy.util")
|
|
|
|
local M = {}
|
|
|
|
---@alias GitInfo {branch?:string, commit?:string, tag?:string, version?:Semver}
|
|
|
|
---@param repo string
|
|
---@param details? boolean Fetching details is slow! Don't loop over a plugin to fetch all details!
|
|
---@return GitInfo?
|
|
function M.info(repo, details)
|
|
local line = M.head(repo)
|
|
if line then
|
|
---@type string, string
|
|
local ref, branch = line:match("ref: refs/(heads/(.*))")
|
|
local ret = ref and {
|
|
branch = branch,
|
|
commit = M.ref(repo, ref),
|
|
} or { commit = line }
|
|
|
|
if details then
|
|
for tag, tag_ref in pairs(M.get_tag_refs(repo)) do
|
|
if tag_ref == ret.commit then
|
|
ret.tag = tag
|
|
ret.version = ret.version or Semver.version(tag)
|
|
end
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
end
|
|
|
|
---@param a GitInfo
|
|
---@param b GitInfo
|
|
function M.eq(a, b)
|
|
local ra = a.commit and a.commit:sub(1, 7)
|
|
local rb = b.commit and b.commit:sub(1, 7)
|
|
return ra == rb
|
|
end
|
|
|
|
function M.head(repo)
|
|
return Util.head(repo .. "/.git/HEAD")
|
|
end
|
|
|
|
---@class TaggedSemver: Semver
|
|
---@field tag string
|
|
|
|
---@param spec? string
|
|
function M.get_versions(repo, spec)
|
|
local range = Semver.range(spec or "*")
|
|
---@type TaggedSemver[]
|
|
local versions = {}
|
|
for _, tag in ipairs(M.get_tags(repo)) do
|
|
local v = Semver.version(tag)
|
|
---@cast v TaggedSemver
|
|
if v and range:matches(v) then
|
|
v.tag = tag
|
|
table.insert(versions, v)
|
|
end
|
|
end
|
|
return versions
|
|
end
|
|
|
|
function M.get_tags(repo)
|
|
---@type string[]
|
|
local ret = {}
|
|
Util.ls(repo .. "/.git/refs/tags", function(_, name)
|
|
ret[#ret + 1] = name
|
|
end)
|
|
for name in pairs(M.packed_refs(repo)) do
|
|
local tag = name:match("^tags/(.*)")
|
|
if tag then
|
|
ret[#ret + 1] = tag
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
---@param plugin LazyPlugin
|
|
---@return string?
|
|
function M.get_branch(plugin)
|
|
if plugin.branch then
|
|
return plugin.branch
|
|
else
|
|
-- we need to return the default branch
|
|
-- Try origin first
|
|
local main = M.ref(plugin.dir, "remotes/origin/HEAD")
|
|
if main then
|
|
local branch = main:match("ref: refs/remotes/origin/(.*)")
|
|
if branch then
|
|
return branch
|
|
end
|
|
end
|
|
|
|
-- fallback to local HEAD
|
|
main = assert(M.head(plugin.dir))
|
|
return main and main:match("ref: refs/heads/(.*)")
|
|
end
|
|
end
|
|
|
|
-- Return the last commit for the given branch
|
|
---@param repo string
|
|
---@param branch string
|
|
---@param origin? boolean
|
|
function M.get_commit(repo, branch, origin)
|
|
if origin then
|
|
-- origin ref might not exist if it is the same as local
|
|
return M.ref(repo, "remotes/origin", branch) or M.ref(repo, "heads", branch)
|
|
else
|
|
return M.ref(repo, "heads", branch)
|
|
end
|
|
end
|
|
|
|
---@param plugin LazyPlugin
|
|
---@return GitInfo?
|
|
function M.get_target(plugin)
|
|
if plugin._.is_local then
|
|
local info = M.info(plugin.dir)
|
|
local branch = assert(info and info.branch or M.get_branch(plugin))
|
|
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
|
|
end
|
|
|
|
local branch = assert(M.get_branch(plugin))
|
|
|
|
if plugin.commit then
|
|
return {
|
|
branch = branch,
|
|
commit = plugin.commit,
|
|
}
|
|
end
|
|
if plugin.tag then
|
|
return {
|
|
branch = branch,
|
|
tag = plugin.tag,
|
|
commit = M.ref(plugin.dir, "tags/" .. plugin.tag),
|
|
}
|
|
end
|
|
|
|
local version = (plugin.version == nil and plugin.branch == nil) and Config.options.defaults.version or plugin.version
|
|
if version then
|
|
local last = Semver.last(M.get_versions(plugin.dir, version))
|
|
if last then
|
|
return {
|
|
branch = branch,
|
|
version = last,
|
|
tag = last.tag,
|
|
commit = M.ref(plugin.dir, "tags/" .. last.tag),
|
|
}
|
|
end
|
|
end
|
|
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
|
|
end
|
|
|
|
function M.ref(repo, ...)
|
|
local ref = table.concat({ ... }, "/")
|
|
|
|
-- if this is a tag ref, then dereference it instead
|
|
if ref:find("tags/", 1, true) == 1 then
|
|
local tags = M.get_tag_refs(repo, ref)
|
|
for _, tag_ref in pairs(tags) do
|
|
return tag_ref
|
|
end
|
|
end
|
|
|
|
-- otherwise just get the ref
|
|
return Util.head(repo .. "/.git/refs/" .. ref) or M.packed_refs(repo)[ref]
|
|
end
|
|
|
|
function M.packed_refs(repo)
|
|
local ok, refs = pcall(Util.read_file, repo .. "/.git/packed-refs")
|
|
---@type table<string,string>
|
|
local ret = {}
|
|
if ok then
|
|
for _, line in ipairs(vim.split(refs, "\n")) do
|
|
local ref, name = line:match("^(.*) refs/(.*)$")
|
|
if ref then
|
|
ret[name] = ref
|
|
end
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
-- this is slow, so don't use on a loop over all plugins!
|
|
---@param tagref string?
|
|
function M.get_tag_refs(repo, tagref)
|
|
tagref = tagref or "--tags"
|
|
---@type table<string,string>
|
|
local tags = {}
|
|
local ok, lines = pcall(function()
|
|
return Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
|
|
end)
|
|
if not ok then
|
|
return {}
|
|
end
|
|
for _, line in ipairs(lines) do
|
|
local ref, tag = line:match("^(%w+) refs/tags/([^%^]+)%^?{?}?$")
|
|
if ref then
|
|
tags[tag] = ref
|
|
end
|
|
end
|
|
return tags
|
|
end
|
|
|
|
---@param repo string
|
|
function M.get_origin(repo)
|
|
return M.get_config(repo)["remote.origin.url"]
|
|
end
|
|
|
|
---@param repo string
|
|
function M.get_config(repo)
|
|
local ok, config = pcall(Util.read_file, repo .. "/.git/config")
|
|
if not ok then
|
|
return {}
|
|
end
|
|
---@type table<string, string>
|
|
local ret = {}
|
|
---@type string
|
|
local current_section = nil
|
|
for line in config:gmatch("[^\n]+") do
|
|
-- Check if the line is a section header
|
|
local section = line:match("^%s*%[(.+)%]%s*$")
|
|
if section then
|
|
---@type string
|
|
current_section = section:gsub('%s+"', "."):gsub('"+%s*$', "")
|
|
else
|
|
-- Ignore comments and blank lines
|
|
if not line:match("^%s*[#;]") and line:match("%S") then
|
|
local key, value = line:match("^%s*(%S+)%s*=%s*(.+)%s*$")
|
|
ret[current_section .. "." .. key] = value
|
|
end
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
function M.count(repo, commit1, commit2)
|
|
local lines = Process.exec({ "git", "rev-list", "--count", commit1 .. ".." .. commit2 }, { cwd = repo })
|
|
return tonumber(lines[1] or "0") or 0
|
|
end
|
|
|
|
function M.age(repo, commit)
|
|
local lines = Process.exec({ "git", "show", "-s", "--format=%cr", "--date=short", commit }, { cwd = repo })
|
|
return lines[1] or ""
|
|
end
|
|
|
|
return M
|