vagrant-git-hooks 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +19 -0
- data/LICENSE.md +21 -0
- data/README.md +84 -0
- data/lib/vagrant-git-hooks/VERSION +1 -0
- data/lib/vagrant-git-hooks/command.rb +227 -0
- data/lib/vagrant-git-hooks/config.rb +91 -0
- data/lib/vagrant-git-hooks/helpers.rb +133 -0
- data/lib/vagrant-git-hooks/hook_builder.rb +69 -0
- data/lib/vagrant-git-hooks/plugin.rb +112 -0
- data/lib/vagrant-git-hooks/service.rb +90 -0
- data/lib/vagrant-git-hooks/util/git.rb +58 -0
- data/lib/vagrant-git-hooks/util/installer.rb +159 -0
- data/lib/vagrant-git-hooks/util/manifest.rb +61 -0
- data/lib/vagrant-git-hooks/util/verbose.rb +22 -0
- data/lib/vagrant-git-hooks/version.rb +12 -0
- data/lib/vagrant-git-hooks.rb +4 -0
- data/locales/en.yml +76 -0
- data/locales/fr.yml +75 -0
- metadata +113 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "vagrant"
|
|
4
|
+
require "i18n"
|
|
5
|
+
|
|
6
|
+
require_relative "version"
|
|
7
|
+
require_relative "helpers"
|
|
8
|
+
require_relative "config"
|
|
9
|
+
require_relative "command"
|
|
10
|
+
require_relative "service"
|
|
11
|
+
require_relative "util/git"
|
|
12
|
+
|
|
13
|
+
module VagrantGitHooks
|
|
14
|
+
class Plugin < Vagrant.plugin("2")
|
|
15
|
+
name "vagrant-git-hooks"
|
|
16
|
+
|
|
17
|
+
description <<~DESC
|
|
18
|
+
Manage git hooks declaratively from your Vagrantfile. A husky replacement
|
|
19
|
+
that needs no Node/npm, only Vagrant: it installs hook scripts into the
|
|
20
|
+
repository's hooks directory with ownership markers and backups.
|
|
21
|
+
DESC
|
|
22
|
+
|
|
23
|
+
config(:git_hooks) do
|
|
24
|
+
Config
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
command("hooks") do
|
|
28
|
+
Command
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
%i[machine_action_up machine_action_provision machine_action_reload].each do |hook_name|
|
|
32
|
+
action_hook(:vgh_install, hook_name) do |hook|
|
|
33
|
+
hook.append(Action::Install)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
action_hook(:vgh_uninstall, :machine_action_destroy) do |hook|
|
|
38
|
+
hook.prepend(Action::Uninstall)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
module Action
|
|
43
|
+
class Install
|
|
44
|
+
def initialize(app, _env) = (@app = app)
|
|
45
|
+
|
|
46
|
+
def call(env)
|
|
47
|
+
UiHelpers.setup_i18n!
|
|
48
|
+
cfg = env[:machine].config.git_hooks
|
|
49
|
+
UiHelpers.setup_locale_from_config!(cfg)
|
|
50
|
+
ui = env[:ui]
|
|
51
|
+
root = env[:machine].env.root_path.to_s
|
|
52
|
+
|
|
53
|
+
run(cfg, ui, root) if cfg.install_on_up
|
|
54
|
+
rescue StandardError => e
|
|
55
|
+
UiHelpers.error(env[:ui], "VGH: #{e.message}")
|
|
56
|
+
ensure
|
|
57
|
+
@app.call(env)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def run(cfg, ui, root)
|
|
63
|
+
unless Util::Git.available? && Util::Git.repo?(root)
|
|
64
|
+
UiHelpers.warn(ui, "#{UiHelpers.e(:warning)} #{UiHelpers.t("messages.not_a_repo")}")
|
|
65
|
+
return
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
res = Service.install!(root: root, cfg: cfg, ui: ui)
|
|
69
|
+
if res[:installed].empty? && res[:updated].empty?
|
|
70
|
+
UiHelpers.say(ui, "#{UiHelpers.e(:info)} #{UiHelpers.t("messages.no_hooks")}")
|
|
71
|
+
return
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
UiHelpers.say(ui, "#{UiHelpers.e(:success)} #{UiHelpers.t("messages.installed",
|
|
75
|
+
count: res[:installed].size + res[:updated].size)}")
|
|
76
|
+
return if res[:backed_up].empty?
|
|
77
|
+
|
|
78
|
+
UiHelpers.say(ui, "#{UiHelpers.e(:info)} #{UiHelpers.t("messages.backed_up",
|
|
79
|
+
list: res[:backed_up].join(", "))}")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
class Uninstall
|
|
84
|
+
def initialize(app, _env) = (@app = app)
|
|
85
|
+
|
|
86
|
+
def call(env)
|
|
87
|
+
UiHelpers.setup_i18n!
|
|
88
|
+
cfg = env[:machine].config.git_hooks
|
|
89
|
+
UiHelpers.setup_locale_from_config!(cfg)
|
|
90
|
+
ui = env[:ui]
|
|
91
|
+
root = env[:machine].env.root_path.to_s
|
|
92
|
+
|
|
93
|
+
run(cfg, ui, root) if cfg.remove_on_destroy
|
|
94
|
+
rescue StandardError => e
|
|
95
|
+
UiHelpers.error(env[:ui], "VGH: #{e.message}")
|
|
96
|
+
ensure
|
|
97
|
+
@app.call(env)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
private
|
|
101
|
+
|
|
102
|
+
def run(cfg, ui, root)
|
|
103
|
+
return unless Util::Git.available? && Util::Git.repo?(root)
|
|
104
|
+
|
|
105
|
+
res = Service.uninstall!(root: root, cfg: cfg, ui: ui)
|
|
106
|
+
return if res[:removed].empty? && res[:restored].empty?
|
|
107
|
+
|
|
108
|
+
UiHelpers.say(ui, "#{UiHelpers.e(:broom)} #{UiHelpers.t("messages.removed", count: res[:removed].size)}")
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "config"
|
|
4
|
+
require_relative "util/git"
|
|
5
|
+
require_relative "util/manifest"
|
|
6
|
+
require_relative "util/installer"
|
|
7
|
+
|
|
8
|
+
module VagrantGitHooks
|
|
9
|
+
module Service
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Builds installable hook entries from direct config and an optional source directory.
|
|
13
|
+
#
|
|
14
|
+
# @param cfg [VagrantGitHooks::Config] Plugin configuration.
|
|
15
|
+
# @param root [String, Pathname] Git repository root.
|
|
16
|
+
# @return [Array<Hash>] Hook entries consumed by the installer.
|
|
17
|
+
def entries_from_config(cfg, root)
|
|
18
|
+
entries = {}
|
|
19
|
+
|
|
20
|
+
(cfg.hooks || {}).each do |name, cmd|
|
|
21
|
+
entries[name.to_s] = { name: name.to_s, commands: cmd }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
adopt_source(cfg, root, entries)
|
|
25
|
+
entries.values
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def adopt_source(cfg, root, entries)
|
|
29
|
+
# Explicit Vagrantfile hooks win over hooks_source files with the same name.
|
|
30
|
+
src = cfg.hooks_source.to_s.strip
|
|
31
|
+
return if src.empty?
|
|
32
|
+
|
|
33
|
+
dir = File.absolute_path?(src) ? src : File.expand_path(src, root.to_s)
|
|
34
|
+
return unless File.directory?(dir)
|
|
35
|
+
|
|
36
|
+
Dir.children(dir).sort.each do |fname|
|
|
37
|
+
next unless Config::KNOWN_HOOKS.include?(fname)
|
|
38
|
+
|
|
39
|
+
path = File.join(dir, fname)
|
|
40
|
+
next unless File.file?(path)
|
|
41
|
+
next if entries.key?(fname)
|
|
42
|
+
|
|
43
|
+
entries[fname] = { name: fname, content: File.read(path) }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def installer_for(root, ui: nil)
|
|
48
|
+
Util::Installer.new(
|
|
49
|
+
hooks_dir: Util::Git.hooks_dir(root),
|
|
50
|
+
manifest: Util::Manifest.new(Util::Git.manifest_path(root)),
|
|
51
|
+
ui: ui
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Installs or updates hooks for a Git repository.
|
|
56
|
+
#
|
|
57
|
+
# @param root [String, Pathname] Git repository root.
|
|
58
|
+
# @param cfg [VagrantGitHooks::Config] Plugin configuration.
|
|
59
|
+
# @param ui [Object, nil] Optional Vagrant UI object.
|
|
60
|
+
# @param dry_run [Boolean] Whether to report planned changes without writing files.
|
|
61
|
+
# @return [Hash] Installation result.
|
|
62
|
+
def install!(root:, cfg:, ui: nil, dry_run: false)
|
|
63
|
+
installer_for(root, ui: ui).install(
|
|
64
|
+
entries_from_config(cfg, root),
|
|
65
|
+
fail_on_error: cfg.fail_on_error,
|
|
66
|
+
dry_run: dry_run
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Removes managed hooks and restores backed-up foreign hooks.
|
|
71
|
+
#
|
|
72
|
+
# @param root [String, Pathname] Git repository root.
|
|
73
|
+
# @param ui [Object, nil] Optional Vagrant UI object.
|
|
74
|
+
# @param dry_run [Boolean] Whether to report planned changes without writing files.
|
|
75
|
+
# @return [Hash] Uninstall result.
|
|
76
|
+
def uninstall!(root:, ui: nil, dry_run: false)
|
|
77
|
+
installer_for(root, ui: ui).uninstall(dry_run: dry_run)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Reports hook installation status for configured and previously managed hooks.
|
|
81
|
+
#
|
|
82
|
+
# @param root [String, Pathname] Git repository root.
|
|
83
|
+
# @param cfg [VagrantGitHooks::Config] Plugin configuration.
|
|
84
|
+
# @return [Array<Hash>] Status rows keyed by hook name.
|
|
85
|
+
def status(root:, cfg:)
|
|
86
|
+
configured = entries_from_config(cfg, root).map { |e| e[:name].to_s }
|
|
87
|
+
installer_for(root).status(configured)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "open3"
|
|
4
|
+
require_relative "verbose"
|
|
5
|
+
|
|
6
|
+
module VagrantGitHooks
|
|
7
|
+
module Util
|
|
8
|
+
module Git
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def available?
|
|
12
|
+
Verbose.log("git", "--version")
|
|
13
|
+
_out, _err, st = Open3.capture3("git", "--version")
|
|
14
|
+
st.success?
|
|
15
|
+
rescue StandardError
|
|
16
|
+
false
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def repo?(root)
|
|
20
|
+
out, _err, st = capture(root, "rev-parse", "--is-inside-work-tree")
|
|
21
|
+
st.success? && out.strip == "true"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def toplevel(root)
|
|
25
|
+
out, _err, st = capture(root, "rev-parse", "--show-toplevel")
|
|
26
|
+
st.success? && !out.strip.empty? ? out.strip : nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def hooks_dir(root)
|
|
30
|
+
resolve(root, "hooks") || File.join(toplevel(root) || root.to_s, ".git", "hooks")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def manifest_path(root)
|
|
34
|
+
resolve(root, "vagrant-git-hooks.json") ||
|
|
35
|
+
File.join(toplevel(root) || root.to_s, ".git", "vagrant-git-hooks.json")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def resolve(root, name)
|
|
39
|
+
out, _err, st = capture(root, "rev-parse", "--git-path", name)
|
|
40
|
+
return nil unless st.success?
|
|
41
|
+
|
|
42
|
+
path = out.strip
|
|
43
|
+
return nil if path.empty?
|
|
44
|
+
|
|
45
|
+
absolute?(path) ? path : File.expand_path(path, root.to_s)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def capture(root, *args)
|
|
49
|
+
Verbose.log("git", "-C", root.to_s, *args)
|
|
50
|
+
Open3.capture3("git", "-C", root.to_s, *args)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def absolute?(path)
|
|
54
|
+
File.absolute_path?(path)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "time"
|
|
6
|
+
require_relative "../hook_builder"
|
|
7
|
+
|
|
8
|
+
module VagrantGitHooks
|
|
9
|
+
module Util
|
|
10
|
+
class Installer
|
|
11
|
+
BACKUP_SUFFIX = ".bak"
|
|
12
|
+
|
|
13
|
+
def initialize(hooks_dir:, manifest:, ui: nil)
|
|
14
|
+
@hooks_dir = hooks_dir.to_s
|
|
15
|
+
@manifest = manifest
|
|
16
|
+
@ui = ui
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Installs or updates managed Git hooks.
|
|
20
|
+
#
|
|
21
|
+
# Existing foreign hooks are backed up once before replacement. Managed
|
|
22
|
+
# hooks are updated in place and tracked in the manifest by SHA-256 digest.
|
|
23
|
+
#
|
|
24
|
+
# @param entries [Array<Hash>] Hook definitions with `:name` and either `:commands` or `:content`.
|
|
25
|
+
# @param fail_on_error [Boolean] Whether generated hooks should include `set -e`.
|
|
26
|
+
# @param dry_run [Boolean] Whether to report planned changes without writing files.
|
|
27
|
+
# @return [Hash] Installation result with installed, updated, backed_up, and dry_run keys.
|
|
28
|
+
def install(entries, fail_on_error: true, dry_run: false)
|
|
29
|
+
FileUtils.mkdir_p(@hooks_dir) unless dry_run
|
|
30
|
+
data = @manifest.load
|
|
31
|
+
data["hooks_dir"] = @hooks_dir
|
|
32
|
+
managed = data["managed"]
|
|
33
|
+
result = { installed: [], updated: [], backed_up: [], dry_run: dry_run }
|
|
34
|
+
|
|
35
|
+
entries.each do |entry|
|
|
36
|
+
name = entry[:name].to_s
|
|
37
|
+
script = build(entry, fail_on_error)
|
|
38
|
+
path = File.join(@hooks_dir, name)
|
|
39
|
+
ours = File.exist?(path) && HookBuilder.managed?(safe_read(path))
|
|
40
|
+
|
|
41
|
+
backup = backup_if_foreign(path, name, ours, managed, result, dry_run)
|
|
42
|
+
write_hook(path, script) unless dry_run
|
|
43
|
+
|
|
44
|
+
(ours ? result[:updated] : result[:installed]) << name
|
|
45
|
+
managed[name] = {
|
|
46
|
+
"backup" => backup,
|
|
47
|
+
"sha256" => Digest::SHA256.hexdigest(script),
|
|
48
|
+
"installed_at" => now
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
@manifest.save(data) unless dry_run
|
|
53
|
+
result
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Removes managed hooks and restores backups recorded in the manifest.
|
|
57
|
+
#
|
|
58
|
+
# @param dry_run [Boolean] Whether to report planned changes without writing files.
|
|
59
|
+
# @return [Hash] Uninstall result with removed, restored, and dry_run keys.
|
|
60
|
+
def uninstall(dry_run: false)
|
|
61
|
+
data = @manifest.load
|
|
62
|
+
base = data["hooks_dir"] || @hooks_dir
|
|
63
|
+
result = { removed: [], restored: [], dry_run: dry_run }
|
|
64
|
+
|
|
65
|
+
data["managed"].each do |name, info|
|
|
66
|
+
path = File.join(base, name)
|
|
67
|
+
|
|
68
|
+
if File.exist?(path) && HookBuilder.managed?(safe_read(path))
|
|
69
|
+
File.delete(path) unless dry_run
|
|
70
|
+
result[:removed] << name
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
backup = info["backup"]
|
|
74
|
+
next unless backup && File.exist?(backup)
|
|
75
|
+
|
|
76
|
+
restore(backup, path) unless dry_run
|
|
77
|
+
result[:restored] << name
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@manifest.clear unless dry_run
|
|
81
|
+
result
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Reports whether hooks are installed, modified, foreign, or missing.
|
|
85
|
+
#
|
|
86
|
+
# @param configured_names [Array<String>] Hook names configured in the current Vagrantfile.
|
|
87
|
+
# @return [Array<Hash>] Status rows with name, state, and backup fields.
|
|
88
|
+
def status(configured_names = [])
|
|
89
|
+
data = @manifest.load
|
|
90
|
+
base = data["hooks_dir"] || @hooks_dir
|
|
91
|
+
managed = data["managed"]
|
|
92
|
+
names = (managed.keys + configured_names.map(&:to_s)).uniq.sort
|
|
93
|
+
|
|
94
|
+
names.map do |name|
|
|
95
|
+
{ name: name, state: state_of(File.join(base, name), managed[name]), backup: managed.dig(name, "backup") }
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def build(entry, fail_on_error)
|
|
102
|
+
if entry[:content]
|
|
103
|
+
HookBuilder.adopt_script(name: entry[:name].to_s, content: entry[:content])
|
|
104
|
+
else
|
|
105
|
+
HookBuilder.script_for(name: entry[:name].to_s, commands: entry[:commands], fail_on_error: fail_on_error)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def backup_if_foreign(path, name, ours, managed, result, dry_run)
|
|
110
|
+
# Do not overwrite an existing backup: it is the user's original hook,
|
|
111
|
+
# not a rolling history of plugin-generated updates.
|
|
112
|
+
existing = managed.dig(name, "backup")
|
|
113
|
+
return existing unless File.exist?(path) && !ours && existing.nil?
|
|
114
|
+
|
|
115
|
+
backup = "#{path}#{BACKUP_SUFFIX}"
|
|
116
|
+
return backup if File.exist?(backup)
|
|
117
|
+
|
|
118
|
+
FileUtils.cp(path, backup) unless dry_run
|
|
119
|
+
result[:backed_up] << name
|
|
120
|
+
backup
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def write_hook(path, script)
|
|
124
|
+
File.write(path, script)
|
|
125
|
+
File.chmod(0o755, path) unless Gem.win_platform?
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def restore(backup, path)
|
|
129
|
+
FileUtils.mv(backup, path)
|
|
130
|
+
File.chmod(0o755, path) unless Gem.win_platform?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def state_of(path, info)
|
|
134
|
+
# A managed hook with a changed digest is still ours, but status reports
|
|
135
|
+
# it as modified so users can spot manual edits.
|
|
136
|
+
return "missing" unless File.exist?(path)
|
|
137
|
+
return "foreign" unless HookBuilder.managed?(safe_read(path))
|
|
138
|
+
|
|
139
|
+
if info && Digest::SHA256.hexdigest(safe_read(path)) != info["sha256"]
|
|
140
|
+
"modified"
|
|
141
|
+
else
|
|
142
|
+
"installed"
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def safe_read(path)
|
|
147
|
+
File.read(path)
|
|
148
|
+
rescue StandardError
|
|
149
|
+
""
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def now
|
|
153
|
+
Time.now.utc.iso8601
|
|
154
|
+
rescue StandardError
|
|
155
|
+
Time.now.utc.to_s
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module VagrantGitHooks
|
|
7
|
+
module Util
|
|
8
|
+
class Manifest
|
|
9
|
+
attr_reader :path
|
|
10
|
+
|
|
11
|
+
def initialize(path)
|
|
12
|
+
@path = path.to_s
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Loads the hook manifest from disk.
|
|
16
|
+
#
|
|
17
|
+
# Invalid or missing manifest data is treated as empty so hook commands can
|
|
18
|
+
# recover from interrupted runs.
|
|
19
|
+
#
|
|
20
|
+
# @return [Hash] Normalized manifest data.
|
|
21
|
+
def load
|
|
22
|
+
return empty unless File.exist?(@path)
|
|
23
|
+
|
|
24
|
+
data = JSON.parse(File.read(@path))
|
|
25
|
+
data.is_a?(Hash) ? normalize(data) : empty
|
|
26
|
+
rescue StandardError
|
|
27
|
+
empty
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Saves normalized hook manifest data.
|
|
31
|
+
#
|
|
32
|
+
# @param data [Hash] Manifest data.
|
|
33
|
+
# @return [Hash] Original data argument.
|
|
34
|
+
def save(data)
|
|
35
|
+
FileUtils.mkdir_p(File.dirname(@path))
|
|
36
|
+
File.write(@path, JSON.pretty_generate(normalize(data)))
|
|
37
|
+
data
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def clear
|
|
41
|
+
File.delete(@path) if File.exist?(@path)
|
|
42
|
+
true
|
|
43
|
+
rescue StandardError
|
|
44
|
+
false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def empty
|
|
50
|
+
{ "hooks_dir" => nil, "managed" => {} }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def normalize(data)
|
|
54
|
+
{
|
|
55
|
+
"hooks_dir" => data["hooks_dir"],
|
|
56
|
+
"managed" => (data["managed"].is_a?(Hash) ? data["managed"] : {})
|
|
57
|
+
}
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shellwords"
|
|
4
|
+
|
|
5
|
+
module VagrantGitHooks
|
|
6
|
+
module Util
|
|
7
|
+
module Verbose
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def enabled?
|
|
11
|
+
ENV["VGH_VERBOSE"].to_s == "1"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def log(*args)
|
|
15
|
+
return unless enabled?
|
|
16
|
+
|
|
17
|
+
line = args.length == 1 && args.first.is_a?(String) ? args.first : args.map(&:to_s).shelljoin
|
|
18
|
+
warn("[VGH] #{line}")
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/locales/en.yml
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
en:
|
|
2
|
+
vgh:
|
|
3
|
+
emoji:
|
|
4
|
+
success: "✅"
|
|
5
|
+
info: "🔍"
|
|
6
|
+
ongoing: "🔁"
|
|
7
|
+
warning: "⚠️"
|
|
8
|
+
error: "❌"
|
|
9
|
+
version: "💾"
|
|
10
|
+
broom: "🧹"
|
|
11
|
+
question: "❓"
|
|
12
|
+
messages:
|
|
13
|
+
not_a_repo: "Not a git repository — skipping git hooks."
|
|
14
|
+
no_hooks: "No git hooks configured."
|
|
15
|
+
installed: "%{count} git hook(s) installed."
|
|
16
|
+
backed_up: "Backed up pre-existing hook(s): %{list}."
|
|
17
|
+
removed: "%{count} managed git hook(s) removed."
|
|
18
|
+
restored: "%{count} pre-existing hook(s) restored."
|
|
19
|
+
list_header: "Git hooks managed by the plugin:"
|
|
20
|
+
usage:
|
|
21
|
+
banner: "Usage: vagrant hooks <install|uninstall|list|run|version|help> [options]"
|
|
22
|
+
run: "Usage: vagrant hooks run <hook> [args...]"
|
|
23
|
+
errors:
|
|
24
|
+
unknown_command: "Unknown command. Try: vagrant hooks help."
|
|
25
|
+
no_machine: "No target machine found. Run this inside a Vagrant project or pass a VM name."
|
|
26
|
+
not_a_repo: "Not a git repository. Run this inside a git working tree."
|
|
27
|
+
cancelled: "Cancelled by user."
|
|
28
|
+
confirmation_required: "Confirmation required: pass --yes to proceed in --json mode."
|
|
29
|
+
hook_not_configured: "Hook '%{name}' is not configured in this Vagrantfile."
|
|
30
|
+
prompts:
|
|
31
|
+
uninstall: "This will remove all managed git hooks and restore backups. Continue? [y/N]"
|
|
32
|
+
log:
|
|
33
|
+
version_line: "v%{version}."
|
|
34
|
+
help:
|
|
35
|
+
general_title: "Usage: vagrant hooks <command> [args] [options]"
|
|
36
|
+
commands:
|
|
37
|
+
install: "hooks install Install configured hooks into .git/hooks"
|
|
38
|
+
uninstall: "hooks uninstall Remove managed hooks and restore backups"
|
|
39
|
+
list: "hooks list Show managed hooks and their state"
|
|
40
|
+
run: "hooks run <hook> [args] Run a configured hook on demand (host)"
|
|
41
|
+
version: "hooks version Print the plugin version"
|
|
42
|
+
help: "hooks help [TOPIC] Show this help or a subcommand's help"
|
|
43
|
+
topic:
|
|
44
|
+
install: |
|
|
45
|
+
🔧 Help: vagrant hooks install
|
|
46
|
+
Usage:
|
|
47
|
+
vagrant hooks install [--dry-run] [--json]
|
|
48
|
+
Description:
|
|
49
|
+
Writes each configured hook into the repository hooks directory with a
|
|
50
|
+
managed header. A pre-existing, non-managed hook is backed up to
|
|
51
|
+
<hook>.bak before being replaced.
|
|
52
|
+
uninstall: |
|
|
53
|
+
🧹 Help: vagrant hooks uninstall
|
|
54
|
+
Usage:
|
|
55
|
+
vagrant hooks uninstall [--yes] [--json]
|
|
56
|
+
Description:
|
|
57
|
+
Removes hooks managed by the plugin and restores any <hook>.bak backup.
|
|
58
|
+
list: |
|
|
59
|
+
📋 Help: vagrant hooks list
|
|
60
|
+
Usage:
|
|
61
|
+
vagrant hooks list [--json]
|
|
62
|
+
Description:
|
|
63
|
+
Shows each hook with its state: installed, missing, modified, or foreign.
|
|
64
|
+
run: |
|
|
65
|
+
▶️ Help: vagrant hooks run
|
|
66
|
+
Usage:
|
|
67
|
+
vagrant hooks run <hook> [args...]
|
|
68
|
+
Description:
|
|
69
|
+
Executes a configured hook on the host, useful for testing it without
|
|
70
|
+
making a commit.
|
|
71
|
+
version: |
|
|
72
|
+
💾 Help: vagrant hooks version
|
|
73
|
+
Usage:
|
|
74
|
+
vagrant hooks version [--json]
|
|
75
|
+
Description:
|
|
76
|
+
Prints the plugin version.
|
data/locales/fr.yml
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
fr:
|
|
2
|
+
vgh:
|
|
3
|
+
emoji:
|
|
4
|
+
success: "✅"
|
|
5
|
+
info: "🔍"
|
|
6
|
+
ongoing: "🔁"
|
|
7
|
+
warning: "⚠️"
|
|
8
|
+
error: "❌"
|
|
9
|
+
version: "💾"
|
|
10
|
+
broom: "🧹"
|
|
11
|
+
question: "❓"
|
|
12
|
+
messages:
|
|
13
|
+
not_a_repo: "Pas un dépôt git — hooks ignorés."
|
|
14
|
+
no_hooks: "Aucun hook git configuré."
|
|
15
|
+
installed: "%{count} hook(s) git installé(s)."
|
|
16
|
+
backed_up: "Hook(s) préexistant(s) sauvegardé(s) : %{list}."
|
|
17
|
+
removed: "%{count} hook(s) git géré(s) supprimé(s)."
|
|
18
|
+
restored: "%{count} hook(s) préexistant(s) restauré(s)."
|
|
19
|
+
list_header: "Hooks git gérés par le plugin :"
|
|
20
|
+
usage:
|
|
21
|
+
banner: "Utilisation : vagrant hooks <install|uninstall|list|run|version|help> [options]"
|
|
22
|
+
run: "Utilisation : vagrant hooks run <hook> [args...]"
|
|
23
|
+
errors:
|
|
24
|
+
unknown_command: "Commande inconnue. Essayez : vagrant hooks help."
|
|
25
|
+
no_machine: "Aucune machine cible. Lancez ceci dans un projet Vagrant ou passez un nom de VM."
|
|
26
|
+
not_a_repo: "Pas un dépôt git. Lancez ceci dans une copie de travail git."
|
|
27
|
+
cancelled: "Annulé par l'utilisateur."
|
|
28
|
+
confirmation_required: "Confirmation requise : ajoutez --yes pour confirmer en mode --json."
|
|
29
|
+
hook_not_configured: "Le hook '%{name}' n'est pas configuré dans ce Vagrantfile."
|
|
30
|
+
prompts:
|
|
31
|
+
uninstall: "Ceci supprimera tous les hooks gérés et restaurera les sauvegardes. Continuer ? [y/N]"
|
|
32
|
+
log:
|
|
33
|
+
version_line: "v%{version}."
|
|
34
|
+
help:
|
|
35
|
+
general_title: "Utilisation : vagrant hooks <commande> [args] [options]"
|
|
36
|
+
commands:
|
|
37
|
+
install: "hooks install Installe les hooks configurés dans .git/hooks"
|
|
38
|
+
uninstall: "hooks uninstall Supprime les hooks gérés et restaure les sauvegardes"
|
|
39
|
+
list: "hooks list Affiche les hooks gérés et leur état"
|
|
40
|
+
run: "hooks run <hook> [args] Exécute un hook configuré à la demande (hôte)"
|
|
41
|
+
version: "hooks version Affiche la version du plugin"
|
|
42
|
+
help: "hooks help [SUJET] Affiche cette aide ou celle d'une sous-commande"
|
|
43
|
+
topic:
|
|
44
|
+
install: |
|
|
45
|
+
🔧 Aide : vagrant hooks install
|
|
46
|
+
Utilisation :
|
|
47
|
+
vagrant hooks install [--dry-run] [--json]
|
|
48
|
+
Description :
|
|
49
|
+
Écrit chaque hook configuré dans le dossier de hooks du dépôt avec un
|
|
50
|
+
en-tête géré. Un hook préexistant non géré est sauvegardé en
|
|
51
|
+
<hook>.bak avant d'être remplacé.
|
|
52
|
+
uninstall: |
|
|
53
|
+
🧹 Aide : vagrant hooks uninstall
|
|
54
|
+
Utilisation :
|
|
55
|
+
vagrant hooks uninstall [--yes] [--json]
|
|
56
|
+
Description :
|
|
57
|
+
Supprime les hooks gérés par le plugin et restaure toute sauvegarde <hook>.bak.
|
|
58
|
+
list: |
|
|
59
|
+
📋 Aide : vagrant hooks list
|
|
60
|
+
Utilisation :
|
|
61
|
+
vagrant hooks list [--json]
|
|
62
|
+
Description :
|
|
63
|
+
Affiche chaque hook avec son état : installed, missing, modified ou foreign.
|
|
64
|
+
run: |
|
|
65
|
+
▶️ Aide : vagrant hooks run
|
|
66
|
+
Utilisation :
|
|
67
|
+
vagrant hooks run <hook> [args...]
|
|
68
|
+
Description :
|
|
69
|
+
Exécute un hook configuré sur l'hôte, utile pour le tester sans commit.
|
|
70
|
+
version: |
|
|
71
|
+
💾 Aide : vagrant hooks version
|
|
72
|
+
Utilisation :
|
|
73
|
+
vagrant hooks version [--json]
|
|
74
|
+
Description :
|
|
75
|
+
Affiche la version du plugin.
|