Skip to content

Commit c22bacb

Browse files
committed
Add send hook args option
Supports cmd, file and String tasks
1 parent a8759d3 commit c22bacb

File tree

4 files changed

+92
-10
lines changed

4 files changed

+92
-10
lines changed

config/config.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@ config :git_hooks,
55
commit_msg: [
66
verbose: true,
77
tasks: [
8-
{:file, "./priv/test_script"}
8+
{:file, "./priv/test_script", include_hook_args?: true},
9+
{:cmd, "elixir ./priv/test_task.ex", include_hook_args?: true}
910
]
1011
],
1112
pre_commit: [

lib/mix/tasks/git_hooks/run.ex

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,27 @@ defmodule Mix.Tasks.GitHooks.Run do
2424

2525
@opaque git_hook_args :: list(String.t())
2626

27+
@typedoc """
28+
Run options:
29+
30+
* `include_hook_args?`: Whether the git hook args should be sent to the
31+
command to be executed. In case of `true`, the args will be amended to the
32+
command. Defaults to `false`.
33+
"""
34+
@type run_opts :: [{:include_hook_args, String.t()}]
35+
36+
@doc """
37+
Runs a task for a given git hook.
38+
39+
The task can be one of three different types:
40+
41+
* `{:cmd, "command arg1 arg2"}`: Runs a command.
42+
* `{:file, "path_to_file"}`: Runs an executable file.
43+
* `"command arg1 arg2"`: Runs a simple command, supports no options.
44+
45+
The first two options above can use a third element in the tuple, see
46+
[here](`t:run_opts/0`) more info about the options.
47+
"""
2748
@impl true
2849
@spec run(list(String.t())) :: :ok | no_return
2950
def run([]), do: error_exit()
@@ -46,34 +67,55 @@ defmodule Mix.Tasks.GitHooks.Run do
4667
end
4768

4869
@spec run_task(String.t(), atom, git_hook_args()) :: :ok | no_return
49-
@spec run_task({:file, String.t()}, atom, git_hook_args()) :: :ok | no_return
70+
@spec run_task({:file, String.t(), run_opts()}, atom, git_hook_args()) :: :ok | no_return
71+
@spec run_task({:cmd, String.t(), run_opts()}, atom, git_hook_args()) :: :ok | no_return
5072
defp run_task({:file, script_file}, git_hook_type, git_hook_args) do
73+
run_task({:file, script_file, []}, git_hook_type, git_hook_args)
74+
end
75+
76+
defp run_task({:file, script_file, opts}, git_hook_type, git_hook_args) do
77+
args =
78+
if Keyword.get(opts, :include_hook_args?, false) do
79+
git_hook_args
80+
else
81+
[]
82+
end
83+
5184
script_file
5285
|> Path.absname()
5386
|> System.cmd(
54-
git_hook_args,
55-
stderr_to_stdout: true,
87+
args,
5688
into: Config.io_stream(git_hook_type)
5789
)
5890
end
5991

60-
defp run_task(task, git_hook_type, _git_hook_args) do
61-
[command | args] = String.split(task, " ")
92+
defp run_task({:cmd, command}, git_hook_type, git_hook_args) do
93+
run_task({:cmd, command, []}, git_hook_type, git_hook_args)
94+
end
95+
96+
defp run_task({:cmd, command, opts}, git_hook_type, git_hook_args) when is_list(opts) do
97+
[command | args] = String.split(command, " ")
98+
99+
command_args =
100+
if Keyword.get(opts, :include_hook_args?, false) do
101+
Enum.concat(args, git_hook_args)
102+
else
103+
args
104+
end
62105

63106
command
64107
|> System.cmd(
65-
args,
66-
stderr_to_stdout: true,
108+
command_args,
67109
into: Config.io_stream(git_hook_type)
68110
)
69111
|> case do
70112
{_result, 0} ->
71-
Printer.success("`#{task}` was successful")
113+
Printer.success("`#{command}` was successful")
72114

73115
{result, _} ->
74116
if !Config.verbose?(git_hook_type), do: IO.puts(result)
75117

76-
Printer.error("#{Atom.to_string(git_hook_type)} failed on `#{task}`")
118+
Printer.error("#{Atom.to_string(git_hook_type)} failed on `#{command}`")
77119
error_exit()
78120
end
79121
rescue
@@ -82,6 +124,16 @@ defmodule Mix.Tasks.GitHooks.Run do
82124
error_exit()
83125
end
84126

127+
defp run_task(command, git_hook_type, git_hook_args) when is_binary(command) do
128+
run_task({:cmd, command, []}, git_hook_type, git_hook_args)
129+
end
130+
131+
defp run_task(task, git_hook_type, _git_hook_args),
132+
do:
133+
raise("""
134+
Invalid task #{inspect(task)} for hook #{inspect(git_hook_type)}", only String, {:file, ""} or {:cmd, ""} are supported.
135+
""")
136+
85137
@spec get_atom_from_arg(String.t()) :: atom | no_return
86138
defp get_atom_from_arg(git_hook_type_arg) do
87139
case git_hook_type_arg do

priv/test_task.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
IO.inspect("Elixir file")
2+
IO.inspect(System.argv())

test/mix/tasks/git_hooks/run_test.exs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,33 @@ defmodule Mix.Tasks.RunTest do
1818
assert Run.run(["pre-commit"]) == :ok
1919
end) =~ "Test script"
2020
end
21+
22+
test "when it is a command then the command it's executed" do
23+
put_git_hook_config(:pre_commit, tasks: [{:cmd, "echo 'test command'"}], verbose: true)
24+
25+
assert capture_io(fn ->
26+
assert Run.run(["pre-commit"]) == :ok
27+
end) =~ "test command"
28+
end
29+
30+
test "when it is a string then the command it's executed" do
31+
put_git_hook_config(:pre_commit, tasks: ["echo 'test string command'"], verbose: true)
32+
33+
assert capture_io(fn ->
34+
assert Run.run(["pre-commit"]) == :ok
35+
end) =~ "test string command"
36+
end
37+
38+
test "when the config is unknown then an error is raised" do
39+
put_git_hook_config(:pre_commit,
40+
tasks: [{:cmd, "echo 'test string command'", :make_it_fail}],
41+
verbose: true
42+
)
43+
44+
capture_io(fn ->
45+
assert_raise RuntimeError, fn -> Run.run(["pre-commit"]) end
46+
end)
47+
end
2148
end
2249

2350
describe "Given args for the mix git hook task" do

0 commit comments

Comments
 (0)