2022-12-01 06:12:00 +08:00
|
|
|
local Config = require("lazy.core.config")
|
2022-12-21 17:13:18 +08:00
|
|
|
local Process = require("lazy.manage.process")
|
2023-10-09 17:25:42 +08:00
|
|
|
local Semver = require("lazy.manage.semver")
|
|
|
|
local Util = require("lazy.util")
|
2022-11-28 18:36:12 +08:00
|
|
|
|
|
|
|
local M = {}
|
|
|
|
|
2022-11-29 05:03:44 +08:00
|
|
|
---@alias GitInfo {branch?:string, commit?:string, tag?:string, version?:Semver}
|
|
|
|
|
2022-12-21 17:13:18 +08:00
|
|
|
---@param repo string
|
|
|
|
---@param details? boolean Fetching details is slow! Don't loop over a plugin to fetch all details!
|
2022-11-29 05:03:44 +08:00
|
|
|
---@return GitInfo?
|
2022-11-28 18:36:12 +08:00
|
|
|
function M.info(repo, details)
|
2022-12-31 16:32:35 +08:00
|
|
|
local line = M.head(repo)
|
2022-11-28 18:36:12 +08:00
|
|
|
if line then
|
|
|
|
---@type string, string
|
2022-12-31 16:32:35 +08:00
|
|
|
local ref, branch = line:match("ref: refs/(heads/(.*))")
|
2022-11-28 18:36:12 +08:00
|
|
|
local ret = ref and {
|
|
|
|
branch = branch,
|
2022-12-31 16:32:35 +08:00
|
|
|
commit = M.ref(repo, ref),
|
2022-11-28 18:36:12 +08:00
|
|
|
} or { commit = line }
|
|
|
|
|
|
|
|
if details then
|
2022-12-21 17:13:18 +08:00
|
|
|
for tag, tag_ref in pairs(M.get_tag_refs(repo)) do
|
|
|
|
if tag_ref == ret.commit then
|
|
|
|
ret.tag = tag
|
2022-12-31 16:36:08 +08:00
|
|
|
ret.version = ret.version or Semver.version(tag)
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
2022-12-21 17:13:18 +08:00
|
|
|
end
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
|
|
|
return ret
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-01-03 16:36:35 +08:00
|
|
|
---@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
|
|
|
|
|
2022-12-31 16:32:35 +08:00
|
|
|
function M.head(repo)
|
|
|
|
return Util.head(repo .. "/.git/HEAD")
|
|
|
|
end
|
|
|
|
|
2022-11-28 18:36:12 +08:00
|
|
|
---@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 = {}
|
2022-12-31 16:32:35 +08:00
|
|
|
for _, tag in ipairs(M.get_tags(repo)) do
|
|
|
|
local v = Semver.version(tag)
|
2022-11-28 18:36:12 +08:00
|
|
|
---@cast v TaggedSemver
|
|
|
|
if v and range:matches(v) then
|
2022-12-31 16:32:35 +08:00
|
|
|
v.tag = tag
|
2022-11-28 18:36:12 +08:00
|
|
|
table.insert(versions, v)
|
|
|
|
end
|
2022-12-31 16:32:35 +08:00
|
|
|
end
|
2022-11-28 18:36:12 +08:00
|
|
|
return versions
|
|
|
|
end
|
|
|
|
|
2022-12-31 16:32:35 +08:00
|
|
|
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
|
|
|
|
|
2022-11-28 18:36:12 +08:00
|
|
|
---@param plugin LazyPlugin
|
2022-12-02 18:26:07 +08:00
|
|
|
---@return string?
|
2022-11-28 18:36:12 +08:00
|
|
|
function M.get_branch(plugin)
|
|
|
|
if plugin.branch then
|
2022-12-02 18:26:07 +08:00
|
|
|
return plugin.branch
|
2022-11-28 18:36:12 +08:00
|
|
|
else
|
2022-12-02 18:26:07 +08:00
|
|
|
-- we need to return the default branch
|
|
|
|
-- Try origin first
|
2022-11-28 18:36:12 +08:00
|
|
|
local main = M.ref(plugin.dir, "remotes/origin/HEAD")
|
|
|
|
if main then
|
|
|
|
local branch = main:match("ref: refs/remotes/origin/(.*)")
|
|
|
|
if branch then
|
2022-12-02 18:26:07 +08:00
|
|
|
return branch
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
|
|
|
end
|
2022-12-02 18:26:07 +08:00
|
|
|
|
|
|
|
-- fallback to local HEAD
|
2022-12-31 16:32:35 +08:00
|
|
|
main = assert(M.head(plugin.dir))
|
2022-12-02 18:26:07 +08:00
|
|
|
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)
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
---@param plugin LazyPlugin
|
2022-11-29 05:03:44 +08:00
|
|
|
---@return GitInfo?
|
2022-11-28 18:36:12 +08:00
|
|
|
function M.get_target(plugin)
|
2024-07-07 23:13:49 +08:00
|
|
|
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
|
|
|
|
|
2022-12-02 18:26:07 +08:00
|
|
|
local branch = assert(M.get_branch(plugin))
|
2022-11-28 18:36:12 +08:00
|
|
|
|
|
|
|
if plugin.commit then
|
2022-11-29 05:03:44 +08:00
|
|
|
return {
|
2022-12-02 18:26:07 +08:00
|
|
|
branch = branch,
|
2022-11-29 05:03:44 +08:00
|
|
|
commit = plugin.commit,
|
|
|
|
}
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
|
|
|
if plugin.tag then
|
2022-11-29 05:03:44 +08:00
|
|
|
return {
|
2022-12-02 18:26:07 +08:00
|
|
|
branch = branch,
|
2022-11-29 05:03:44 +08:00
|
|
|
tag = plugin.tag,
|
|
|
|
commit = M.ref(plugin.dir, "tags/" .. plugin.tag),
|
|
|
|
}
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
2023-01-17 20:14:25 +08:00
|
|
|
|
|
|
|
local version = (plugin.version == nil and plugin.branch == nil) and Config.options.defaults.version or plugin.version
|
2022-12-01 06:12:00 +08:00
|
|
|
if version then
|
|
|
|
local last = Semver.last(M.get_versions(plugin.dir, version))
|
2022-11-28 18:36:12 +08:00
|
|
|
if last then
|
2022-11-29 05:03:44 +08:00
|
|
|
return {
|
2022-12-02 18:26:07 +08:00
|
|
|
branch = branch,
|
2022-11-29 05:03:44 +08:00
|
|
|
version = last,
|
|
|
|
tag = last.tag,
|
|
|
|
commit = M.ref(plugin.dir, "tags/" .. last.tag),
|
|
|
|
}
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
|
|
|
end
|
2024-07-07 14:42:19 +08:00
|
|
|
return { branch = branch, commit = M.get_commit(plugin.dir, branch, true) }
|
|
|
|
end
|
|
|
|
|
2022-12-02 18:26:07 +08:00
|
|
|
function M.ref(repo, ...)
|
2022-12-21 17:13:18 +08:00
|
|
|
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
|
2022-12-31 16:32:35 +08:00
|
|
|
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
|
2022-12-21 17:13:18 +08:00
|
|
|
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 = {}
|
2024-06-29 00:31:10 +08:00
|
|
|
local ok, lines = pcall(function()
|
|
|
|
return Process.exec({ "git", "show-ref", "-d", tagref }, { cwd = repo })
|
|
|
|
end)
|
|
|
|
if not ok then
|
|
|
|
return {}
|
|
|
|
end
|
2022-12-21 17:13:18 +08:00
|
|
|
for _, line in ipairs(lines) do
|
|
|
|
local ref, tag = line:match("^(%w+) refs/tags/([^%^]+)%^?{?}?$")
|
|
|
|
if ref then
|
|
|
|
tags[tag] = ref
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return tags
|
2022-11-28 18:36:12 +08:00
|
|
|
end
|
|
|
|
|
2023-01-08 15:04:34 +08:00
|
|
|
---@param repo string
|
|
|
|
function M.get_origin(repo)
|
2023-03-05 21:09:15 +08:00
|
|
|
return M.get_config(repo)["remote.origin.url"]
|
2023-01-08 15:04:34 +08:00
|
|
|
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
|
2024-01-20 22:05:26 +08:00
|
|
|
if not line:match("^%s*[#;]") and line:match("%S") then
|
2023-01-08 15:04:34 +08:00
|
|
|
local key, value = line:match("^%s*(%S+)%s*=%s*(.+)%s*$")
|
|
|
|
ret[current_section .. "." .. key] = value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
return ret
|
|
|
|
end
|
|
|
|
|
2023-01-17 03:41:29 +08:00
|
|
|
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
|
|
|
|
|
2022-11-28 18:36:12 +08:00
|
|
|
return M
|