207 lines
5.5 KiB
Lua
207 lines
5.5 KiB
Lua
local M = {}
|
|
|
|
local UNMERGED = {
|
|
DD = true,
|
|
AU = true,
|
|
UD = true,
|
|
UA = true,
|
|
DU = true,
|
|
AA = true,
|
|
UU = true,
|
|
}
|
|
|
|
---@alias ow.Git.Status.EntryKind "untracked"|"unstaged"|"staged"|"unmerged"|"ignored"
|
|
|
|
---@class ow.Git.Status.Entry
|
|
---@field path string
|
|
---@field kind ow.Git.Status.EntryKind
|
|
---@field char string
|
|
---@field hl string
|
|
---@field orig string?
|
|
|
|
---@class ow.Git.Status.BranchInfo
|
|
---@field head string?
|
|
---@field upstream string?
|
|
---@field ahead integer
|
|
---@field behind integer
|
|
|
|
---@class ow.Git.Status
|
|
---@field branch ow.Git.Status.BranchInfo
|
|
---@field entries table<string, ow.Git.Status.Entry[]>
|
|
local Status = {}
|
|
Status.__index = Status
|
|
|
|
---@param kind ow.Git.Status.EntryKind
|
|
---@return ow.Git.Status.Entry[]
|
|
function Status:by_kind(kind)
|
|
local out = {}
|
|
for _, list in pairs(self.entries) do
|
|
for _, e in ipairs(list) do
|
|
if e.kind == kind then
|
|
table.insert(out, e)
|
|
end
|
|
end
|
|
end
|
|
return out
|
|
end
|
|
|
|
---@param prefix string
|
|
---@return ow.Git.Status.Entry[]
|
|
function Status:aggregate_at(prefix)
|
|
local match = (prefix == "" or prefix == ".") and "" or prefix .. "/"
|
|
local seen = {}
|
|
local out = {}
|
|
for path, list in pairs(self.entries) do
|
|
if path == prefix or vim.startswith(path, match) then
|
|
for _, e in ipairs(list) do
|
|
local key = e.char .. "\0" .. e.hl
|
|
if not seen[key] then
|
|
seen[key] = true
|
|
table.insert(out, e)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
table.sort(out, function(a, b)
|
|
return a.char < b.char
|
|
end)
|
|
return out
|
|
end
|
|
|
|
---@param x string
|
|
---@return string char, string hl
|
|
local function staged_attrs(x)
|
|
if x == "R" then
|
|
return "R", "GitRenamed"
|
|
end
|
|
return x, "GitStaged"
|
|
end
|
|
|
|
---@param y string
|
|
---@return string char, string hl
|
|
local function unstaged_attrs(y)
|
|
if y == "R" then
|
|
return "R", "GitRenamed"
|
|
end
|
|
if y == "D" then
|
|
return "D", "GitDeleted"
|
|
end
|
|
return y, "GitUnstaged"
|
|
end
|
|
|
|
---@param line string
|
|
---@return ow.Git.Status.BranchInfo
|
|
local function parse_branch_line(line)
|
|
local info = { ahead = 0, behind = 0 }
|
|
local content = line:sub(4)
|
|
local arrow = content:find("...", 1, true)
|
|
if not arrow then
|
|
info.head = content
|
|
return info
|
|
end
|
|
info.head = content:sub(1, arrow - 1)
|
|
local rest = content:sub(arrow + 3)
|
|
local bracket = rest:find(" %[")
|
|
if not bracket then
|
|
info.upstream = rest
|
|
return info
|
|
end
|
|
info.upstream = rest:sub(1, bracket - 1)
|
|
local inside = rest:match("%[([^%]]+)%]")
|
|
if inside then
|
|
info.ahead = (tonumber(inside:match("ahead (%d+)")) or 0) --[[@as integer]]
|
|
info.behind = (tonumber(inside:match("behind (%d+)")) or 0) --[[@as integer]]
|
|
end
|
|
return info
|
|
end
|
|
|
|
---@param line string
|
|
---@return string x, string y, string path, string? orig
|
|
local function parse_status_line(line)
|
|
local x = line:sub(1, 1)
|
|
local y = line:sub(2, 2)
|
|
local rest = line:sub(4)
|
|
local orig
|
|
if x == "R" or x == "C" or y == "R" or y == "C" then
|
|
local arrow = rest:find(" -> ", 1, true)
|
|
if arrow then
|
|
orig = rest:sub(1, arrow - 1)
|
|
rest = rest:sub(arrow + 4)
|
|
end
|
|
end
|
|
return x, y, rest, orig
|
|
end
|
|
|
|
---@param entries table<string, ow.Git.Status.Entry[]>
|
|
---@param entry ow.Git.Status.Entry
|
|
local function add(entries, entry)
|
|
local key = entry.path
|
|
if key:sub(-1) == "/" then
|
|
key = key:sub(1, -2)
|
|
end
|
|
local list = entries[key] or {}
|
|
table.insert(list, entry)
|
|
entries[key] = list
|
|
end
|
|
|
|
---@param stdout string
|
|
---@return ow.Git.Status
|
|
function M.parse(stdout)
|
|
local branch = { ahead = 0, behind = 0 }
|
|
---@type table<string, ow.Git.Status.Entry[]>
|
|
local entries = {}
|
|
for line in stdout:gmatch("[^\r\n]+") do
|
|
if line:sub(1, 2) == "##" then
|
|
branch = parse_branch_line(line)
|
|
else
|
|
local x, y, path, orig = parse_status_line(line)
|
|
if x == "?" and y == "?" then
|
|
add(entries, {
|
|
path = path,
|
|
kind = "untracked",
|
|
char = "?",
|
|
hl = "GitUntracked",
|
|
})
|
|
elseif x == "!" and y == "!" then
|
|
add(entries, {
|
|
path = path,
|
|
kind = "ignored",
|
|
char = "i",
|
|
hl = "GitIgnored",
|
|
})
|
|
elseif UNMERGED[x .. y] then
|
|
add(entries, {
|
|
path = path,
|
|
kind = "unmerged",
|
|
char = "!",
|
|
hl = "GitUnmerged",
|
|
})
|
|
else
|
|
if x ~= " " then
|
|
local char, hl = staged_attrs(x)
|
|
add(entries, {
|
|
path = path,
|
|
kind = "staged",
|
|
char = char,
|
|
hl = hl,
|
|
orig = orig,
|
|
})
|
|
end
|
|
if y ~= " " then
|
|
local char, hl = unstaged_attrs(y)
|
|
add(entries, {
|
|
path = path,
|
|
kind = "unstaged",
|
|
char = char,
|
|
hl = hl,
|
|
orig = orig,
|
|
})
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return setmetatable({ branch = branch, entries = entries }, Status)
|
|
end
|
|
|
|
return M
|