refactor(git/status): rework entries into typed variants on porcelain v2
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
local t = require("test")
|
||||
local status = require("git.status")
|
||||
|
||||
local NUL = "\0"
|
||||
|
||||
---@param parts string[]
|
||||
---@return string
|
||||
local function nul(parts)
|
||||
return table.concat(parts, NUL) .. NUL
|
||||
end
|
||||
|
||||
t.test("branch headers: initial repo, no commits", function()
|
||||
local s = status.parse(nul({
|
||||
"# branch.oid (initial)",
|
||||
"# branch.head main",
|
||||
}))
|
||||
t.eq(s.branch.oid, nil)
|
||||
t.eq(s.branch.head, "main")
|
||||
t.eq(s.branch.upstream, nil)
|
||||
t.eq(s.branch.ahead, 0)
|
||||
t.eq(s.branch.behind, 0)
|
||||
end)
|
||||
|
||||
t.test("branch headers: detached HEAD", function()
|
||||
local s = status.parse(nul({
|
||||
"# branch.oid 1234567890abcdef1234567890abcdef12345678",
|
||||
"# branch.head (detached)",
|
||||
}))
|
||||
t.eq(s.branch.oid, "1234567890abcdef1234567890abcdef12345678")
|
||||
t.eq(s.branch.head, nil)
|
||||
end)
|
||||
|
||||
t.test("branch headers: with upstream and ahead/behind", function()
|
||||
local s = status.parse(nul({
|
||||
"# branch.oid abc123",
|
||||
"# branch.head main",
|
||||
"# branch.upstream origin/main",
|
||||
"# branch.ab +3 -2",
|
||||
}))
|
||||
t.eq(s.branch.head, "main")
|
||||
t.eq(s.branch.upstream, "origin/main")
|
||||
t.eq(s.branch.ahead, 3)
|
||||
t.eq(s.branch.behind, 2)
|
||||
end)
|
||||
|
||||
t.test("type 1: staged-only modification", function()
|
||||
local s = status.parse(nul({
|
||||
"1 M. N... 100644 100644 100644 abc abc foo.lua",
|
||||
}))
|
||||
local e = s.entries["foo.lua"]
|
||||
---@cast e ow.Git.Status.ChangedEntry
|
||||
t.eq(e.kind, "changed")
|
||||
t.eq(e.path, "foo.lua")
|
||||
t.eq(e.staged, "modified")
|
||||
t.eq(e.unstaged, nil)
|
||||
t.eq(e.orig, nil)
|
||||
end)
|
||||
|
||||
t.test("type 1: unstaged-only modification", function()
|
||||
local s = status.parse(nul({
|
||||
"1 .M N... 100644 100644 100644 abc abc foo.lua",
|
||||
}))
|
||||
local e = s.entries["foo.lua"]
|
||||
---@cast e ow.Git.Status.ChangedEntry
|
||||
t.eq(e.staged, nil)
|
||||
t.eq(e.unstaged, "modified")
|
||||
end)
|
||||
|
||||
t.test("type 1: both sides modified", function()
|
||||
local s = status.parse(nul({
|
||||
"1 MM N... 100644 100644 100644 abc abc foo.lua",
|
||||
}))
|
||||
local e = s.entries["foo.lua"]
|
||||
---@cast e ow.Git.Status.ChangedEntry
|
||||
t.eq(e.staged, "modified")
|
||||
t.eq(e.unstaged, "modified")
|
||||
end)
|
||||
|
||||
t.test("type 1: deleted (unstaged)", function()
|
||||
local s = status.parse(nul({
|
||||
"1 .D N... 100644 100644 000000 abc abc foo.lua",
|
||||
}))
|
||||
local e = s.entries["foo.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
t.eq(e.unstaged, "deleted")
|
||||
end)
|
||||
|
||||
t.test("type 1: added (staged)", function()
|
||||
local s = status.parse(nul({
|
||||
"1 A. N... 000000 100644 100644 abc abc new.lua",
|
||||
}))
|
||||
local e = s.entries["new.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
t.eq(e.staged, "added")
|
||||
end)
|
||||
|
||||
t.test("type 1: type-changed (unstaged)", function()
|
||||
local s = status.parse(nul({
|
||||
"1 .T N... 100644 100644 120000 abc abc foo.lua",
|
||||
}))
|
||||
local e = s.entries["foo.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
t.eq(e.unstaged, "type_changed")
|
||||
end)
|
||||
|
||||
t.test("type 2: renamed with orig", function()
|
||||
local s = status.parse(nul({
|
||||
"2 R. N... 100644 100644 100644 abc abc R100 new.lua",
|
||||
"old.lua",
|
||||
}))
|
||||
local e = s.entries["new.lua"]
|
||||
---@cast e ow.Git.Status.ChangedEntry
|
||||
t.eq(e.kind, "changed")
|
||||
t.eq(e.path, "new.lua")
|
||||
t.eq(e.staged, "renamed")
|
||||
t.eq(e.orig, "old.lua")
|
||||
end)
|
||||
|
||||
t.test("type 2: copied with orig", function()
|
||||
local s = status.parse(nul({
|
||||
"2 C. N... 100644 100644 100644 abc abc C90 copy.lua",
|
||||
"src.lua",
|
||||
}))
|
||||
local e = s.entries["copy.lua"]
|
||||
---@cast e ow.Git.Status.ChangedEntry
|
||||
t.eq(e.staged, "copied")
|
||||
t.eq(e.orig, "src.lua")
|
||||
end)
|
||||
|
||||
t.test("type u: all seven conflict types", function()
|
||||
local cases = {
|
||||
{ xy = "DD", expected = "both_deleted" },
|
||||
{ xy = "AU", expected = "added_by_us" },
|
||||
{ xy = "UD", expected = "deleted_by_them" },
|
||||
{ xy = "UA", expected = "added_by_them" },
|
||||
{ xy = "DU", expected = "deleted_by_us" },
|
||||
{ xy = "AA", expected = "both_added" },
|
||||
{ xy = "UU", expected = "both_modified" },
|
||||
}
|
||||
for _, c in ipairs(cases) do
|
||||
local s = status.parse(nul({
|
||||
string.format(
|
||||
"u %s N... 100644 100644 100644 100644 abc abc abc conflict.lua",
|
||||
c.xy
|
||||
),
|
||||
}))
|
||||
local e = s.entries["conflict.lua"]
|
||||
t.eq(e.kind, "unmerged", "kind for " .. c.xy)
|
||||
t.eq(
|
||||
(e --[[@as ow.Git.Status.UnmergedEntry]]).conflict,
|
||||
c.expected,
|
||||
"conflict for " .. c.xy
|
||||
)
|
||||
end
|
||||
end)
|
||||
|
||||
t.test("type ?: untracked", function()
|
||||
local s = status.parse(nul({ "? new.txt" }))
|
||||
local e = s.entries["new.txt"]
|
||||
t.eq(e.kind, "untracked")
|
||||
t.eq(e.path, "new.txt")
|
||||
end)
|
||||
|
||||
t.test("type !: ignored", function()
|
||||
local s = status.parse(nul({ "! .secret" }))
|
||||
local e = s.entries[".secret"]
|
||||
t.eq(e.kind, "ignored")
|
||||
end)
|
||||
|
||||
t.test("mixed: branch + multiple variants", function()
|
||||
local s = status.parse(nul({
|
||||
"# branch.oid abc",
|
||||
"# branch.head main",
|
||||
"# branch.upstream origin/main",
|
||||
"# branch.ab +0 -0",
|
||||
"1 M. N... 100644 100644 100644 a a staged.lua",
|
||||
"1 .M N... 100644 100644 100644 a a unstaged.lua",
|
||||
"1 MM N... 100644 100644 100644 a a both.lua",
|
||||
"u UU N... 100644 100644 100644 100644 a a a conflict.lua",
|
||||
"? untracked.txt",
|
||||
"! ignored.txt",
|
||||
}))
|
||||
t.eq(s.branch.head, "main")
|
||||
local staged = s.entries["staged.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
local unstaged = s.entries["unstaged.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
local both = s.entries["both.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
t.eq(staged.staged, "modified")
|
||||
t.eq(unstaged.unstaged, "modified")
|
||||
t.eq(both.staged, "modified")
|
||||
t.eq(both.unstaged, "modified")
|
||||
t.eq(s.entries["conflict.lua"].kind, "unmerged")
|
||||
t.eq(s.entries["untracked.txt"].kind, "untracked")
|
||||
t.eq(s.entries["ignored.txt"].kind, "ignored")
|
||||
end)
|
||||
|
||||
t.test("paths with spaces survive splitting", function()
|
||||
local s = status.parse(nul({
|
||||
"1 .M N... 100644 100644 100644 a a path with spaces.lua",
|
||||
}))
|
||||
local e = s.entries["path with spaces.lua"] --[[@as ow.Git.Status.ChangedEntry]]
|
||||
t.eq(e.unstaged, "modified")
|
||||
end)
|
||||
|
||||
t.test("mark_for: changed staged modified", function()
|
||||
local entry = {
|
||||
kind = "changed",
|
||||
path = "x",
|
||||
staged = "modified",
|
||||
}
|
||||
t.eq(status.mark_for(entry, "staged"), { char = "M", hl = "GitStagedModified" })
|
||||
end)
|
||||
|
||||
t.test("mark_for: changed unstaged deleted uses GitUnstagedDeleted", function()
|
||||
local entry = {
|
||||
kind = "changed",
|
||||
path = "x",
|
||||
unstaged = "deleted",
|
||||
}
|
||||
t.eq(status.mark_for(entry, "unstaged"), { char = "D", hl = "GitUnstagedDeleted" })
|
||||
end)
|
||||
|
||||
t.test("mark_for: changed renamed uses per-side renamed hl", function()
|
||||
local entry = {
|
||||
kind = "changed",
|
||||
path = "x",
|
||||
staged = "renamed",
|
||||
orig = "y",
|
||||
}
|
||||
t.eq(status.mark_for(entry, "staged"), { char = "R", hl = "GitStagedRenamed" })
|
||||
end)
|
||||
|
||||
t.test("mark_for: untracked / ignored / unmerged ignore side", function()
|
||||
t.eq(
|
||||
status.mark_for({ kind = "untracked", path = "x" } --[[@as ow.Git.Status.Entry]]),
|
||||
{ char = "?", hl = "GitUntracked" }
|
||||
)
|
||||
t.eq(
|
||||
status.mark_for({ kind = "ignored", path = "x" } --[[@as ow.Git.Status.Entry]]),
|
||||
{ char = "i", hl = "GitIgnored" }
|
||||
)
|
||||
t.eq(
|
||||
status.mark_for({
|
||||
kind = "unmerged",
|
||||
path = "x",
|
||||
conflict = "both_modified",
|
||||
} --[[@as ow.Git.Status.Entry]]),
|
||||
{ char = "!", hl = "GitUnmergedBothModified" }
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("marks_for: changed with both sides yields two marks", function()
|
||||
local entry = {
|
||||
kind = "changed",
|
||||
path = "x",
|
||||
staged = "modified",
|
||||
unstaged = "modified",
|
||||
}
|
||||
local marks = status.marks_for(entry)
|
||||
t.eq(#marks, 2)
|
||||
t.eq(marks[1], { char = "M", hl = "GitStagedModified" })
|
||||
t.eq(marks[2], { char = "M", hl = "GitUnstagedModified" })
|
||||
end)
|
||||
|
||||
t.test("marks_for: changed one-sided yields one mark", function()
|
||||
local entry = { kind = "changed", path = "x", staged = "added" }
|
||||
local marks = status.marks_for(entry)
|
||||
t.eq(#marks, 1)
|
||||
t.eq(marks[1], { char = "A", hl = "GitStagedAdded" })
|
||||
end)
|
||||
|
||||
t.test("marks_for: untracked yields one mark", function()
|
||||
local entry = { kind = "untracked", path = "x" } --[[@as ow.Git.Status.Entry]]
|
||||
local marks = status.marks_for(entry)
|
||||
t.eq(#marks, 1)
|
||||
t.eq(marks[1], { char = "?", hl = "GitUntracked" })
|
||||
end)
|
||||
|
||||
t.test("Status:rows buckets by section", function()
|
||||
local s = status.parse(nul({
|
||||
"1 M. N... 100644 100644 100644 a a staged.lua",
|
||||
"1 .M N... 100644 100644 100644 a a unstaged.lua",
|
||||
"1 MM N... 100644 100644 100644 a a both.lua",
|
||||
"? untracked.txt",
|
||||
}))
|
||||
t.eq(#s:rows("staged"), 2, "staged section: staged.lua + both.lua")
|
||||
t.eq(#s:rows("unstaged"), 2, "unstaged section: unstaged.lua + both.lua")
|
||||
t.eq(#s:rows("untracked"), 1)
|
||||
t.eq(#s:rows("unmerged"), 0)
|
||||
t.eq(#s:rows("ignored"), 0)
|
||||
end)
|
||||
|
||||
t.test("Status:rows for staged carries side='staged'", function()
|
||||
local s = status.parse(nul({
|
||||
"1 M. N... 100644 100644 100644 a a x.lua",
|
||||
}))
|
||||
local row = assert(s:rows("staged")[1])
|
||||
t.eq(row.section, "staged")
|
||||
t.eq(row.side, "staged")
|
||||
t.eq(row.entry.kind, "changed")
|
||||
end)
|
||||
|
||||
t.test("Status:rows for untracked has nil side", function()
|
||||
local s = status.parse(nul({ "? x.txt" }))
|
||||
local row = assert(s:rows("untracked")[1])
|
||||
t.eq(row.section, "untracked")
|
||||
t.eq(row.side, nil)
|
||||
end)
|
||||
|
||||
t.test("Status:aggregate_at dedups marks under prefix", function()
|
||||
local s = status.parse(nul({
|
||||
"1 .M N... 100644 100644 100644 a a sub/a.lua",
|
||||
"1 .M N... 100644 100644 100644 a a sub/b.lua",
|
||||
"? sub/c.txt",
|
||||
}))
|
||||
local marks = s:aggregate_at("sub")
|
||||
t.eq(#marks, 2, "modified ('M') and untracked ('?') deduped")
|
||||
local m1 = assert(marks[1])
|
||||
local m2 = assert(marks[2])
|
||||
local hls = { m1.hl, m2.hl }
|
||||
table.sort(hls)
|
||||
t.eq(hls, { "GitUnstagedModified", "GitUntracked" })
|
||||
end)
|
||||
|
||||
t.test("Status:aggregate_at with prefix '.' includes everything", function()
|
||||
local s = status.parse(nul({
|
||||
"1 .M N... 100644 100644 100644 a a a.lua",
|
||||
"? b.txt",
|
||||
}))
|
||||
t.eq(#s:aggregate_at("."), 2)
|
||||
end)
|
||||
@@ -78,7 +78,7 @@ local function setup_sidebar_with_unstaged_file(
|
||||
)
|
||||
r:refresh()
|
||||
t.wait_for(function()
|
||||
return r.status and #r.status:by_kind("unstaged") > 0
|
||||
return r.status and #r.status:rows("unstaged") > 0
|
||||
end, "git status to report unstaged changes")
|
||||
|
||||
local entry_line = assert(
|
||||
@@ -108,7 +108,7 @@ t.test("stage with diff open: sidebar cursor stays put", function()
|
||||
vim.api.nvim_set_current_win(sidebar_win)
|
||||
t.press("s")
|
||||
t.wait_for(function()
|
||||
return #r.status:by_kind("staged") > 0
|
||||
return #r.status:rows("staged") > 0
|
||||
end, "stage to propagate to repo state")
|
||||
|
||||
t.eq(
|
||||
@@ -145,7 +145,7 @@ t.test(
|
||||
vim.api.nvim_set_current_win(sidebar_win)
|
||||
t.press("s")
|
||||
t.wait_for(function()
|
||||
return #r.status:by_kind("staged") > 0
|
||||
return #r.status:rows("staged") > 0
|
||||
end, "stage to propagate to repo state")
|
||||
|
||||
t.eq(
|
||||
|
||||
Reference in New Issue
Block a user