From 6703b8acbaf9571c7f1580f91bc7a58318d8d965 Mon Sep 17 00:00:00 2001 From: Oscar Wallberg Date: Sun, 3 May 2026 00:46:53 +0200 Subject: [PATCH] refactor(git): move porcelain parsing into repo.lua --- lua/git/repo.lua | 134 ++++++++++++++++++++++++++++++++++++++++---- lua/git/sidebar.lua | 122 +++++----------------------------------- 2 files changed, 136 insertions(+), 120 deletions(-) diff --git a/lua/git/repo.lua b/lua/git/repo.lua index 3cf9224..cd38afe 100644 --- a/lua/git/repo.lua +++ b/lua/git/repo.lua @@ -19,6 +19,25 @@ end ---@field log_max_count integer? ---@field pending_content string? +---@class ow.Git.StatusEntry +---@field section "Untracked"|"Unstaged"|"Staged"|"Unmerged" +---@field x string +---@field y string +---@field path string +---@field orig string? + +---@class ow.Git.BranchInfo +---@field head string? +---@field upstream string? +---@field ahead integer +---@field behind integer + +---@class ow.Git.PorcelainGroups +---@field Untracked ow.Git.StatusEntry[] +---@field Unstaged ow.Git.StatusEntry[] +---@field Staged ow.Git.StatusEntry[] +---@field Unmerged ow.Git.StatusEntry[] + ---@class ow.Git.Repo ---@field gitdir string ---@field worktree string @@ -30,6 +49,23 @@ end local Repo = {} Repo.__index = Repo +---@param line string +---@return string x, string y, string path, string? orig +local function parse_porcelain_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 r ow.Git.Repo local function do_refresh(r) vim.system( @@ -47,18 +83,9 @@ local function do_refresh(r) 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)] = - status.format(code) + local x, y, path = parse_porcelain_line(line) + statuses[vim.fs.joinpath(r.worktree, path)] = + status.format(x .. y) end end else @@ -392,4 +419,87 @@ function M.stop_all() end end +---@param line string +---@return ow.Git.BranchInfo +function M.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 stdout string +---@return ow.Git.BranchInfo, ow.Git.PorcelainGroups +function M.parse_porcelain(stdout) + local branch = { ahead = 0, behind = 0 } + ---@type ow.Git.PorcelainGroups + local groups = { + Untracked = {}, + Unstaged = {}, + Staged = {}, + Unmerged = {}, + } + for line in stdout:gmatch("[^\r\n]+") do + if line:sub(1, 2) == "##" then + branch = M.parse_branch_line(line) + else + local x, y, path, orig = parse_porcelain_line(line) + if x == "?" and y == "?" then + table.insert(groups.Untracked, { + section = "Untracked", + x = x, + y = y, + path = path, + orig = orig, + }) + elseif status.UNMERGED[x .. y] then + table.insert(groups.Unmerged, { + section = "Unmerged", + x = x, + y = y, + path = path, + orig = orig, + }) + else + if x ~= " " then + table.insert(groups.Staged, { + section = "Staged", + x = x, + y = y, + path = path, + orig = orig, + }) + end + if y ~= " " then + table.insert(groups.Unstaged, { + section = "Unstaged", + x = x, + y = y, + path = path, + orig = orig, + }) + end + end + end + end + return branch, groups +end + return M diff --git a/lua/git/sidebar.lua b/lua/git/sidebar.lua index f837747..799dd81 100644 --- a/lua/git/sidebar.lua +++ b/lua/git/sidebar.lua @@ -17,19 +17,12 @@ local SECTIONS = { } local SIDEBAR_WIDTH = 50 ----@class ow.Git.FileEntry ----@field section string ----@field path string ----@field orig string? ----@field x string ----@field y string - ---@class ow.Git.CommitEntry ----@field section string +---@field section "Unpushed"|"Unpulled" ---@field sha string ---@field subject string? ----@alias ow.Git.SidebarEntry ow.Git.FileEntry | ow.Git.CommitEntry +---@alias ow.Git.SidebarEntry ow.Git.StatusEntry | ow.Git.CommitEntry ---@class ow.Git.SidebarState ---@field repo ow.Git.Repo @@ -108,100 +101,13 @@ local function format_entry(entry) return string.format(" %s %s", char, label), hl, #char end ----@class ow.Git.BranchInfo ----@field head string? ----@field upstream string? ----@field ahead integer ----@field behind integer - ----@param line string ----@return ow.Git.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 stdout string ---@return ow.Git.BranchInfo, table local function parse_porcelain(stdout) - local branch = { ahead = 0, behind = 0 } - local groups = { - Untracked = {}, - Unstaged = {}, - Staged = {}, - Unmerged = {}, - Unpushed = {}, - Unpulled = {}, - } - for line in stdout:gmatch("[^\r\n]+") do - if line:sub(1, 2) == "##" then - branch = parse_branch_line(line) - else - 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 - local entry = { - section = nil, - path = rest, - orig = orig, - x = x, - y = y, - } - if x == "?" and y == "?" then - entry.section = "Untracked" - table.insert(groups.Untracked, entry) - elseif status.UNMERGED[x .. y] then - entry.section = "Unmerged" - table.insert(groups.Unmerged, entry) - else - if x ~= " " then - table.insert(groups.Staged, { - section = "Staged", - path = entry.path, - orig = entry.orig, - x = entry.x, - y = entry.y, - }) - end - if y ~= " " then - table.insert(groups.Unstaged, { - section = "Unstaged", - path = entry.path, - orig = entry.orig, - x = entry.x, - y = entry.y, - }) - end - end - end - end + local branch, groups = repo.parse_porcelain(stdout) + ---@cast groups table + groups.Unpushed = {} + groups.Unpulled = {} return branch, groups end @@ -476,7 +382,7 @@ local function worktree_pane(r, path) end ---@param s ow.Git.SidebarState ----@param entry ow.Git.FileEntry +---@param entry ow.Git.StatusEntry ---@return ow.Git.DiffSide local function index_pane(s, entry) local rev = Revision.new({ stage = 0, path = entry.path }) @@ -487,7 +393,7 @@ local function index_pane(s, entry) end ---@param s ow.Git.SidebarState ----@param entry ow.Git.FileEntry +---@param entry ow.Git.StatusEntry ---@return ow.Git.DiffSide? local function older_pane(s, entry) if entry.section == "Staged" then @@ -503,7 +409,7 @@ local function older_pane(s, entry) end ---@param s ow.Git.SidebarState ----@param entry ow.Git.FileEntry +---@param entry ow.Git.StatusEntry ---@return ow.Git.DiffSide? local function newer_pane(s, entry) if entry.section == "Staged" then @@ -581,7 +487,7 @@ local function adopt_diff_wins(s, sidebar_win) return left, right end ----@param entry ow.Git.FileEntry +---@param entry ow.Git.StatusEntry ---@return string local function entry_key(entry) return entry.section .. "|" .. entry.path .. "|" .. (entry.orig or "") @@ -626,7 +532,7 @@ local function view_entry(s, entry, focus_left) if not entry.path then return end - ---@cast entry ow.Git.FileEntry + ---@cast entry ow.Git.StatusEntry local sidebar_win = sidebar_win_for(s) if not sidebar_win then return @@ -722,7 +628,7 @@ local function action_stage() if not s or not entry or not entry.path then return end - ---@cast entry ow.Git.FileEntry + ---@cast entry ow.Git.StatusEntry if entry.section == "Staged" then return end @@ -742,7 +648,7 @@ local function action_unstage() if not s or not entry or not entry.path then return end - ---@cast entry ow.Git.FileEntry + ---@cast entry ow.Git.StatusEntry if entry.section ~= "Staged" then return end @@ -770,7 +676,7 @@ local function action_discard() if not s or not entry or not entry.path then return end - ---@cast entry ow.Git.FileEntry + ---@cast entry ow.Git.StatusEntry if entry.section == "Staged" then util.warning("file has staged changes, unstage first with 'u'") return