-
Notifications
You must be signed in to change notification settings - Fork 16
/
install.ex
180 lines (142 loc) · 4.75 KB
/
install.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
defmodule Mix.Tasks.GitHooks.Install do
@shortdoc "Installs the configured git hooks backing up the previous files."
@moduledoc """
Installs the configured git hooks.
Before installing the new hooks, the already hooks files are backed up
with the extension `.pre_git_hooks_backup`.
## Command line options
* `--quiet` - disables the output of the files that are being copied/backed up
To manually install the git hooks run:
```elixir
mix git_hooks.install`
```
"""
use Mix.Task
alias GitHooks.Config
alias GitHooks.Printer
alias Mix.Project
@impl true
@spec run(Keyword.t()) :: :ok
def run(args) do
{opts, _other_args, _} =
OptionParser.parse(args, switches: [quiet: :boolean], aliases: [q: :quiet])
install(opts)
end
@spec install(Keyword.t()) :: any()
defp install(opts) do
template_file =
:git_hooks
|> :code.priv_dir()
|> Path.join("/hook_template")
Printer.info("Installing git hooks...")
ensure_hooks_folder_exists()
clean_missing_hooks()
track_configured_hooks()
Config.git_hooks()
|> Enum.each(fn git_hook ->
git_hook_atom_as_string = Atom.to_string(git_hook)
git_hook_atom_as_kebab_string = Recase.to_kebab(git_hook_atom_as_string)
case File.read(template_file) do
{:ok, body} ->
target_file_path =
Project.deps_path()
|> Path.join("/../.git/hooks/#{git_hook_atom_as_kebab_string}")
target_file_body =
String.replace(body, "$git_hook", git_hook_atom_as_string, global: true)
unless opts[:quiet] || !Config.verbose?() do
Printer.info(
"Writing git hook for `#{git_hook_atom_as_string}` to `#{target_file_path}`"
)
end
backup_current_hook(git_hook_atom_as_kebab_string, opts)
File.write(target_file_path, target_file_body)
File.chmod(target_file_path, 0o755)
{:error, reason} ->
reason |> inspect() |> Printer.error()
end
end)
:ok
end
@spec backup_current_hook(String.t(), Keyword.t()) :: {:error, atom} | {:ok, non_neg_integer()}
defp backup_current_hook(git_hook_to_backup, opts) do
source_file_path =
Project.deps_path()
|> Path.join("/../.git/hooks/#{git_hook_to_backup}")
target_file_path =
Project.deps_path()
|> Path.join("/../.git/hooks/#{git_hook_to_backup}.pre_git_hooks_backup")
unless opts[:quiet] || !Config.verbose?() do
Printer.info("Backing up git hook file `#{source_file_path}` to `#{target_file_path}`")
end
File.copy(source_file_path, target_file_path)
end
@spec track_configured_hooks() :: any
defp track_configured_hooks do
git_hooks = Config.git_hooks() |> Enum.join(" ")
Project.deps_path()
|> Path.join("/../.git/hooks/git_hooks.db")
|> write_backup(git_hooks)
end
@spec write_backup(String.t(), String.t()) :: any
defp write_backup(file_path, git_hooks) do
file_path
|> File.open!([:write])
|> IO.binwrite(git_hooks)
rescue
error ->
Printer.warn(
"Couldn't find git_hooks.db file, won't be able to restore old backups: #{inspect(error)}"
)
end
@spec ensure_hooks_folder_exists() :: any
defp ensure_hooks_folder_exists do
Project.deps_path()
|> Path.join("/../.git/hooks")
|> File.mkdir_p()
end
@spec clean_missing_hooks() :: any
defp clean_missing_hooks do
configured_git_hooks =
Config.git_hooks()
|> Enum.map(&Atom.to_string/1)
Project.deps_path()
|> Path.join("/../.git/hooks/git_hooks.db")
|> File.read()
|> case do
{:ok, file} ->
file
|> String.split(" ")
|> Enum.each(fn installed_git_hook ->
if installed_git_hook not in configured_git_hooks do
git_hook_atom_as_kebab_string = Recase.to_kebab(installed_git_hook)
Printer.warn(
"Remove old git hook `#{git_hook_atom_as_kebab_string}` and restore backup"
)
Project.deps_path()
|> Path.join("/../.git/hooks/#{git_hook_atom_as_kebab_string}")
|> File.rm()
restore_backup(git_hook_atom_as_kebab_string)
end
end)
_error ->
:ok
end
end
@spec restore_backup(String.t()) :: any
defp restore_backup(git_hook_atom_as_kebab_string) do
backup_path =
Path.join(
Project.deps_path(),
"/../.git/hooks/#{git_hook_atom_as_kebab_string}.pre_git_hooks_backup"
)
restore_path =
Path.join(
Project.deps_path(),
"/../.git/hooks/#{git_hook_atom_as_kebab_string}"
)
case File.rename(backup_path, restore_path) do
:ok -> :ok
{:error, reason} -> Printer.warn("Cannot restore backup: #{inspect(reason)}")
end
end
end