2024-07-16 15:50:31 +01:00
|
|
|
local Async = require("lazy.async")
|
2023-10-09 10:25:42 +01:00
|
|
|
local Config = require("lazy.core.config")
|
2022-11-28 10:36:12 +00:00
|
|
|
local Git = require("lazy.manage.git")
|
2022-11-28 23:15:13 +00:00
|
|
|
local Lock = require("lazy.manage.lock")
|
2023-02-28 10:51:16 +00:00
|
|
|
local Util = require("lazy.util")
|
2022-11-28 10:04:32 +00:00
|
|
|
|
2024-07-16 15:50:31 +01:00
|
|
|
local throttle = {}
|
|
|
|
throttle.running = 0
|
|
|
|
throttle.waiting = {} ---@type Async[]
|
|
|
|
throttle.timer = vim.uv.new_timer()
|
|
|
|
|
|
|
|
function throttle.next()
|
|
|
|
throttle.running = 0
|
|
|
|
while #throttle.waiting > 0 and throttle.running < Config.options.git.throttle.rate do
|
|
|
|
---@type Async
|
|
|
|
local task = table.remove(throttle.waiting, 1)
|
|
|
|
task:resume()
|
|
|
|
throttle.running = throttle.running + 1
|
|
|
|
end
|
|
|
|
if throttle.running == 0 then
|
|
|
|
throttle.timer:stop()
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
function throttle.wait()
|
|
|
|
if not Config.options.git.throttle.enabled then
|
|
|
|
return
|
|
|
|
end
|
|
|
|
if not throttle.timer:is_active() then
|
|
|
|
throttle.timer:start(0, Config.options.git.throttle.duration, vim.schedule_wrap(throttle.next))
|
|
|
|
end
|
|
|
|
local running = Async.running()
|
|
|
|
if throttle.running < Config.options.git.throttle.rate then
|
|
|
|
throttle.running = throttle.running + 1
|
|
|
|
else
|
|
|
|
table.insert(throttle.waiting, running)
|
|
|
|
coroutine.yield("waiting")
|
|
|
|
running:suspend()
|
|
|
|
coroutine.yield("")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-11-28 10:04:32 +00:00
|
|
|
---@type table<string, LazyTaskDef>
|
|
|
|
local M = {}
|
|
|
|
|
|
|
|
M.log = {
|
2022-11-29 14:25:09 +00:00
|
|
|
---@param opts {updated?:boolean, check?: boolean}
|
2022-11-28 12:10:52 +00:00
|
|
|
skip = function(plugin, opts)
|
2022-12-21 13:39:08 +00:00
|
|
|
if opts.check and plugin.pin then
|
|
|
|
return true
|
|
|
|
end
|
2022-11-28 21:03:44 +00:00
|
|
|
if opts.updated and not (plugin._.updated and plugin._.updated.from ~= plugin._.updated.to) then
|
|
|
|
return true
|
2022-11-28 10:04:32 +00:00
|
|
|
end
|
2024-03-22 07:58:36 +00:00
|
|
|
local stat = vim.uv.fs_stat(plugin.dir .. "/.git")
|
2023-02-01 07:26:20 +00:00
|
|
|
return not (stat and stat.type == "directory")
|
2022-11-28 10:04:32 +00:00
|
|
|
end,
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2022-11-29 14:25:09 +00:00
|
|
|
---@param opts {args?: string[], updated?:boolean, check?:boolean}
|
2022-11-28 21:03:44 +00:00
|
|
|
run = function(self, opts)
|
2024-06-30 07:48:03 +01:00
|
|
|
-- self:spawn({ "sleep", "5" })
|
2022-11-28 10:04:32 +00:00
|
|
|
local args = {
|
|
|
|
"log",
|
|
|
|
"--pretty=format:%h %s (%cr)",
|
|
|
|
"--abbrev-commit",
|
|
|
|
"--decorate",
|
|
|
|
"--date=short",
|
|
|
|
"--color=never",
|
2022-12-29 00:02:05 +00:00
|
|
|
"--no-show-signature",
|
2022-11-28 10:04:32 +00:00
|
|
|
}
|
|
|
|
|
2024-07-07 16:13:49 +01:00
|
|
|
local info, target
|
|
|
|
|
2022-11-28 21:03:44 +00:00
|
|
|
if opts.updated then
|
2022-11-28 10:19:50 +00:00
|
|
|
table.insert(args, self.plugin._.updated.from .. ".." .. (self.plugin._.updated.to or "HEAD"))
|
2022-11-29 07:23:23 +00:00
|
|
|
elseif opts.check then
|
2024-07-07 16:13:49 +01:00
|
|
|
info = assert(Git.info(self.plugin.dir))
|
|
|
|
target = assert(Git.get_target(self.plugin))
|
2023-01-02 18:01:02 +00:00
|
|
|
if not target.commit then
|
|
|
|
for k, v in pairs(target) do
|
|
|
|
error(k .. " '" .. v .. "' not found")
|
|
|
|
end
|
|
|
|
error("no target commit found")
|
|
|
|
end
|
2022-12-02 10:26:07 +00:00
|
|
|
assert(target.commit, self.plugin.name .. " " .. target.branch)
|
2024-07-07 16:13:49 +01:00
|
|
|
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
|
2023-10-23 06:52:54 +01:00
|
|
|
end
|
2024-07-07 16:13:49 +01:00
|
|
|
else
|
|
|
|
self.plugin._.updates = { from = info, to = target }
|
2023-10-23 06:52:54 +01:00
|
|
|
end
|
2022-12-31 15:01:59 +00:00
|
|
|
end
|
2022-11-29 07:23:23 +00:00
|
|
|
table.insert(args, info.commit .. ".." .. target.commit)
|
2022-11-29 09:55:49 +00:00
|
|
|
else
|
2022-11-29 14:25:09 +00:00
|
|
|
vim.list_extend(args, opts.args or Config.options.git.log)
|
2022-11-28 10:04:32 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
self:spawn("git", {
|
|
|
|
args = args,
|
|
|
|
cwd = self.plugin.dir,
|
|
|
|
})
|
2024-07-07 16:13:49 +01:00
|
|
|
|
|
|
|
-- 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
|
2022-11-28 10:04:32 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
2022-11-28 21:03:44 +00:00
|
|
|
M.clone = {
|
|
|
|
skip = function(plugin)
|
|
|
|
return plugin._.installed or plugin._.is_local
|
|
|
|
end,
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2022-11-28 10:04:32 +00:00
|
|
|
run = function(self)
|
2024-07-16 15:50:31 +01:00
|
|
|
throttle.wait()
|
2022-11-28 21:03:44 +00:00
|
|
|
local args = {
|
|
|
|
"clone",
|
2022-12-06 10:12:54 +00:00
|
|
|
self.plugin.url,
|
2023-01-23 18:18:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if Config.options.git.filter then
|
|
|
|
args[#args + 1] = "--filter=blob:none"
|
|
|
|
end
|
|
|
|
|
2023-02-12 11:56:42 +00:00
|
|
|
if self.plugin.submodules ~= false then
|
|
|
|
args[#args + 1] = "--recurse-submodules"
|
|
|
|
end
|
|
|
|
|
2023-03-05 13:09:15 +00:00
|
|
|
args[#args + 1] = "--origin=origin"
|
|
|
|
|
2024-05-13 07:34:33 +01:00
|
|
|
-- 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"
|
|
|
|
|
2023-02-12 11:56:42 +00:00
|
|
|
args[#args + 1] = "--progress"
|
2022-11-28 21:03:44 +00:00
|
|
|
|
|
|
|
if self.plugin.branch then
|
|
|
|
vim.list_extend(args, { "-b", self.plugin.branch })
|
2022-11-28 12:10:52 +00:00
|
|
|
end
|
2022-11-28 21:03:44 +00:00
|
|
|
|
|
|
|
table.insert(args, self.plugin.dir)
|
2023-02-28 10:51:16 +00:00
|
|
|
|
|
|
|
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, "")
|
|
|
|
|
2022-11-28 21:03:44 +00:00
|
|
|
self:spawn("git", {
|
|
|
|
args = args,
|
|
|
|
on_exit = function(ok)
|
|
|
|
if ok then
|
|
|
|
self.plugin._.cloned = true
|
|
|
|
self.plugin._.installed = true
|
|
|
|
self.plugin._.dirty = true
|
2024-03-22 07:58:36 +00:00
|
|
|
vim.uv.fs_unlink(marker)
|
2022-11-28 21:03:44 +00:00
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
2022-12-02 10:26:07 +00:00
|
|
|
-- setup origin branches if needed
|
|
|
|
-- fetch will retrieve the data
|
2022-11-28 21:03:44 +00:00
|
|
|
M.branch = {
|
|
|
|
skip = function(plugin)
|
|
|
|
if not plugin._.installed or plugin._.is_local then
|
|
|
|
return true
|
2022-11-28 10:04:32 +00:00
|
|
|
end
|
2022-11-28 21:03:44 +00:00
|
|
|
local branch = assert(Git.get_branch(plugin))
|
2024-06-26 06:14:42 +01:00
|
|
|
return Git.get_commit(plugin.dir, branch, true)
|
2022-11-28 21:03:44 +00:00
|
|
|
end,
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2022-11-28 21:03:44 +00:00
|
|
|
run = function(self)
|
|
|
|
local args = {
|
|
|
|
"remote",
|
|
|
|
"set-branches",
|
|
|
|
"--add",
|
|
|
|
"origin",
|
2022-12-02 10:26:07 +00:00
|
|
|
assert(Git.get_branch(self.plugin)),
|
2022-11-28 21:03:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self:spawn("git", {
|
|
|
|
args = args,
|
|
|
|
cwd = self.plugin.dir,
|
|
|
|
})
|
2022-11-28 10:04:32 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
2023-01-08 07:32:03 +00:00
|
|
|
-- 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,
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2023-01-08 07:32:03 +00:00
|
|
|
---@param opts {check?:boolean}
|
|
|
|
run = function(self, opts)
|
|
|
|
if opts.check then
|
|
|
|
local origin = Git.get_origin(self.plugin.dir)
|
2024-06-26 17:31:31 +01:00
|
|
|
self:error({
|
|
|
|
"Origin has changed:",
|
|
|
|
" * old: " .. origin,
|
|
|
|
" * new: " .. self.plugin.url,
|
|
|
|
"Please run update to fix",
|
|
|
|
})
|
2023-01-08 07:32:03 +00:00
|
|
|
return
|
|
|
|
end
|
|
|
|
require("lazy.manage.task.fs").clean.run(self, opts)
|
|
|
|
M.clone.run(self, opts)
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
2023-10-10 10:41:32 +01:00
|
|
|
M.status = {
|
|
|
|
skip = function(plugin)
|
|
|
|
return not plugin._.installed or plugin._.is_local
|
|
|
|
end,
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2023-10-10 10:41:32 +01:00
|
|
|
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")
|
2024-06-26 17:31:31 +01:00
|
|
|
---@type string[]
|
2023-10-10 10:41:32 +01:00
|
|
|
lines = vim.tbl_filter(function(line)
|
2023-10-10 10:41:49 +01:00
|
|
|
-- 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
|
2023-10-10 10:41:32 +01:00
|
|
|
return line ~= ""
|
|
|
|
end, lines)
|
|
|
|
if #lines > 0 then
|
2024-06-26 17:31:31 +01:00
|
|
|
local msg = { "You have local changes in `" .. self.plugin.dir .. "`:" }
|
2023-10-10 10:41:32 +01:00
|
|
|
for _, line in ipairs(lines) do
|
2024-06-26 17:31:31 +01:00
|
|
|
msg[#msg + 1] = " * " .. line
|
2023-10-10 10:41:32 +01:00
|
|
|
end
|
2024-06-26 17:31:31 +01:00
|
|
|
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)
|
2023-10-10 10:41:32 +01:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
2022-12-02 10:26:07 +00:00
|
|
|
-- fetches all needed origin branches
|
2022-11-28 21:03:44 +00:00
|
|
|
M.fetch = {
|
|
|
|
skip = function(plugin)
|
|
|
|
return not plugin._.installed or plugin._.is_local
|
|
|
|
end,
|
2022-11-30 22:05:51 +00:00
|
|
|
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2022-11-28 10:04:32 +00:00
|
|
|
run = function(self)
|
2024-07-16 15:50:31 +01:00
|
|
|
throttle.wait()
|
2022-11-28 21:03:44 +00:00
|
|
|
local args = {
|
|
|
|
"fetch",
|
|
|
|
"--recurse-submodules",
|
2022-12-31 14:55:06 +00:00
|
|
|
"--tags", -- also fetch remote tags
|
|
|
|
"--force", -- overwrite existing tags if needed
|
2022-11-28 21:03:44 +00:00
|
|
|
"--progress",
|
|
|
|
}
|
|
|
|
|
2023-02-12 11:56:42 +00:00
|
|
|
if self.plugin.submodules == false then
|
|
|
|
table.remove(args, 2)
|
|
|
|
end
|
|
|
|
|
2022-11-28 21:03:44 +00:00
|
|
|
self:spawn("git", {
|
|
|
|
args = args,
|
|
|
|
cwd = self.plugin.dir,
|
|
|
|
})
|
|
|
|
end,
|
|
|
|
}
|
|
|
|
|
2022-12-02 10:26:07 +00:00
|
|
|
-- checkout to the target commit
|
|
|
|
-- branches will exists at this point, so so will the commit
|
2022-11-28 21:03:44 +00:00
|
|
|
M.checkout = {
|
|
|
|
skip = function(plugin)
|
|
|
|
return not plugin._.installed or plugin._.is_local
|
|
|
|
end,
|
2022-11-30 22:05:51 +00:00
|
|
|
|
2024-06-26 17:31:31 +01:00
|
|
|
---@async
|
2022-11-28 23:15:13 +00:00
|
|
|
---@param opts {lockfile?:boolean}
|
|
|
|
run = function(self, opts)
|
2024-07-16 15:50:31 +01:00
|
|
|
throttle.wait()
|
2022-11-28 21:03:44 +00:00
|
|
|
local info = assert(Git.info(self.plugin.dir))
|
|
|
|
local target = assert(Git.get_target(self.plugin))
|
|
|
|
|
2022-12-02 10:26:07 +00:00
|
|
|
-- if the plugin is pinned and we did not just clone it,
|
2022-11-29 13:27:32 +00:00
|
|
|
-- then don't update
|
2022-11-29 19:19:07 +00:00
|
|
|
if self.plugin.pin and not self.plugin._.cloned then
|
2022-11-29 11:36:07 +00:00
|
|
|
target = info
|
|
|
|
end
|
|
|
|
|
2022-11-28 23:15:13 +00:00
|
|
|
local lock
|
|
|
|
if opts.lockfile then
|
2022-12-02 10:26:07 +00:00
|
|
|
-- restore to the lock if it exists
|
2022-11-28 23:15:13 +00:00
|
|
|
lock = Lock.get(self.plugin)
|
|
|
|
if lock then
|
|
|
|
---@diagnostic disable-next-line: cast-local-type
|
|
|
|
target = lock
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-12-02 10:26:07 +00:00
|
|
|
-- dont run checkout if target is already reached.
|
2022-12-21 22:13:18 +00:00
|
|
|
-- unless we just cloned, since then we won't have any data yet
|
2023-01-03 08:36:35 +00:00
|
|
|
if Git.eq(info, target) and info.branch == target.branch then
|
2022-11-29 09:30:45 +00:00
|
|
|
self.plugin._.updated = {
|
|
|
|
from = info.commit,
|
|
|
|
to = info.commit,
|
|
|
|
}
|
2022-11-28 21:03:44 +00:00
|
|
|
return
|
2022-11-28 10:04:32 +00:00
|
|
|
end
|
2022-11-28 21:03:44 +00:00
|
|
|
|
|
|
|
local args = {
|
|
|
|
"checkout",
|
|
|
|
"--progress",
|
2022-12-21 22:13:18 +00:00
|
|
|
"--recurse-submodules",
|
2022-11-28 21:03:44 +00:00
|
|
|
}
|
|
|
|
|
2023-02-12 11:56:42 +00:00
|
|
|
if self.plugin.submodules == false then
|
|
|
|
table.remove(args, 3)
|
|
|
|
end
|
|
|
|
|
2022-11-28 23:15:13 +00:00
|
|
|
if lock then
|
|
|
|
table.insert(args, lock.commit)
|
|
|
|
elseif target.tag then
|
2022-11-28 21:03:44 +00:00
|
|
|
table.insert(args, "tags/" .. target.tag)
|
|
|
|
elseif self.plugin.commit then
|
|
|
|
table.insert(args, self.plugin.commit)
|
2022-12-02 10:26:07 +00:00
|
|
|
else
|
|
|
|
table.insert(args, target.commit)
|
2022-11-28 21:03:44 +00:00
|
|
|
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,
|
|
|
|
}
|
2022-12-21 13:45:32 +00:00
|
|
|
if self.plugin._.updated.from ~= self.plugin._.updated.to then
|
|
|
|
self.plugin._.dirty = true
|
|
|
|
end
|
2022-11-28 21:03:44 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end,
|
|
|
|
})
|
2022-11-28 10:04:32 +00:00
|
|
|
end,
|
|
|
|
}
|
|
|
|
return M
|