rails_git_hooks 0.7.0 → 0.7.1

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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -6
  3. data/README.md +143 -77
  4. data/lib/rails_git_hooks/checks/base.rb +55 -0
  5. data/lib/rails_git_hooks/checks/commit_msg/jira_prefix.rb +31 -0
  6. data/lib/rails_git_hooks/checks/commit_msg.rb +10 -0
  7. data/lib/rails_git_hooks/checks/pre_commit/debugger_check.rb +43 -0
  8. data/lib/rails_git_hooks/checks/pre_commit/default_branch.rb +20 -0
  9. data/lib/rails_git_hooks/checks/pre_commit/json_format_check.rb +30 -0
  10. data/lib/rails_git_hooks/checks/pre_commit/migrations_check.rb +37 -0
  11. data/lib/rails_git_hooks/checks/pre_commit/rubocop.rb +30 -0
  12. data/lib/rails_git_hooks/checks/pre_commit/whitespace_check.rb +31 -0
  13. data/lib/rails_git_hooks/checks/pre_commit/yaml_format_check.rb +31 -0
  14. data/lib/rails_git_hooks/checks/pre_commit.rb +16 -0
  15. data/lib/rails_git_hooks/checks/pre_push/run_tests.rb +24 -0
  16. data/lib/rails_git_hooks/checks/pre_push.rb +10 -0
  17. data/lib/rails_git_hooks/checks.rb +6 -0
  18. data/lib/rails_git_hooks/cli.rb +78 -59
  19. data/lib/rails_git_hooks/config/constants.rb +20 -0
  20. data/lib/rails_git_hooks/config/defaults.yml +127 -0
  21. data/lib/rails_git_hooks/config/defaults_loader.rb +42 -0
  22. data/lib/rails_git_hooks/core/check_definition.rb +63 -0
  23. data/lib/rails_git_hooks/core/check_result.rb +41 -0
  24. data/lib/rails_git_hooks/core/error.rb +5 -0
  25. data/lib/rails_git_hooks/install/installer.rb +53 -0
  26. data/lib/rails_git_hooks/runtime/check_registry.rb +29 -0
  27. data/lib/rails_git_hooks/runtime/dependency_checker.rb +51 -0
  28. data/lib/rails_git_hooks/runtime/file_matcher.rb +23 -0
  29. data/lib/rails_git_hooks/runtime/override_config.rb +125 -0
  30. data/lib/rails_git_hooks/runtime/policy_resolver.rb +36 -0
  31. data/lib/rails_git_hooks/runtime/repository.rb +61 -0
  32. data/lib/rails_git_hooks/runtime/runner.rb +76 -0
  33. data/lib/rails_git_hooks/runtime.rb +25 -0
  34. data/lib/rails_git_hooks/version.rb +1 -1
  35. data/lib/rails_git_hooks.rb +14 -6
  36. data/templates/hooks/commit-msg +7 -17
  37. data/templates/hooks/pre-commit +7 -21
  38. data/templates/hooks/pre-push +7 -17
  39. metadata +30 -9
  40. data/lib/rails_git_hooks/constants.rb +0 -21
  41. data/lib/rails_git_hooks/installer.rb +0 -156
  42. data/templates/shared/commit_msg/jira_prefix.rb +0 -20
  43. data/templates/shared/pre_commit/debugger_check.rb +0 -48
  44. data/templates/shared/pre_commit/default_branch.rb +0 -9
  45. data/templates/shared/pre_commit/rubocop_check.rb +0 -24
  46. data/templates/shared/pre_commit/whitespace_check.rb +0 -25
  47. data/templates/shared/pre_push/run_tests.rb +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b990407e471e8f6dddf8df8741a8ac6de92955bc44470b1b45a19b93644b542
4
- data.tar.gz: ed3f378f82db9a1dc81dcf58d90f02d12da0ff32c05697ecc2f1dbb92f8db2ca
3
+ metadata.gz: b95455f85a5e5f3789055e84fc610dcfa199f593d3fc34e5dfb94b781b0ad050
4
+ data.tar.gz: d2f37971fb832ba3c0e2b3651c7d27b4ca8a7665d6fdac879a43eac8ca38b022
5
5
  SHA512:
6
- metadata.gz: 0b1aa8a31af84e697e05f56d94ff3427a0afa82e0dfabe4de21e5f0ec865f7ba6c7ddd0537f058e852281a415f68b26a063fd6ef8e71f0d0f1a0b7f9c718aa04
7
- data.tar.gz: 966f4fbd5dcfac4ce1e4a9ad1d8a3f8209f0d2885030ece2d916c5598e2324aeac348f7bc8e9690e60bd3676fc995e264e0c2e2b1fd80e51cd1563d4cf3bb4d0
6
+ metadata.gz: 93b5d01ffd6b14ed1d94543a41e95286afbcaa1f808d9c1d4045dd193e9de3a9f02b3b8a5d5374eadaea450028b28ee89a09c1af20869e30cd8cdeeb8620f259
7
+ data.tar.gz: 549b45c9aeb5f9607c5aa779d4fdf71196d7d08525f79cfde5e3bf2c3a7df107aa34e397258d4218e45a026e6ed474ab6bb4fc906174af0876eb61ba5cd4e44b
data/CHANGELOG.md CHANGED
@@ -4,11 +4,27 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
6
6
 
7
- ## [0.7.0] (latest)
7
+ ## [0.7.1]
8
8
 
9
9
  ### Added
10
10
 
11
- - **Pre-commit debugger check** — Warns (does not block) when staged files contain debugger statements: Ruby (`binding.pry`, `debugger`, `byebug`, `binding.irb`), JavaScript/TypeScript (`.js`, `.jsx`, `.ts`, `.tsx`, `.mjs`, `.cjs` — `debugger`), Python (`breakpoint()`, `pdb.set_trace()`, `ipdb.set_trace()`). On by default with pre-commit.
11
+ - **Code-first runtime architecture** — Hook behavior now lives in Ruby classes with a central runner, registry, policy resolver, dependency checker, and sparse override config loader.
12
+ - **Sparse override config** — Added optional `.rails_git_hooks.yml` with Overcommit-style per-check overrides for `enabled`, `quiet`, `on_fail`, `on_warn`, `on_missing_dependency`, `include`, `exclude`, and `command`.
13
+ - **Thin hook bootstraps** — `templates/hooks/*` now dispatch into the embedded `rails_git_hooks` runtime instead of loading copied script files per check.
14
+ - **Pre-commit YAML format check** — Warns (does not block) when any staged `.yml` or `.yaml` file fails to parse (invalid YAML). Reports file and line from the parser. On by default with pre-commit.
15
+ - **Pre-commit JSON format check** — Warns (does not block) when any staged `.json` file fails to parse (invalid JSON). Reports file and parser message. On by default with pre-commit.
16
+ - **Pre-commit migrations check** — Warns (does not block) when: (a) migration file(s) are staged but neither `db/schema.rb` nor `db/structure.sql` is staged; (b) data migration file(s) in `db/data/` or `db/data_migrate/` are staged but `db/data_schema.rb` is not. **On by default.** Disable with `rails_git_hooks disable migrations-check`.
17
+ - **Defaults YAML** — `lib/rails_git_hooks/config/defaults.yml` holds default settings for every check and hook. `DefaultsLoader` reads it; `OverrideConfig` uses it as the base when merging with `.rails_git_hooks.yml`. Edit the YAML to change built-in defaults.
18
+
19
+ ### Changed
20
+
21
+ - **CLI redesign** — Replaced per-check flag-file commands with generic config-driven commands: `install`, `list`, `init`, `enable`, `disable`, `set`, and `show-config`.
22
+ - **Manual install layout** — Manual install is via `bundle exec rails_git_hooks install` only; dropped `rake sync_hooks` and the repo `hooks/` tree.
23
+ - **Dependency handling** — Checks can now declare executables, Ruby libraries, files, and install hints, with centralized `on_missing_dependency` policy handling.
24
+ - **Runtime install** — Installer copies all runtime files (including `config/defaults.yml`), not only `*.rb`, into `.git/hooks/rails_git_hooks/`.
25
+ - **Removed `hooks/` directory** — `/hooks/` added to `.gitignore`. README manual-install section updated.
26
+
27
+ ## [0.7.0]
12
28
 
13
29
  ### Changed
14
30
 
@@ -16,10 +32,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
16
32
  - **Constants:** Extracted `lib/rails_git_hooks/constants.rb` (GEM_ROOT, HOOKS_DIR, DEFAULT_HOOKS, FEATURE_FLAG_FILES). Installer and CLI use it. Single source of truth; no config file.
17
33
  - Gemspec includes `templates/**/*`. Version set to 0.7.0.
18
34
 
19
- ### Fixed
20
-
21
- - **Hook scripts** — `RailsGitHooks::GIT_DIR` was set inside a `module` body, where the script’s local `git_dir` is out of scope (Ruby scope gate), causing `NameError` when hooks ran. All three hooks (commit-msg, pre-commit, pre-push) now set the constant via `RailsGitHooks.const_set(:GIT_DIR, git_dir.freeze)` from the script scope.
22
-
23
35
  ## [0.6.1]
24
36
 
25
37
  ### Changed
data/README.md CHANGED
@@ -4,40 +4,40 @@
4
4
  [![Build Status](https://github.com/NikitaNazarov1/rails_git_hooks/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/NikitaNazarov1/rails_git_hooks/actions/workflows/tests.yml?query=branch%3Amain)
5
5
  [![RuboCop](https://github.com/NikitaNazarov1/rails_git_hooks/actions/workflows/rubocop.yml/badge.svg?branch=main)](https://github.com/NikitaNazarov1/rails_git_hooks/actions/workflows/rubocop.yml?query=branch%3Amain)
6
6
 
7
- Most useful git hooks for Rails and Ruby. Install only what you need. Turn hooks off anytime without uninstalling.
7
+ Git hooks for Rails and Ruby projects with built-in defaults, optional sparse overrides, and Overcommit-style per-check policies.
8
8
 
9
- ---
9
+ ## What changed
10
10
 
11
- ## What’s included
11
+ The project now uses a **code-first hook runtime**:
12
12
 
13
- | Hook | Description |
14
- |------|-------------|
15
- | **commit-msg** | Adds `[TICKET-123]` to commit messages when your branch name contains a Jira ticket. |
16
- | **pre-commit** | Blocks commits to `master`/`main`. Warns about debugger statements (Ruby, JS/TS, Python). Optional: RuboCop, trailing-whitespace/conflict checks. |
17
- | **pre-push** | Runs `bundle exec rspec` before push and blocks push if tests fail. |
13
+ - checks declare their defaults in Ruby
14
+ - hook scripts are thin bootstraps
15
+ - `.rails_git_hooks.yml` is optional and contains only overrides
16
+ - checks can be configured with `enabled`, `quiet`, `on_fail`, `on_warn`, `on_missing_dependency`, `include`, and `exclude`
18
17
 
19
- - **Installed by default:** `commit-msg` and `pre-commit` (branch protection + debugger warnings; RuboCop and whitespace checks are off).
20
- - **Optional:** `pre-push`, RuboCop, and whitespace/conflict checks — enable when you want them.
18
+ ## Included hooks
21
19
 
22
- ---
20
+ | Git hook | Purpose |
21
+ |---|---|
22
+ | `commit-msg` | Prefix commit messages with Jira-style ticket IDs from the current branch |
23
+ | `pre-commit` | Run commit-time checks like branch protection, debugger detection, format validation, and optional code quality checks |
24
+ | `pre-push` | Run the test suite before push |
23
25
 
24
26
  ## Quick start
25
27
 
26
28
  ### 1. Install the gem
27
29
 
28
- **Standalone:**
29
-
30
30
  ```bash
31
31
  gem install rails_git_hooks
32
32
  ```
33
33
 
34
- **With Bundler** add to your `Gemfile`:
34
+ Or add it to your `Gemfile`:
35
35
 
36
36
  ```ruby
37
37
  gem "rails_git_hooks"
38
38
  ```
39
39
 
40
- Then:
40
+ Then install dependencies:
41
41
 
42
42
  ```bash
43
43
  bundle install
@@ -45,107 +45,176 @@ bundle install
45
45
 
46
46
  ### 2. Install hooks
47
47
 
48
- From your project root (inside the git repo):
48
+ From your project root:
49
49
 
50
50
  ```bash
51
51
  bundle exec rails_git_hooks install
52
52
  ```
53
53
 
54
- This installs **commit-msg** and **pre-commit** (default-branch protection only). No Jira project key needed — the hook detects ticket IDs like `APD-123` or `PROJ-456` from the branch name.
54
+ This installs `commit-msg` and `pre-commit` by default.
55
55
 
56
- ### 3. Optional: add more
56
+ ### 3. Inspect available checks
57
57
 
58
58
  ```bash
59
- # Run tests before every push
60
- rails_git_hooks install pre-push
59
+ bundle exec rails_git_hooks list
60
+ ```
61
61
 
62
- # Run RuboCop on staged .rb files before commit
63
- rails_git_hooks enable rubocop-check
62
+ ### 4. Override behavior only when needed
63
+
64
+ Enable RuboCop:
65
+
66
+ ```bash
67
+ bundle exec rails_git_hooks enable rubocop-check
68
+ ```
69
+
70
+ Make debugger statements fail instead of warn:
64
71
 
65
- # Reject trailing whitespace and conflict markers in staged files
66
- rails_git_hooks enable whitespace-check
72
+ ```bash
73
+ bundle exec rails_git_hooks set debugger-check on_fail fail
67
74
  ```
68
75
 
69
- **Tip:** If a hook doesn’t run, make it executable: `chmod +x .git/hooks/<hook-name>`
76
+ Disable migrations warnings:
70
77
 
71
- ---
78
+ ```bash
79
+ bundle exec rails_git_hooks disable migrations-check
80
+ ```
72
81
 
73
- ## Command reference
82
+ Show effective config:
74
83
 
75
- All commands are run from the project root.
84
+ ```bash
85
+ bundle exec rails_git_hooks show-config
86
+ ```
76
87
 
77
- | Command | Description |
78
- |---------|-------------|
79
- | `rails_git_hooks install [HOOK...]` | Install hooks. No arguments = install default (commit-msg + pre-commit). |
80
- | `rails_git_hooks list` | List available hook names. |
81
- | `rails_git_hooks disable HOOK [...]` | Disable hooks or options (use `*` for all). |
82
- | `rails_git_hooks enable HOOK [...]` | Re-enable hooks or enable optional checks. |
83
- | `rails_git_hooks disabled` | Show currently disabled hooks. |
88
+ ## Config model
89
+
90
+ You do **not** need a config file for the defaults to work.
84
91
 
85
- **Common examples:**
92
+ If you want overrides, create one:
86
93
 
87
94
  ```bash
88
- rails_git_hooks install # default hooks
89
- rails_git_hooks install pre-push # add pre-push
90
- rails_git_hooks disable pre-commit # turn off pre-commit temporarily
91
- rails_git_hooks disable * # turn off all
92
- rails_git_hooks enable pre-commit # turn pre-commit back on
93
- rails_git_hooks enable rubocop-check # run RuboCop on staged .rb files
94
- rails_git_hooks enable whitespace-check # reject trailing ws & conflict markers
95
+ bundle exec rails_git_hooks init
95
96
  ```
96
97
 
97
- Disabled state is stored in `.git/rails_git_hooks_disabled` and persists until you run `enable`.
98
+ This creates `.rails_git_hooks.yml`. The file is sparse: only your changes are stored there.
98
99
 
99
- ---
100
+ Example:
100
101
 
101
- ## Hooks in detail
102
+ ```yaml
103
+ PreCommit:
104
+ DebuggerCheck:
105
+ on_fail: fail
106
+
107
+ RuboCop:
108
+ enabled: true
109
+ quiet: true
110
+ include:
111
+ - "app/**/*.rb"
112
+ - "lib/**/*.rb"
113
+ exclude:
114
+ - "db/schema.rb"
115
+ ```
102
116
 
103
- ### commit-msg — Jira ticket prefix
117
+ ### Supported per-check options
104
118
 
105
- **What it does:** If the branch name contains a Jira-style ticket (e.g. `task/APD-1234/fix-bug`), the hook prepends `[APD-1234]` to the commit message — unless the message already starts with that format. Works with any project key (2–5 letters + digits); no config.
119
+ | Option | Meaning |
120
+ |---|---|
121
+ | `enabled` | Enable or disable a check |
122
+ | `quiet` | Hide normal output unless the check warns or fails |
123
+ | `on_fail` | Map a check failure to `fail`, `warn`, or `pass` |
124
+ | `on_warn` | Map a warning to `warn`, `fail`, or `pass` |
125
+ | `on_missing_dependency` | Control behavior when required tools/libraries are missing |
126
+ | `include` | File paths or glob patterns that the check should apply to |
127
+ | `exclude` | File paths or glob patterns to remove from the included set |
128
+ | `command` | Override the command used by command-based checks |
106
129
 
107
- **Example:**
130
+ ## Dependency model
108
131
 
109
- - Branch: `task/APD-1234/fix-bug`
110
- - Command: `git commit -m 'fix bug'`
111
- - Result: **`[APD-1234] fix bug`**
132
+ Checks can declare dependencies such as:
112
133
 
113
- ---
134
+ - executables like `bundle`
135
+ - Ruby libraries like `rubocop`
136
+ - required files
114
137
 
115
- ### pre-commit Branch protection and optional checks
138
+ If a dependency is missing, the runner produces a structured result and applies the check’s `on_missing_dependency` policy. This makes missing-tool behavior consistent across all checks.
116
139
 
117
- **Always on (when installed):**
140
+ ## Default checks
118
141
 
119
- - Blocks commits to `master` or `main`. You must commit from a feature branch.
120
- - **Debugger check** — Warns (does not block) when staged files contain debugger statements: Ruby (`binding.pry`, `debugger`, `byebug`, `binding.irb`), JavaScript/TypeScript (`.js`, `.jsx`, `.ts`, `.tsx` — `debugger`), Python (`breakpoint()`, `pdb.set_trace()`, `ipdb.set_trace()`).
142
+ ### `commit-msg`
121
143
 
122
- **Optional (off by default):**
144
+ - `jira-prefix`
145
+ - enabled by default
146
+ - prefixes commit messages with `[TICKET-123]` when the branch name contains a Jira-style ticket ID
123
147
 
124
- 1. **RuboCop** — Run RuboCop on staged `.rb` files; commit fails if there are offenses.
125
- `rails_git_hooks enable rubocop-check` / `disable rubocop-check`. Requires the `rubocop` gem.
148
+ ### `pre-commit`
126
149
 
127
- 2. **Whitespace & conflict markers** — Reject commits that add trailing spaces/tabs or `<<<<<<<` / `=======` / `>>>>>>>` in staged files.
128
- `rails_git_hooks enable whitespace-check` / `disable whitespace-check`.
150
+ - `default-branch`
151
+ - enabled by default
152
+ - fails on `master` / `main`
129
153
 
130
- ---
154
+ - `debugger-check`
155
+ - enabled by default
156
+ - warns on debugger statements in Ruby, JavaScript/TypeScript, and Python
131
157
 
132
- ### pre-push — Run tests before push
158
+ - `yaml-format-check`
159
+ - enabled by default
160
+ - warns on invalid `.yml` / `.yaml`
133
161
 
134
- Runs `bundle exec rspec` before every `git push`. If the suite fails, the push is aborted. Not installed by default; add with `rails_git_hooks install pre-push`. For Minitest, edit the hook to use `bundle exec rake test`.
162
+ - `json-format-check`
163
+ - enabled by default
164
+ - warns on invalid `.json`
135
165
 
136
- ---
166
+ - `migrations-check`
167
+ - enabled by default
168
+ - warns when schema/data schema files appear to be missing after migrations
137
169
 
138
- ## Manual installation (without the gem)
170
+ - `whitespace-check`
171
+ - disabled by default
172
+ - fails on trailing whitespace and merge conflict markers
139
173
 
140
- Copy the **entire** `hooks/` directory into your repo’s `.git/hooks/` (so the hook scripts and the `pre_commit/`, `commit_msg/`, `pre_push/` subdirs are all under `.git/hooks/`). Run `rake sync_hooks` to regenerate `hooks/` from `templates/hooks/` and `templates/shared/`.
174
+ - `rubocop-check`
175
+ - disabled by default
176
+ - runs RuboCop on staged Ruby files
177
+ - default dependency behavior warns if `rubocop` is missing
141
178
 
142
- | Script | Notes |
143
- |--------|--------|
144
- | [commit-msg](hooks/commit-msg) | Jira-style ticket prefix; no config. |
145
- | [pre-commit](hooks/pre-commit) | Branch protection + debugger warnings; optional RuboCop (requires `rubocop` gem). |
146
- | [pre-push](hooks/pre-push) | Runs `bundle exec rspec`; edit for Minitest if needed. |
179
+ ### `pre-push`
147
180
 
148
- ---
181
+ - `run-tests`
182
+ - enabled by default when `pre-push` is installed
183
+ - runs `bundle exec rspec`
184
+
185
+ ## CLI reference
186
+
187
+ | Command | Description |
188
+ |---|---|
189
+ | `rails_git_hooks install [HOOK...]` | Install hook scripts into `.git/hooks` |
190
+ | `rails_git_hooks list` | List available git hooks and check keys |
191
+ | `rails_git_hooks init` | Create `.rails_git_hooks.yml` |
192
+ | `rails_git_hooks enable CHECK_NAME` | Set `enabled: true` for a check override |
193
+ | `rails_git_hooks disable CHECK_NAME` | Set `enabled: false` for a check override |
194
+ | `rails_git_hooks set CHECK_NAME OPTION VALUE` | Set a single override option |
195
+ | `rails_git_hooks show-config` | Print effective merged configuration |
196
+
197
+ Examples:
198
+
199
+ ```bash
200
+ rails_git_hooks install
201
+ rails_git_hooks install pre-push
202
+ rails_git_hooks enable rubocop-check
203
+ rails_git_hooks disable migrations-check
204
+ rails_git_hooks set debugger-check on_fail fail
205
+ rails_git_hooks set rubocop-check quiet true
206
+ rails_git_hooks show-config
207
+ ```
208
+
209
+ ## Manual installation
210
+
211
+ From your project (with the gem in the Gemfile or installed), run:
212
+
213
+ ```bash
214
+ bundle exec rails_git_hooks install
215
+ ```
216
+
217
+ This installs the thin hook bootstraps and the embedded `rails_git_hooks` runtime into `.git/hooks/`.
149
218
 
150
219
  ## Development
151
220
 
@@ -154,11 +223,8 @@ bundle install
154
223
  bundle exec rake # run specs
155
224
  bundle exec rake build # build the gem
156
225
  bundle exec rake install # install locally
157
- bundle exec rake sync_hooks # copy templates (hooks + shared subdirs) → hooks/
158
226
  ```
159
227
 
160
- ---
161
-
162
228
  ## License
163
229
 
164
230
  MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'open3'
5
+ require 'yaml'
6
+
7
+ module GitHooks
8
+ module Checks
9
+ class Base
10
+ class << self
11
+ attr_reader :definition
12
+
13
+ def check_definition(key:, hook:, description:, config_name: name.split('::').last, **options)
14
+ @definition = CheckDefinition.new(
15
+ key: key,
16
+ config_name: config_name,
17
+ hook: hook,
18
+ klass: self,
19
+ description: description,
20
+ **options
21
+ )
22
+ end
23
+ end
24
+
25
+ attr_reader :config, :context
26
+
27
+ def initialize(config:, context:)
28
+ @config = config
29
+ @context = context
30
+ end
31
+
32
+ private
33
+
34
+ def repo
35
+ context.fetch(:repo)
36
+ end
37
+
38
+ def applicable_files
39
+ context.fetch(:applicable_files, [])
40
+ end
41
+
42
+ def argv
43
+ context.fetch(:argv, [])
44
+ end
45
+
46
+ def stdin
47
+ context.fetch(:stdin, '')
48
+ end
49
+
50
+ def capture(*command)
51
+ Open3.capture2e(*command, chdir: repo.root)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module CommitMsg
6
+ class JiraPrefix < Base
7
+ TICKET_PATTERN = /([A-Z]{2,5}-\d+)/.freeze
8
+
9
+ check_definition key: 'jira-prefix',
10
+ hook: :commit_msg,
11
+ description: 'Prefix commit messages with ticket id from branch'
12
+
13
+ def run
14
+ message_file = argv.first
15
+ return CheckResult.pass unless message_file && File.file?(message_file)
16
+
17
+ branch = repo.current_branch
18
+ ticket = branch[TICKET_PATTERN, 1]
19
+ return CheckResult.pass unless ticket
20
+
21
+ message = File.read(message_file)
22
+ prefix = "[#{ticket}]"
23
+ return CheckResult.pass if message.lstrip.start_with?(prefix)
24
+
25
+ File.write(message_file, "#{prefix} #{message}")
26
+ CheckResult.pass
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module CommitMsg
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'commit_msg/jira_prefix'
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PreCommit
6
+ class DebuggerCheck < Base
7
+ DEBUGGER_PATTERNS = {
8
+ '.rb' => [/\bbinding\.pry\b/, /\bbinding\.irb\b/, /\bdebugger\b/, /\bbyebug\b/],
9
+ '.js' => [/\bdebugger\s*;?/],
10
+ '.jsx' => [/\bdebugger\s*;?/],
11
+ '.ts' => [/\bdebugger\s*;?/],
12
+ '.tsx' => [/\bdebugger\s*;?/],
13
+ '.mjs' => [/\bdebugger\s*;?/],
14
+ '.cjs' => [/\bdebugger\s*;?/],
15
+ '.py' => [/\bbreakpoint\s*\(\s*\)/, /\bpdb\.set_trace\s*\(\s*\)/, /\bipdb\.set_trace\s*\(\s*\)/]
16
+ }.freeze
17
+
18
+ check_definition key: 'debugger-check',
19
+ hook: :pre_commit,
20
+ description: 'Warn on debugger statements',
21
+ file_based: true,
22
+ on_fail: :warn
23
+
24
+ def run
25
+ warnings = []
26
+
27
+ applicable_files.each do |path|
28
+ next unless File.file?(path)
29
+
30
+ patterns = DEBUGGER_PATTERNS[File.extname(path)]
31
+ next unless patterns
32
+
33
+ File.read(path).lines.each_with_index do |line, index|
34
+ warnings << "#{path}:#{index + 1}: debugger statement" if patterns.any? { |pattern| line.match?(pattern) }
35
+ end
36
+ end
37
+
38
+ warnings.empty? ? CheckResult.pass : CheckResult.fail(messages: warnings.uniq)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PreCommit
6
+ class DefaultBranch < Base
7
+ check_definition key: 'default-branch',
8
+ hook: :pre_commit,
9
+ description: 'Prevent commits on default branch'
10
+
11
+ def run
12
+ branch = repo.current_branch
13
+ return CheckResult.pass unless %w[master main].include?(branch)
14
+
15
+ CheckResult.fail(messages: ["Commits on '#{branch}' are not allowed. Create a feature branch."])
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PreCommit
6
+ class JSONFormatCheck < Base
7
+ check_definition key: 'json-format-check',
8
+ hook: :pre_commit,
9
+ description: 'Warn on invalid JSON',
10
+ file_based: true,
11
+ on_fail: :warn
12
+
13
+ def run
14
+ warnings = []
15
+
16
+ applicable_files.each do |path|
17
+ next unless File.file?(path)
18
+ next unless File.extname(path) == '.json'
19
+
20
+ JSON.parse(File.read(path))
21
+ rescue JSON::ParserError => e
22
+ warnings << "#{path}: #{e.message}"
23
+ end
24
+
25
+ warnings.empty? ? CheckResult.pass : CheckResult.fail(messages: warnings)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PreCommit
6
+ class MigrationsCheck < Base
7
+ check_definition key: 'migrations-check',
8
+ hook: :pre_commit,
9
+ description: 'Warn on missing schema files after migrations',
10
+ file_based: true,
11
+ on_fail: :warn
12
+
13
+ def run
14
+ messages = []
15
+ migration_files = applicable_files.grep(%r{\Adb/migrate/.*\.rb\z})
16
+ schema_staged = applicable_files.include?('db/schema.rb') || applicable_files.include?('db/structure.sql')
17
+
18
+ if migration_files.any? && !schema_staged
19
+ messages << 'Migration file(s) are staged but neither db/schema.rb nor db/structure.sql is staged.'
20
+ messages << 'Run `rails db:migrate` and add db/schema.rb (or db/structure.sql) to your commit.'
21
+ migration_files.each { |path| messages << "- #{path}" }
22
+ end
23
+
24
+ data_migration_files = applicable_files.grep(%r{\Adb/(data/|data_migrate/).*\.rb\z})
25
+ data_schema_staged = applicable_files.include?('db/data_schema.rb')
26
+ if data_migration_files.any? && !data_schema_staged
27
+ messages << 'Data migration file(s) are staged but db/data_schema.rb is not staged.'
28
+ messages << 'Run your data migrate task and add db/data_schema.rb to your commit.'
29
+ data_migration_files.each { |path| messages << "- #{path}" }
30
+ end
31
+
32
+ messages.empty? ? CheckResult.pass : CheckResult.fail(messages: messages)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PreCommit
6
+ class RuboCop < Base
7
+ check_definition key: 'rubocop-check',
8
+ hook: :pre_commit,
9
+ description: 'Run RuboCop on staged Ruby files',
10
+ file_based: true,
11
+ enabled: false,
12
+ quiet: true,
13
+ dependencies: { 'executables' => ['bundle'], 'libraries' => ['rubocop'] },
14
+ command: %w[bundle exec rubocop],
15
+ install_hint: 'Add `gem "rubocop"` to your Gemfile and run bundle install'
16
+
17
+ def run
18
+ ruby_files = applicable_files.select { |path| File.extname(path) == '.rb' && File.file?(path) }
19
+ return CheckResult.pass if ruby_files.empty?
20
+
21
+ output, status = capture(*Array(config['command']), *ruby_files)
22
+ return CheckResult.pass if status.success?
23
+
24
+ messages = output.split("\n").map(&:rstrip).reject(&:empty?)
25
+ CheckResult.fail(messages: messages)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PreCommit
6
+ class WhitespaceCheck < Base
7
+ check_definition key: 'whitespace-check',
8
+ hook: :pre_commit,
9
+ description: 'Reject trailing whitespace and conflict markers',
10
+ file_based: true,
11
+ enabled: false
12
+
13
+ def run
14
+ errors = []
15
+
16
+ applicable_files.each do |path|
17
+ next unless File.file?(path)
18
+
19
+ File.read(path).lines.each_with_index do |line, index|
20
+ errors << "#{path}:#{index + 1}: trailing whitespace" if line.match?(/[ \t]\z/)
21
+ stripped = line.strip
22
+ errors << "#{path}:#{index + 1}: conflict marker" if stripped.start_with?('<<<<<<<', '=======', '>>>>>>>')
23
+ end
24
+ end
25
+
26
+ errors.empty? ? CheckResult.pass : CheckResult.fail(messages: errors.uniq)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end