refactor(git): split watcher out of repo
This commit is contained in:
+5
-5
@@ -29,26 +29,26 @@ function M.init()
|
|||||||
{
|
{
|
||||||
group = group,
|
group = group,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
require("git.repo").refresh_buf(args.buf)
|
require("git.watcher").refresh_buf(args.buf)
|
||||||
end,
|
end,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
vim.api.nvim_create_autocmd({ "BufDelete", "BufWipeout" }, {
|
vim.api.nvim_create_autocmd({ "BufDelete", "BufWipeout" }, {
|
||||||
group = group,
|
group = group,
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
require("git.repo").unregister(args.buf)
|
require("git.watcher").unregister(args.buf)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd("FocusGained", {
|
vim.api.nvim_create_autocmd("FocusGained", {
|
||||||
group = group,
|
group = group,
|
||||||
callback = function()
|
callback = function(args)
|
||||||
require("git.repo").refresh_buf(vim.api.nvim_get_current_buf())
|
require("git.watcher").refresh_buf(args.buf)
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
vim.api.nvim_create_autocmd("VimLeavePre", {
|
vim.api.nvim_create_autocmd("VimLeavePre", {
|
||||||
group = group,
|
group = group,
|
||||||
callback = function()
|
callback = function()
|
||||||
require("git.repo").stop_all()
|
require("git.watcher").stop_all()
|
||||||
end,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -41,16 +41,6 @@ function M.indicator(code)
|
|||||||
return "M", "GitUnstaged"
|
return "M", "GitUnstaged"
|
||||||
end
|
end
|
||||||
|
|
||||||
---@param code string
|
|
||||||
---@return string?
|
|
||||||
local function format(code)
|
|
||||||
local char, hl = M.indicator(code)
|
|
||||||
if not char then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return string.format("%%#%s#%s%%*", hl, char)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string? gitdir
|
---@return string? gitdir
|
||||||
---@return string? worktree
|
---@return string? worktree
|
||||||
@@ -94,196 +84,6 @@ function M.resolve_cwd()
|
|||||||
return M.resolve(path)
|
return M.resolve(path)
|
||||||
end
|
end
|
||||||
|
|
||||||
---@class ow.Git.Repo
|
|
||||||
---@field gitdir string
|
|
||||||
---@field worktree string
|
|
||||||
---@field buffers table<integer, true>
|
|
||||||
---@field watcher? uv.uv_fs_event_t
|
|
||||||
---@field refresh fun(self: ow.Git.Repo)
|
|
||||||
---@field refresh_handle ow.Git.Util.DebounceHandle
|
|
||||||
local Repo = {}
|
|
||||||
Repo.__index = Repo
|
|
||||||
|
|
||||||
function Repo:start_watcher()
|
|
||||||
local watcher = assert(vim.uv.new_fs_event())
|
|
||||||
assert(watcher:start(self.gitdir, {}, function(err, filename)
|
|
||||||
if err or (filename ~= "index" and filename ~= "HEAD") then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
self:refresh()
|
|
||||||
end))
|
|
||||||
self.watcher = watcher
|
|
||||||
end
|
|
||||||
|
|
||||||
function Repo:stop_watcher()
|
|
||||||
-- Stop the libuv watcher first so no further fs-events can trigger
|
|
||||||
-- self:refresh(). Tearing down the debounce handle first leaves a
|
|
||||||
-- window where an in-flight watcher callback hits a closed timer.
|
|
||||||
if self.watcher then
|
|
||||||
self.watcher:stop()
|
|
||||||
self.watcher:close()
|
|
||||||
self.watcher = nil
|
|
||||||
end
|
|
||||||
self.refresh_handle.close()
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer
|
|
||||||
function Repo:add_buffer(buf)
|
|
||||||
self.buffers[buf] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer
|
|
||||||
function Repo:remove_buffer(buf)
|
|
||||||
self.buffers[buf] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
---@return boolean
|
|
||||||
function Repo:has_buffers()
|
|
||||||
return next(self.buffers) ~= nil
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param repo ow.Git.Repo
|
|
||||||
local function do_refresh(repo)
|
|
||||||
vim.system(
|
|
||||||
{
|
|
||||||
"git",
|
|
||||||
"-c",
|
|
||||||
"core.quotePath=false",
|
|
||||||
"status",
|
|
||||||
"--porcelain=v1",
|
|
||||||
"--branch",
|
|
||||||
},
|
|
||||||
{ cwd = repo.worktree, text = true },
|
|
||||||
vim.schedule_wrap(function(obj)
|
|
||||||
local statuses = {}
|
|
||||||
if obj.code == 0 then
|
|
||||||
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
|
|
||||||
if line:sub(1, 2) ~= "##" then
|
|
||||||
local code = line:sub(1, 2)
|
|
||||||
local x = code:sub(1, 1)
|
|
||||||
local y = code:sub(2, 2)
|
|
||||||
local path_part = line:sub(4)
|
|
||||||
if x == "R" or x == "C" or y == "R" or y == "C" then
|
|
||||||
local arrow = path_part:find(" -> ", 1, true)
|
|
||||||
if arrow then
|
|
||||||
path_part = path_part:sub(arrow + 4)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
statuses[vim.fs.joinpath(repo.worktree, path_part)] =
|
|
||||||
format(code)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
util.warning(
|
|
||||||
"git status failed: %s",
|
|
||||||
vim.trim(obj.stderr or "")
|
|
||||||
)
|
|
||||||
end
|
|
||||||
local dirty = false
|
|
||||||
for buf in pairs(repo.buffers) do
|
|
||||||
if not vim.api.nvim_buf_is_valid(buf) then
|
|
||||||
repo.buffers[buf] = nil
|
|
||||||
else
|
|
||||||
local status = statuses[vim.api.nvim_buf_get_name(buf)]
|
|
||||||
if vim.b[buf].git_status ~= status then
|
|
||||||
vim.b[buf].git_status = status
|
|
||||||
dirty = true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if dirty then
|
|
||||||
vim.cmd.redrawstatus({ bang = true })
|
|
||||||
end
|
|
||||||
vim.api.nvim_exec_autocmds("User", {
|
|
||||||
pattern = "GitRefresh",
|
|
||||||
data = {
|
|
||||||
gitdir = repo.gitdir,
|
|
||||||
worktree = repo.worktree,
|
|
||||||
porcelain_stdout = obj.code == 0 and obj.stdout or nil,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
end)
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param gitdir string
|
|
||||||
---@param worktree string
|
|
||||||
---@return ow.Git.Repo
|
|
||||||
function Repo.new(gitdir, worktree)
|
|
||||||
local self = setmetatable({
|
|
||||||
gitdir = gitdir,
|
|
||||||
worktree = worktree,
|
|
||||||
buffers = {},
|
|
||||||
}, Repo)
|
|
||||||
self.refresh, self.refresh_handle = util.debounce(do_refresh, 50)
|
|
||||||
self:start_watcher()
|
|
||||||
return self
|
|
||||||
end
|
|
||||||
|
|
||||||
---@type table<string, ow.Git.Repo>
|
|
||||||
local repo_by_gitdir = {}
|
|
||||||
|
|
||||||
---@type table<integer, ow.Git.Repo>
|
|
||||||
local repo_by_buf = {}
|
|
||||||
|
|
||||||
---@param buf integer
|
|
||||||
---@return ow.Git.Repo?
|
|
||||||
local function register(buf)
|
|
||||||
local existing = repo_by_buf[buf]
|
|
||||||
if existing then
|
|
||||||
return existing
|
|
||||||
end
|
|
||||||
local path = vim.api.nvim_buf_get_name(buf)
|
|
||||||
if path == "" then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local gitdir, worktree = M.resolve(path)
|
|
||||||
if not gitdir or not worktree then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
local repo = repo_by_gitdir[gitdir]
|
|
||||||
if not repo then
|
|
||||||
repo = Repo.new(gitdir, worktree)
|
|
||||||
repo_by_gitdir[gitdir] = repo
|
|
||||||
end
|
|
||||||
repo:add_buffer(buf)
|
|
||||||
repo_by_buf[buf] = repo
|
|
||||||
return repo
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer
|
|
||||||
function M.unregister(buf)
|
|
||||||
local repo = repo_by_buf[buf]
|
|
||||||
if not repo then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
repo_by_buf[buf] = nil
|
|
||||||
repo:remove_buffer(buf)
|
|
||||||
if not repo:has_buffers() then
|
|
||||||
repo:stop_watcher()
|
|
||||||
repo_by_gitdir[repo.gitdir] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param buf integer
|
|
||||||
function M.refresh_buf(buf)
|
|
||||||
if not vim.api.nvim_buf_is_valid(buf) or vim.bo[buf].buftype ~= "" then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local repo = register(buf)
|
|
||||||
if not repo then
|
|
||||||
vim.b[buf].git_status = nil
|
|
||||||
return
|
|
||||||
end
|
|
||||||
repo:refresh()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.stop_all()
|
|
||||||
for _, repo in pairs(repo_by_gitdir) do
|
|
||||||
repo:stop_watcher()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
---@param path string
|
---@param path string
|
||||||
---@return string?
|
---@return string?
|
||||||
function M.head(path)
|
function M.head(path)
|
||||||
|
|||||||
@@ -0,0 +1,206 @@
|
|||||||
|
local repo = require("git.repo")
|
||||||
|
local util = require("git.util")
|
||||||
|
|
||||||
|
local M = {}
|
||||||
|
|
||||||
|
---@param code string
|
||||||
|
---@return string?
|
||||||
|
local function format(code)
|
||||||
|
local char, hl = repo.indicator(code)
|
||||||
|
if not char then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return string.format("%%#%s#%s%%*", hl, char)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@class ow.Git.Repo
|
||||||
|
---@field gitdir string
|
||||||
|
---@field worktree string
|
||||||
|
---@field buffers table<integer, true>
|
||||||
|
---@field watcher? uv.uv_fs_event_t
|
||||||
|
---@field refresh fun(self: ow.Git.Repo)
|
||||||
|
---@field refresh_handle ow.Git.Util.DebounceHandle
|
||||||
|
local Repo = {}
|
||||||
|
Repo.__index = Repo
|
||||||
|
|
||||||
|
function Repo:start_watcher()
|
||||||
|
local watcher = assert(vim.uv.new_fs_event())
|
||||||
|
assert(watcher:start(self.gitdir, {}, function(err, filename)
|
||||||
|
if err or (filename ~= "index" and filename ~= "HEAD") then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self:refresh()
|
||||||
|
end))
|
||||||
|
self.watcher = watcher
|
||||||
|
end
|
||||||
|
|
||||||
|
function Repo:stop_watcher()
|
||||||
|
-- Stop the libuv watcher first so no further fs-events can trigger
|
||||||
|
-- self:refresh(). Tearing down the debounce handle first leaves a
|
||||||
|
-- window where an in-flight watcher callback hits a closed timer.
|
||||||
|
if self.watcher then
|
||||||
|
self.watcher:stop()
|
||||||
|
self.watcher:close()
|
||||||
|
self.watcher = nil
|
||||||
|
end
|
||||||
|
self.refresh_handle.close()
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
function Repo:add_buffer(buf)
|
||||||
|
self.buffers[buf] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
function Repo:remove_buffer(buf)
|
||||||
|
self.buffers[buf] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@return boolean
|
||||||
|
function Repo:has_buffers()
|
||||||
|
return next(self.buffers) ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param r ow.Git.Repo
|
||||||
|
local function do_refresh(r)
|
||||||
|
vim.system(
|
||||||
|
{
|
||||||
|
"git",
|
||||||
|
"-c",
|
||||||
|
"core.quotePath=false",
|
||||||
|
"status",
|
||||||
|
"--porcelain=v1",
|
||||||
|
"--branch",
|
||||||
|
},
|
||||||
|
{ cwd = r.worktree, text = true },
|
||||||
|
vim.schedule_wrap(function(obj)
|
||||||
|
local statuses = {}
|
||||||
|
if obj.code == 0 then
|
||||||
|
for line in (obj.stdout or ""):gmatch("[^\r\n]+") do
|
||||||
|
if line:sub(1, 2) ~= "##" then
|
||||||
|
local code = line:sub(1, 2)
|
||||||
|
local x = code:sub(1, 1)
|
||||||
|
local y = code:sub(2, 2)
|
||||||
|
local path_part = line:sub(4)
|
||||||
|
if x == "R" or x == "C" or y == "R" or y == "C" then
|
||||||
|
local arrow = path_part:find(" -> ", 1, true)
|
||||||
|
if arrow then
|
||||||
|
path_part = path_part:sub(arrow + 4)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
statuses[vim.fs.joinpath(r.worktree, path_part)] =
|
||||||
|
format(code)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
util.warning(
|
||||||
|
"git status failed: %s",
|
||||||
|
vim.trim(obj.stderr or "")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
local dirty = false
|
||||||
|
for buf in pairs(r.buffers) do
|
||||||
|
if not vim.api.nvim_buf_is_valid(buf) then
|
||||||
|
r.buffers[buf] = nil
|
||||||
|
else
|
||||||
|
local status = statuses[vim.api.nvim_buf_get_name(buf)]
|
||||||
|
if vim.b[buf].git_status ~= status then
|
||||||
|
vim.b[buf].git_status = status
|
||||||
|
dirty = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dirty then
|
||||||
|
vim.cmd.redrawstatus({ bang = true })
|
||||||
|
end
|
||||||
|
vim.api.nvim_exec_autocmds("User", {
|
||||||
|
pattern = "GitRefresh",
|
||||||
|
data = {
|
||||||
|
gitdir = r.gitdir,
|
||||||
|
worktree = r.worktree,
|
||||||
|
porcelain_stdout = obj.code == 0 and obj.stdout or nil,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
end)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param gitdir string
|
||||||
|
---@param worktree string
|
||||||
|
---@return ow.Git.Repo
|
||||||
|
function Repo.new(gitdir, worktree)
|
||||||
|
local self = setmetatable({
|
||||||
|
gitdir = gitdir,
|
||||||
|
worktree = worktree,
|
||||||
|
buffers = {},
|
||||||
|
}, Repo)
|
||||||
|
self.refresh, self.refresh_handle = util.debounce(do_refresh, 50)
|
||||||
|
self:start_watcher()
|
||||||
|
return self
|
||||||
|
end
|
||||||
|
|
||||||
|
---@type table<string, ow.Git.Repo>
|
||||||
|
local repo_by_gitdir = {}
|
||||||
|
|
||||||
|
---@type table<integer, ow.Git.Repo>
|
||||||
|
local repo_by_buf = {}
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
---@return ow.Git.Repo?
|
||||||
|
local function register(buf)
|
||||||
|
local existing = repo_by_buf[buf]
|
||||||
|
if existing then
|
||||||
|
return existing
|
||||||
|
end
|
||||||
|
local path = vim.api.nvim_buf_get_name(buf)
|
||||||
|
if path == "" then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local gitdir, worktree = repo.resolve(path)
|
||||||
|
if not gitdir or not worktree then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
local r = repo_by_gitdir[gitdir]
|
||||||
|
if not r then
|
||||||
|
r = Repo.new(gitdir, worktree)
|
||||||
|
repo_by_gitdir[gitdir] = r
|
||||||
|
end
|
||||||
|
r:add_buffer(buf)
|
||||||
|
repo_by_buf[buf] = r
|
||||||
|
return r
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
function M.unregister(buf)
|
||||||
|
local r = repo_by_buf[buf]
|
||||||
|
if not r then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
repo_by_buf[buf] = nil
|
||||||
|
r:remove_buffer(buf)
|
||||||
|
if not r:has_buffers() then
|
||||||
|
r:stop_watcher()
|
||||||
|
repo_by_gitdir[r.gitdir] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
---@param buf integer
|
||||||
|
function M.refresh_buf(buf)
|
||||||
|
if not vim.api.nvim_buf_is_valid(buf) or vim.bo[buf].buftype ~= "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local r = register(buf)
|
||||||
|
if not r then
|
||||||
|
vim.b[buf].git_status = nil
|
||||||
|
return
|
||||||
|
end
|
||||||
|
r:refresh()
|
||||||
|
end
|
||||||
|
|
||||||
|
function M.stop_all()
|
||||||
|
for _, r in pairs(repo_by_gitdir) do
|
||||||
|
r:stop_watcher()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
Reference in New Issue
Block a user