Skip to content

Commit c9e6bd2

Browse files
committed
fix(commit): run pre-commit hooks async blocking on slow hooks (#44)
1 parent 7cc03bf commit c9e6bd2

1 file changed

Lines changed: 72 additions & 34 deletions

File tree

lua/tinygit/commands/commit.lua

Lines changed: 72 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,21 @@ local function postCommitNotif(notifTitle, doStageAllChanges, commitTitle, extra
5555
u.notify(table.concat(lines, "\n"), "info", { title = notifTitle })
5656
end
5757

58+
---@return boolean
59+
local function repoHasPrecommitHook()
60+
local out = vim.system({ "git", "config", "--get", "core.hooksPath" }):wait()
61+
local hasHookConfig = out.code == 0
62+
if not hasHookConfig then return false end
63+
64+
local currentHookPath = vim.fs.normalize(vim.trim(out.stdout))
65+
local gitRoot = vim.fs.root(0, ".git")
66+
67+
local isRelative = not vim.startswith(currentHookPath, "/")
68+
if isRelative then currentHookPath = vim.fs.normalize(gitRoot .. "/" .. currentHookPath) end
69+
local hookPathExists = vim.uv.fs_stat(currentHookPath) ~= nil
70+
return hookPathExists
71+
end
72+
5873
--------------------------------------------------------------------------------
5974

6075
---@param opts? { pushIfClean?: boolean, pullBeforePush?: boolean }
@@ -67,45 +82,68 @@ function M.smartCommit(opts)
6782
local doStageAllChanges = hasNoStagedChanges()
6883
local cleanAfterCommit = hasNoUnstagedChanges() or doStageAllChanges
6984

70-
local prompt = "Commit"
71-
if doStageAllChanges then prompt = "Stage all · " .. prompt:lower() end
72-
if cleanAfterCommit and opts.pushIfClean then prompt = prompt .. " · push" end
73-
74-
local inputMode = doStageAllChanges and "stage-all-and-commit" or "commit"
75-
76-
-- check if pre-commit would pass before opening message input
77-
local preCommitResult = vim.system({ "git", "hook", "run", "--ignore-missing", "pre-commit" })
78-
:wait()
79-
if u.nonZeroExit(preCommitResult) then return end
80-
81-
require("tinygit.commands.commit.msg-input").new(inputMode, prompt, function(title, body)
82-
-- stage
83-
if doStageAllChanges then
84-
local result = vim.system({ "git", "add", "--all" }):wait()
85+
local function startCommit()
86+
-- params
87+
local input = require("tinygit.commands.commit.msg-input")
88+
local prompt = "Commit"
89+
if doStageAllChanges then prompt = "Stage all · " .. prompt:lower() end
90+
if cleanAfterCommit and opts.pushIfClean then prompt = prompt .. " · push" end
91+
local inputMode = doStageAllChanges and "stage-all-and-commit" or "commit"
92+
93+
input.new(inputMode, prompt, function(title, body)
94+
-- stage
95+
if doStageAllChanges then
96+
local result = vim.system({ "git", "add", "--all" }):wait()
97+
if u.nonZeroExit(result) then return end
98+
end
99+
100+
-- commit
101+
-- (using `--no-verify`, since we checked the pre-commit earlier already)
102+
local commitArgs = { "git", "commit", "--no-verify", "--message=" .. title }
103+
if body then table.insert(commitArgs, "--message=" .. body) end
104+
local result = vim.system(commitArgs):wait()
85105
if u.nonZeroExit(result) then return end
86-
end
87106

88-
-- commit
89-
-- (using `--no-verify`, since we checked the pre-commit earlier already)
90-
local commitArgs = { "git", "commit", "--no-verify", "--message=" .. title }
91-
if body then table.insert(commitArgs, "--message=" .. body) end
92-
local result = vim.system(commitArgs):wait()
93-
if u.nonZeroExit(result) then return end
107+
-- notification
108+
local extra = ""
109+
if opts.pushIfClean then
110+
extra = cleanAfterCommit and "Pushing…" or "Not pushing since repo still dirty."
111+
end
112+
postCommitNotif("Smart commit", doStageAllChanges, title, extra)
94113

95-
-- notification
96-
local extra = ""
97-
if opts.pushIfClean then
98-
extra = cleanAfterCommit and "Pushing…" or "Not pushing since repo still dirty."
99-
end
100-
postCommitNotif("Smart commit", doStageAllChanges, title, extra)
114+
-- push
115+
if opts.pushIfClean and cleanAfterCommit then
116+
require("tinygit.commands.push-pull").push({ pullBefore = opts.pullBeforePush }, true)
117+
end
101118

102-
-- push
103-
if opts.pushIfClean and cleanAfterCommit then
104-
require("tinygit.commands.push-pull").push({ pullBefore = opts.pullBeforePush }, true)
105-
end
119+
require("tinygit.statusline").updateAllComponents()
120+
end)
121+
end
106122

107-
require("tinygit.statusline").updateAllComponents()
108-
end)
123+
if repoHasPrecommitHook() then
124+
u.notify("Running pre-commit hook…")
125+
126+
-- open commit window async, in case of slow precommit hook (#44)
127+
vim.system(
128+
{ "git", "hook", "run", "pre-commit" },
129+
vim.schedule_wrap(function(hookResult)
130+
-- hide info notification in case of snacks.nvim
131+
if package.loaded["snacks"] then require("snacks").notifier.hide() end
132+
133+
-- don't open input if hook fails
134+
if hookResult.code ~= 0 then
135+
local msg = (hookResult.stdout or "") .. (hookResult.stderr or "")
136+
msg = "[Pre-commit hook failed]\n\n" .. msg
137+
u.notify(msg, "error")
138+
return
139+
end
140+
141+
startCommit()
142+
end)
143+
)
144+
else
145+
startCommit()
146+
end
109147
end
110148

111149
---@param opts? { forcePushIfDiverged?: boolean, stageAllIfNothingStaged?: boolean }

0 commit comments

Comments
 (0)