rails_git_hooks 0.6.1 → 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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -2
  3. data/README.md +149 -83
  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 +85 -64
  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 -5
  36. data/templates/hooks/commit-msg +13 -0
  37. data/templates/hooks/pre-commit +13 -0
  38. data/templates/hooks/pre-push +13 -0
  39. metadata +33 -5
  40. data/lib/rails_git_hooks/installer.rb +0 -118
  41. data/lib/rails_git_hooks/templates/commit-msg +0 -32
  42. data/lib/rails_git_hooks/templates/pre-commit +0 -58
  43. data/lib/rails_git_hooks/templates/pre-push +0 -21
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '039dc0fb243e8ce670d6cadeb3c3e398cc6441276953c9a0c1e8f8541864256e'
4
- data.tar.gz: 26a46c6776054ec41554c60ad4ea06f48c14f703cd9c396735f2888b6b0f329b
3
+ metadata.gz: b95455f85a5e5f3789055e84fc610dcfa199f593d3fc34e5dfb94b781b0ad050
4
+ data.tar.gz: d2f37971fb832ba3c0e2b3651c7d27b4ca8a7665d6fdac879a43eac8ca38b022
5
5
  SHA512:
6
- metadata.gz: 589952d05b4bbc277efc18f59a02bdd51ba2edeae20b26fb1b093186bdd57413a4949e25987b291b546b06f29b2c81cff394901cd6c6dd9527a1fd1c0f3f0101
7
- data.tar.gz: d79a92159cd66cf640b1fc80a29ade822fd6e3215be50915aec2609301f7edd71a13b1b83252e4b94adbb95feed759f1dfb78eb80e1f23abd800419992a6a24f
6
+ metadata.gz: 93b5d01ffd6b14ed1d94543a41e95286afbcaa1f808d9c1d4045dd193e9de3a9f02b3b8a5d5374eadaea450028b28ee89a09c1af20869e30cd8cdeeb8620f259
7
+ data.tar.gz: 549b45c9aeb5f9607c5aa779d4fdf71196d7d08525f79cfde5e3bf2c3a7df107aa34e397258d4218e45a026e6ed474ab6bb4fc906174af0876eb61ba5cd4e44b
data/CHANGELOG.md CHANGED
@@ -4,11 +4,39 @@ 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.6.1] (latest)
7
+ ## [0.7.1]
8
+
9
+ ### Added
10
+
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]
28
+
29
+ ### Changed
30
+
31
+ - **Overcommit-style layout:** Hook templates live in `templates/hooks/` (was `lib/rails_git_hooks/templates/`). `rake sync_hooks` copies them to `hooks/` for manual install.
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.
33
+ - Gemspec includes `templates/**/*`. Version set to 0.7.0.
34
+
35
+ ## [0.6.1]
8
36
 
9
37
  ### Changed
10
38
 
11
- - **Default install** now installs **commit-msg** and **pre-commit** only (Jira ticket prefix + default-branch protection + RuboCop). Pre-push remains opt-in: `rails_git_hooks install pre-push`.
39
+ - **Default install** now installs **commit-msg** and **pre-commit** only (Jira ticket prefix + default-branch protection; RuboCop opt-in). Pre-push remains opt-in: `rails_git_hooks install pre-push`.
12
40
  - README: quick start and commands table updated for default (commit-msg + pre-commit); Jira project key / `JIRA_PROJECT_KEY` for manual install; pre-push install instruction.
13
41
 
14
42
  ## [0.6.0]
data/README.md CHANGED
@@ -1,164 +1,230 @@
1
- # Git Hooks
1
+ # Rails Git Hooks
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/rails_git_hooks.svg)](https://badge.fury.io/rb/rails_git_hooks)
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
- > Automate Jira ticket prefixes and RuboCop checks with git hooks. Built for Rails and Ruby projects.
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 you get
11
+ The project now uses a **code-first hook runtime**:
12
12
 
13
- | Hook | What it does |
14
- |------|--------------|
15
- | **commit-msg** | Prepends `[TICKET-123]` to commit messages when your branch name contains a Jira ticket. |
16
- | **pre-commit** | Blocks commits to `master`/`main` and runs RuboCop on staged `.rb` files. |
17
- | **pre-push** | Runs the full test suite (`bundle exec rspec`) before push; aborts 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
- Hooks can be disabled temporarily (e.g. for quick WIP commits or CI) without uninstalling.
18
+ ## Included hooks
20
19
 
21
- ---
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 |
22
25
 
23
26
  ## Quick start
24
27
 
25
- **1. Install the gem**
28
+ ### 1. Install the gem
26
29
 
27
30
  ```bash
28
31
  gem install rails_git_hooks
29
32
  ```
30
33
 
31
- Or with Bundler — add to your `Gemfile`:
34
+ Or add it to your `Gemfile`:
32
35
 
33
36
  ```ruby
34
37
  gem "rails_git_hooks"
35
38
  ```
36
39
 
37
- Then:
40
+ Then install dependencies:
38
41
 
39
42
  ```bash
40
43
  bundle install
41
- bundle exec rails_git_hooks install
42
44
  ```
43
45
 
44
- This installs **commit-msg** (Jira ticket prefix) and **pre-commit** (blocks commits on `master`/`main` + RuboCop on staged `.rb` files) by default.
46
+ ### 2. Install hooks
47
+
48
+ From your project root:
45
49
 
46
- **2. Set your Jira project key**
50
+ ```bash
51
+ bundle exec rails_git_hooks install
52
+ ```
47
53
 
48
- Replace the default by passing your project key at install time or via env:
54
+ This installs `commit-msg` and `pre-commit` by default.
55
+
56
+ ### 3. Inspect available checks
49
57
 
50
58
  ```bash
51
- rails_git_hooks install --jira MYPROJ
52
- # or
53
- export GIT_HOOKS_JIRA_PROJECT=MYPROJ
54
- rails_git_hooks install
59
+ bundle exec rails_git_hooks list
55
60
  ```
56
61
 
57
- Default is `APD` if not set. For manual install: replace `JIRA_PROJECT_KEY` in the commit-msg script with your key (e.g. `APD`).
62
+ ### 4. Override behavior only when needed
58
63
 
59
- To also run the full test suite before push:
60
- `rails_git_hooks install pre-push`
64
+ Enable RuboCop:
61
65
 
62
- > **Tip:** If the pre-commit hook doesn’t run, make it executable: `chmod +x .git/hooks/pre-commit`
66
+ ```bash
67
+ bundle exec rails_git_hooks enable rubocop-check
68
+ ```
63
69
 
64
- ---
70
+ Make debugger statements fail instead of warn:
65
71
 
66
- ## Commands
72
+ ```bash
73
+ bundle exec rails_git_hooks set debugger-check on_fail fail
74
+ ```
67
75
 
68
- Run from your project root (inside a git repo).
76
+ Disable migrations warnings:
69
77
 
70
- | Command | Description |
71
- |---------|-------------|
72
- | `rails_git_hooks install [HOOK...] [--jira PROJECT]` | Install hooks. No args = install default (commit-msg + pre-commit). |
73
- | `rails_git_hooks list` | List available hook names. |
74
- | `rails_git_hooks disable HOOK [HOOK...] [whitespace-check]` | Disable hooks (use `*` for all) or the whitespace-check. |
75
- | `rails_git_hooks enable HOOK [HOOK...] [whitespace-check]` | Re-enable hooks or enable whitespace-check. |
76
- | `rails_git_hooks disabled` | Show which hooks are currently disabled. |
78
+ ```bash
79
+ bundle exec rails_git_hooks disable migrations-check
80
+ ```
77
81
 
78
- **Examples**
82
+ Show effective config:
79
83
 
80
84
  ```bash
81
- # Install everything with custom Jira key
82
- rails_git_hooks install --jira MYPROJ
85
+ bundle exec rails_git_hooks show-config
86
+ ```
87
+
88
+ ## Config model
89
+
90
+ You do **not** need a config file for the defaults to work.
83
91
 
84
- # Install only specific hooks
85
- rails_git_hooks install pre-commit commit-msg --jira APD
92
+ If you want overrides, create one:
86
93
 
87
- # Temporarily disable pre-commit (e.g. for a quick fix)
88
- rails_git_hooks disable pre-commit
94
+ ```bash
95
+ bundle exec rails_git_hooks init
96
+ ```
97
+
98
+ This creates `.rails_git_hooks.yml`. The file is sparse: only your changes are stored there.
89
99
 
90
- # Disable all hooks
91
- rails_git_hooks disable *
100
+ Example:
92
101
 
93
- # Turn them back on
94
- rails_git_hooks enable pre-commit
102
+ ```yaml
103
+ PreCommit:
104
+ DebuggerCheck:
105
+ on_fail: fail
95
106
 
96
- # Enable rejection of trailing whitespace and conflict markers in staged files (off by default)
97
- rails_git_hooks enable whitespace-check
107
+ RuboCop:
108
+ enabled: true
109
+ quiet: true
110
+ include:
111
+ - "app/**/*.rb"
112
+ - "lib/**/*.rb"
113
+ exclude:
114
+ - "db/schema.rb"
98
115
  ```
99
116
 
100
- Disabled state is stored in `.git/rails_git_hooks_disabled` and persists until you run `enable`.
117
+ ### Supported per-check options
101
118
 
102
- ---
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 |
103
129
 
104
- ## Hooks in detail
130
+ ## Dependency model
105
131
 
106
- ### commit-msg Jira ticket prefix
132
+ Checks can declare dependencies such as:
107
133
 
108
- If your branch name contains a Jira ticket (e.g. `task/APD-1234/fix-bug`), the hook prepends `[APD-1234] ` to the commit message unless it’s already there.
134
+ - executables like `bundle`
135
+ - Ruby libraries like `rubocop`
136
+ - required files
109
137
 
110
- **Example**
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.
111
139
 
112
- - Branch: `task/APD-1234/fix-bug`
113
- - You run: `git commit -m 'fix bug'`
114
- - Result: **`[APD-1234] fix bug`**
140
+ ## Default checks
115
141
 
116
- Set the Jira project key at install time with `--jira PROJECT` or `GIT_HOOKS_JIRA_PROJECT`.
142
+ ### `commit-msg`
117
143
 
118
- ---
144
+ - `jira-prefix`
145
+ - enabled by default
146
+ - prefixes commit messages with `[TICKET-123]` when the branch name contains a Jira-style ticket ID
119
147
 
120
- ### pre-commit — Default branch protection + RuboCop
148
+ ### `pre-commit`
121
149
 
122
- 1. **Blocks commits on `master` / `main`** — You must commit from a feature branch; direct commits to the default branch are rejected.
123
- 2. **Runs RuboCop** on staged `.rb` files. If there are offenses, the commit is aborted.
124
- 3. **Trailing whitespace / conflict markers** (off by default) — When enabled, rejects commits that add trailing spaces/tabs or `<<<<<<<` / `=======` / `>>>>>>>` in staged files. Enable with: `rails_git_hooks enable whitespace-check`. Disable with: `rails_git_hooks disable whitespace-check`.
150
+ - `default-branch`
151
+ - enabled by default
152
+ - fails on `master` / `main`
125
153
 
126
- Requires the `rubocop` gem in your project. If the hook doesn’t run, ensure it’s executable: `chmod +x .git/hooks/pre-commit`.
154
+ - `debugger-check`
155
+ - enabled by default
156
+ - warns on debugger statements in Ruby, JavaScript/TypeScript, and Python
127
157
 
128
- ---
158
+ - `yaml-format-check`
159
+ - enabled by default
160
+ - warns on invalid `.yml` / `.yaml`
129
161
 
130
- ### pre-push — Run tests before push
162
+ - `json-format-check`
163
+ - enabled by default
164
+ - warns on invalid `.json`
131
165
 
132
- Runs `bundle exec rspec` before every `git push`. If the test suite fails, the push is aborted so you don’t break CI.
166
+ - `migrations-check`
167
+ - enabled by default
168
+ - warns when schema/data schema files appear to be missing after migrations
133
169
 
134
- Requires the `rspec` gem (or for Minitest, edit the hook to use `bundle exec rake test`). If the hook doesn’t run, ensure it’s executable: `chmod +x .git/hooks/pre-push`.
170
+ - `whitespace-check`
171
+ - disabled by default
172
+ - fails on trailing whitespace and merge conflict markers
135
173
 
136
- ---
174
+ - `rubocop-check`
175
+ - disabled by default
176
+ - runs RuboCop on staged Ruby files
177
+ - default dependency behavior warns if `rubocop` is missing
137
178
 
138
- ## Manual installation (without the gem)
179
+ ### `pre-push`
139
180
 
140
- If you don’t want to use the gem, copy the scripts from `hooks/` into `.git/hooks`. The `hooks/` directory is synced from the gem templates via `rake sync_hooks` in development.
181
+ - `run-tests`
182
+ - enabled by default when `pre-push` is installed
183
+ - runs `bundle exec rspec`
141
184
 
142
- | File | Notes |
143
- |------|--------|
144
- | [commit-msg](hooks/commit-msg) | Replace `JIRA_PROJECT_KEY` in the script with your Jira project key (e.g. `APD`). |
145
- | [pre-commit](hooks/pre-commit) | Requires the `rubocop` gem in the repo where you use it. |
146
- | [pre-push](hooks/pre-push) | Runs `bundle exec rspec`; edit to use `bundle exec rake test` for Minitest. |
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 |
147
196
 
148
- ---
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
 
152
221
  ```bash
153
222
  bundle install
154
- bundle exec rake # run specs (default task)
223
+ bundle exec rake # run specs
155
224
  bundle exec rake build # build the gem
156
- bundle exec rake install # install the gem locally
157
- bundle exec rake sync_hooks # copy templates to hooks/
225
+ bundle exec rake install # install locally
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