feat(git): show staged hunks in the gutter with a stage toggle
This commit is contained in:
+215
-2
@@ -18,8 +18,8 @@ local function setup(committed, worktree, file)
|
||||
hunks._flush(buf)
|
||||
t.wait_for(function()
|
||||
local s = hunks.state(buf)
|
||||
return s ~= nil and s.index ~= nil
|
||||
end, "hunks to compute the index snapshot")
|
||||
return s ~= nil and s.index ~= nil and s.head ~= nil
|
||||
end, "hunks to load the index and HEAD snapshots")
|
||||
local state = assert(hunks.state(buf), "buffer state should exist")
|
||||
return dir, buf, state
|
||||
end
|
||||
@@ -296,6 +296,147 @@ t.test("stage_hunk stages a deletion", function()
|
||||
t.eq(h.git(dir, "show", ":0:a.txt").stdout, "a\nc")
|
||||
end)
|
||||
|
||||
t.test("stage_hunk stages only the hunk under the cursor", function()
|
||||
local committed = table.concat({
|
||||
"local M = {}",
|
||||
"",
|
||||
"function M.first()",
|
||||
" return 1",
|
||||
"end",
|
||||
"",
|
||||
"function M.last()",
|
||||
" return 9",
|
||||
"end",
|
||||
"",
|
||||
"return M",
|
||||
}, "\n") .. "\n"
|
||||
local worktree = table.concat({
|
||||
"local M = {}",
|
||||
"",
|
||||
"-- helpers",
|
||||
"function M.first()",
|
||||
" return 1",
|
||||
"end",
|
||||
"",
|
||||
"function M.mid()",
|
||||
" return 5",
|
||||
"end",
|
||||
"",
|
||||
"function M.last()",
|
||||
" return 9",
|
||||
"end",
|
||||
"",
|
||||
"return M",
|
||||
}, "\n") .. "\n"
|
||||
local dir, buf = setup(committed, worktree)
|
||||
vim.api.nvim_win_set_cursor(0, { 9, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return h.git(dir, "diff", "--cached", "--name-only").stdout ~= ""
|
||||
end, "the mid hunk to land in the index")
|
||||
t.eq(
|
||||
h.git(dir, "show", ":0:a.txt").stdout,
|
||||
table.concat({
|
||||
"local M = {}",
|
||||
"",
|
||||
"function M.first()",
|
||||
" return 1",
|
||||
"end",
|
||||
"",
|
||||
"function M.mid()",
|
||||
" return 5",
|
||||
"end",
|
||||
"",
|
||||
"function M.last()",
|
||||
" return 9",
|
||||
"end",
|
||||
"",
|
||||
"return M",
|
||||
}, "\n"),
|
||||
"only the cursor's hunk is staged, placed at the right line"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("stage_hunk stages a whole-file change with no context", function()
|
||||
local dir, buf = setup("a\nb\nc\n", "x\ny\nz\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return h.git(dir, "diff", "--cached", "--name-only").stdout ~= ""
|
||||
end, "the change to land in the index")
|
||||
t.eq(h.git(dir, "show", ":0:a.txt").stdout, "x\ny\nz")
|
||||
end)
|
||||
|
||||
t.test("stage_hunk stages a change at the start of the file", function()
|
||||
local dir, buf = setup("a\nb\nc\nd\ne\n", "A\nb\nc\nd\ne\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return h.git(dir, "diff", "--cached", "--name-only").stdout ~= ""
|
||||
end, "the change to land in the index")
|
||||
t.eq(h.git(dir, "show", ":0:a.txt").stdout, "A\nb\nc\nd\ne")
|
||||
end)
|
||||
|
||||
t.test("stage_hunk stages a change at the end of the file", function()
|
||||
local dir, buf = setup("a\nb\nc\nd\ne\n", "a\nb\nc\nd\nE\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 5, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return h.git(dir, "diff", "--cached", "--name-only").stdout ~= ""
|
||||
end, "the change to land in the index")
|
||||
t.eq(h.git(dir, "show", ":0:a.txt").stdout, "a\nb\nc\nd\nE")
|
||||
end)
|
||||
|
||||
t.test("stage_hunk stages a deletion at the start of the file", function()
|
||||
local dir, buf = setup("a\nb\nc\nd\n", "b\nc\nd\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return h.git(dir, "diff", "--cached", "--name-only").stdout ~= ""
|
||||
end, "the deletion to land in the index")
|
||||
t.eq(h.git(dir, "show", ":0:a.txt").stdout, "b\nc\nd")
|
||||
end)
|
||||
|
||||
t.test("stage_hunk leaves an adjacent unstaged hunk in place", function()
|
||||
local dir, buf = setup("a\nb\nc\nd\ne\n", "A\nb\nC\nd\ne\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 3, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return h.git(dir, "diff", "--cached", "--name-only").stdout ~= ""
|
||||
end, "the line-3 hunk to land in the index")
|
||||
t.eq(
|
||||
h.git(dir, "show", ":0:a.txt").stdout,
|
||||
"a\nb\nC\nd\ne",
|
||||
"only line 3 is staged; the adjacent line-1 hunk is untouched"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("stage_hunk unstages one of two adjacent staged hunks", function()
|
||||
local dir, buf = setup("a\nb\nc\nd\ne\n", "A\nb\nC\nd\ne\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return #assert(hunks.state(buf)).staged == 1
|
||||
end, "the line-1 hunk to be staged")
|
||||
vim.api.nvim_win_set_cursor(0, { 3, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
local s = assert(hunks.state(buf))
|
||||
return #s.hunks == 0 and #s.staged == 2
|
||||
end, "both hunks to be staged")
|
||||
|
||||
vim.api.nvim_win_set_cursor(0, { 3, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return #assert(hunks.state(buf)).staged == 1
|
||||
end, "the line-3 hunk to be unstaged again")
|
||||
t.eq(
|
||||
h.git(dir, "show", ":0:a.txt").stdout,
|
||||
"A\nb\nc\nd\ne",
|
||||
"line 3 reverts to HEAD while the staged line-1 change remains"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("stage_hunk refreshes the gutter when status stays modified", function()
|
||||
local _, buf = setup("a\nb\nc\nd\ne\n", "A\nb\nC\nd\nE\n")
|
||||
t.eq(#assert(hunks.state(buf)).hunks, 3)
|
||||
@@ -313,6 +454,78 @@ t.test("stage_hunk refreshes the gutter when status stays modified", function()
|
||||
end, "gutter to drop the middle staged hunk")
|
||||
end)
|
||||
|
||||
t.test("staged hunks show with the staged highlight", function()
|
||||
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
local s = assert(hunks.state(buf))
|
||||
return #s.hunks == 0 and #s.staged == 1
|
||||
end, "the hunk to move from unstaged to staged")
|
||||
t.eq(sign_marks(buf), {
|
||||
{ row = 1, sign = "┃", hl = "GitHunkStagedChanged" },
|
||||
})
|
||||
end)
|
||||
|
||||
t.test("the gutter shows staged and unstaged hunks together", function()
|
||||
local _, buf = setup("a\nb\nc\nd\ne\n", "A\nb\nC\nd\nE\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 1, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return #assert(hunks.state(buf)).hunks == 2
|
||||
end, "the first hunk to leave the unstaged set")
|
||||
t.eq(sign_marks(buf), {
|
||||
{ row = 0, sign = "┃", hl = "GitHunkStagedChanged" },
|
||||
{ row = 2, sign = "┃", hl = "GitHunkChanged" },
|
||||
{ row = 4, sign = "┃", hl = "GitHunkChanged" },
|
||||
})
|
||||
end)
|
||||
|
||||
t.test("stage_hunk toggles a staged hunk back to unstaged", function()
|
||||
local _, buf = setup("a\nb\nc\n", "a\nB\nc\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
local s = assert(hunks.state(buf))
|
||||
return #s.hunks == 0 and #s.staged == 1
|
||||
end, "the hunk to become staged")
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
local s = assert(hunks.state(buf))
|
||||
return #s.hunks == 1 and #s.staged == 0
|
||||
end, "the hunk to return to unstaged")
|
||||
t.eq(sign_marks(buf), {
|
||||
{ row = 1, sign = "┃", hl = "GitHunkChanged" },
|
||||
})
|
||||
end)
|
||||
|
||||
t.test("stage_hunk unstages correctly when buffer lines are shifted", function()
|
||||
local dir, buf = setup("a\nb\nc\n", "a\nb\nC\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 3, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return #assert(hunks.state(buf)).staged == 1
|
||||
end, "the line-3 change to be staged")
|
||||
|
||||
vim.api.nvim_buf_set_lines(buf, 0, 0, false, { "NEW" })
|
||||
vim.api.nvim_exec_autocmds("TextChanged", { buffer = buf })
|
||||
hunks._flush(buf)
|
||||
t.wait_for(function()
|
||||
return #assert(hunks.state(buf)).hunks == 1
|
||||
end, "the unstaged add at the top to register")
|
||||
|
||||
vim.api.nvim_win_set_cursor(0, { 4, 0 })
|
||||
hunks.stage_hunk(buf)
|
||||
t.wait_for(function()
|
||||
return #assert(hunks.state(buf)).staged == 0
|
||||
end, "the shifted staged hunk to be unstaged")
|
||||
t.eq(
|
||||
h.git(dir, "show", ":0:a.txt").stdout,
|
||||
"a\nb\nc",
|
||||
"the index reverts to HEAD content for the unstaged hunk"
|
||||
)
|
||||
end)
|
||||
|
||||
t.test("reset_hunk restores the index content for a change", function()
|
||||
local _, buf, state = setup("a\nb\nc\n", "a\nB\nc\n")
|
||||
vim.api.nvim_win_set_cursor(0, { 2, 0 })
|
||||
|
||||
Reference in New Issue
Block a user