rails_git_hooks 0.7.0 → 0.7.2

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -6
  3. data/README.md +72 -115
  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/post_checkout/bundle_install.rb +16 -0
  8. data/lib/rails_git_hooks/checks/post_checkout/db_migrate.rb +16 -0
  9. data/lib/rails_git_hooks/checks/post_checkout.rb +11 -0
  10. data/lib/rails_git_hooks/checks/post_merge/bundle_install.rb +16 -0
  11. data/lib/rails_git_hooks/checks/post_merge/db_migrate.rb +16 -0
  12. data/lib/rails_git_hooks/checks/post_merge.rb +11 -0
  13. data/lib/rails_git_hooks/checks/pre_commit/debugger_check.rb +43 -0
  14. data/lib/rails_git_hooks/checks/pre_commit/default_branch.rb +20 -0
  15. data/lib/rails_git_hooks/checks/pre_commit/json_format_check.rb +30 -0
  16. data/lib/rails_git_hooks/checks/pre_commit/migrations_check.rb +37 -0
  17. data/lib/rails_git_hooks/checks/pre_commit/rubocop.rb +30 -0
  18. data/lib/rails_git_hooks/checks/pre_commit/whitespace_check.rb +31 -0
  19. data/lib/rails_git_hooks/checks/pre_commit/yaml_format_check.rb +31 -0
  20. data/lib/rails_git_hooks/checks/pre_commit.rb +16 -0
  21. data/lib/rails_git_hooks/checks/pre_push/run_tests.rb +24 -0
  22. data/lib/rails_git_hooks/checks/pre_push.rb +10 -0
  23. data/lib/rails_git_hooks/checks/shared/bundle_install_check.rb +28 -0
  24. data/lib/rails_git_hooks/checks/shared/db_migrate_check.rb +28 -0
  25. data/lib/rails_git_hooks/checks.rb +10 -0
  26. data/lib/rails_git_hooks/cli.rb +17 -90
  27. data/lib/rails_git_hooks/config/constants.rb +26 -0
  28. data/lib/rails_git_hooks/config/defaults.yml +190 -0
  29. data/lib/rails_git_hooks/config/defaults_loader.rb +42 -0
  30. data/lib/rails_git_hooks/core/check_definition.rb +63 -0
  31. data/lib/rails_git_hooks/core/check_result.rb +41 -0
  32. data/lib/rails_git_hooks/core/error.rb +5 -0
  33. data/lib/rails_git_hooks/install/installer.rb +79 -0
  34. data/lib/rails_git_hooks/runtime/check_registry.rb +33 -0
  35. data/lib/rails_git_hooks/runtime/dependency_checker.rb +51 -0
  36. data/lib/rails_git_hooks/runtime/file_matcher.rb +23 -0
  37. data/lib/rails_git_hooks/runtime/override_config.rb +131 -0
  38. data/lib/rails_git_hooks/runtime/policy_resolver.rb +36 -0
  39. data/lib/rails_git_hooks/runtime/repository.rb +69 -0
  40. data/lib/rails_git_hooks/runtime/runner.rb +80 -0
  41. data/lib/rails_git_hooks/runtime.rb +25 -0
  42. data/lib/rails_git_hooks/version.rb +1 -1
  43. data/lib/rails_git_hooks.rb +14 -6
  44. data/templates/hooks/commit-msg +7 -17
  45. data/templates/hooks/post-checkout +13 -0
  46. data/templates/hooks/post-merge +13 -0
  47. data/templates/hooks/pre-commit +7 -21
  48. data/templates/hooks/pre-push +7 -17
  49. metadata +41 -52
  50. data/lib/rails_git_hooks/constants.rb +0 -21
  51. data/lib/rails_git_hooks/installer.rb +0 -156
  52. data/templates/shared/commit_msg/jira_prefix.rb +0 -20
  53. data/templates/shared/pre_commit/debugger_check.rb +0 -48
  54. data/templates/shared/pre_commit/default_branch.rb +0 -9
  55. data/templates/shared/pre_commit/rubocop_check.rb +0 -24
  56. data/templates/shared/pre_commit/whitespace_check.rb +0 -25
  57. 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: 8f679908a499fe6aed2f61cf433d27bf93042bf0dcf1ecfe2e4854f05b0e7b9e
4
+ data.tar.gz: 481df290f21058504a14d0b6995854861ed7d15099b40a34ec9ccdf6f18fcf3c
5
5
  SHA512:
6
- metadata.gz: 0b1aa8a31af84e697e05f56d94ff3427a0afa82e0dfabe4de21e5f0ec865f7ba6c7ddd0537f058e852281a415f68b26a063fd6ef8e71f0d0f1a0b7f9c718aa04
7
- data.tar.gz: 966f4fbd5dcfac4ce1e4a9ad1d8a3f8209f0d2885030ece2d916c5598e2324aeac348f7bc8e9690e60bd3676fc995e264e0c2e2b1fd80e51cd1563d4cf3bb4d0
6
+ metadata.gz: 63518e1bcbca51fc17881473b90a0eb510eb2a6e62b26e6471e8890f8fd4e702392b6401f92124d4cd9cc1d398ad0aa03b3c9306a4ce34dc40313d6ab028da52
7
+ data.tar.gz: c549c30ca8f646f65bdf10a7281e0f2412bb8a796e0665a80e68c05cdac6ed0a39aed58f48011e23b3eba4622db43b6764bae103e434170a601938f4e5f8291e
data/CHANGELOG.md CHANGED
@@ -4,11 +4,42 @@ 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.2]
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
+ - **post-checkout hook** — Runs after branch checkout. Includes `bundle-install` (run `bundle install` when Gemfile/Gemfile.lock changed) and `db-migrate` (run `rails db:migrate` when migrations or schema changed). Both enabled by default.
12
+ - **post-merge hook** — Runs after merge. Same checks as post-checkout: `bundle-install` and `db-migrate`, enabled by default. Keeps the app bundled and migrated when pulling or merging (inspired by [hookup](https://rubygems.org/gems/hookup)).
13
+ - **Shared check modules** — `checks/shared/bundle_install_check.rb` and `checks/shared/db_migrate_check.rb` hold common logic and options for the post-checkout and post-merge bundle/migrate checks.
14
+ - **Specs for every check** — RSpec examples for all 13 checks (pre-commit, commit-msg, pre-push, post-checkout, post-merge) under `spec/rails_git_hooks/checks/`.
15
+
16
+ ### Changed
17
+
18
+ - **Default install** — With no config file, `install` now installs **commit-msg**, **pre-commit**, **post-checkout**, and **post-merge** (post-checkout/post-merge bundle and db-migrate checks are on by default).
19
+ - **Runner** — `modified_files` now supports `post_checkout` (files changed between refs on branch checkout) and `post_merge` (files changed between ORIG_HEAD and HEAD). Repository adds `changed_files(ref1, ref2)`.
20
+ - **defaults.yml** — PostCheckout and PostMerge sections: `BundleInstall` and `DbMigrate` set to `enabled: true` by default.
21
+
22
+ ## [0.7.1]
23
+
24
+ ### Added
25
+
26
+ - **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.
27
+ - **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`.
28
+ - **Thin hook bootstraps** — `templates/hooks/*` now dispatch into the embedded `rails_git_hooks` runtime instead of loading copied script files per check.
29
+ - **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.
30
+ - **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.
31
+ - **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`.
32
+ - **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.
33
+
34
+ ### Changed
35
+
36
+ - **CLI redesign** — Replaced per-check flag-file commands with generic config-driven commands: `install`, `list`, `init`, `enable`, `disable`, `set`, and `show-config`.
37
+ - **Manual install layout** — Manual install is via `bundle exec rails_git_hooks install` only; dropped `rake sync_hooks` and the repo `hooks/` tree.
38
+ - **Dependency handling** — Checks can now declare executables, Ruby libraries, files, and install hints, with centralized `on_missing_dependency` policy handling.
39
+ - **Runtime install** — Installer copies all runtime files (including `config/defaults.yml`), not only `*.rb`, into `.git/hooks/rails_git_hooks/`.
40
+ - **Removed `hooks/` directory** — `/hooks/` added to `.gitignore`. README manual-install section updated.
41
+
42
+ ## [0.7.0]
12
43
 
13
44
  ### Changed
14
45
 
@@ -16,10 +47,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
16
47
  - **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
48
  - Gemspec includes `templates/**/*`. Version set to 0.7.0.
18
49
 
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
50
  ## [0.6.1]
24
51
 
25
52
  ### Changed
data/README.md CHANGED
@@ -4,34 +4,38 @@
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.
8
-
9
- ---
10
-
11
- ## What’s included
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. |
18
-
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.
21
-
22
- ---
7
+ Git hooks for Rails and Ruby projects: sensible defaults out of the box, optional YAML overrides, and per-check policies (enable/disable, fail vs warn, include/exclude).
8
+
9
+ ## Features
10
+
11
+ - **Code-first runtime** — Checks are Ruby classes; hook scripts are thin bootstraps that delegate to a shared runtime.
12
+ - **Sparse config** — Defaults live in the gem ([config/defaults.yml](https://github.com/NikitaNazarov1/rails_git_hooks/blob/main/lib/rails_git_hooks/config/defaults.yml)). But you can redefine them via your own yml config file in a root repository.
13
+ - **Flexible configuration options** — Per check: `enabled`, `quiet`, `on_fail`, `on_warn`, `on_missing_dependency`, `include`, `exclude`, `command`.
14
+ - **Dependency handling** — Checks declare executables/libraries; missing deps are handled by a single policy (`on_missing_dependency`).
15
+
16
+ ## Included hooks and checks
17
+
18
+ | Hook | Check key | Enabled By Default | Description |
19
+ |-------------|----------------------|---------|-------------|
20
+ | **commit-msg** | `jira-prefix` | ✅ | Prefix commit messages with Jira-style ticket IDs from the branch name (e.g. `[TICKET-123]`). |
21
+ | **pre-commit** | `default-branch` | ✅ | Block commits on `master` / `main`; prompt to use a feature branch. |
22
+ | **pre-commit** | `debugger-check` | ✅ | Warn (or fail) on debugger statements in Ruby, JavaScript/TypeScript, and Python. |
23
+ | **pre-commit** | `yaml-format-check` | ✅ | Warn on invalid `.yml` / `.yaml` files. |
24
+ | **pre-commit** | `json-format-check` | ✅ | Warn on invalid `.json` files. |
25
+ | **pre-commit** | `migrations-check` | ✅ | Warn when migration files are staged but schema/data_schema files are not. |
26
+ | **pre-commit** | `whitespace-check` | Off | Fail on trailing whitespace and merge conflict markers. |
27
+ | **pre-commit** | `rubocop-check` | Off | Run RuboCop on staged Ruby files (requires `rubocop` in the project). |
28
+ | **pre-push** | `run-tests` | Off | Run test suite before push (default: `bundle exec rspec`). Enable in config to install pre-push. |
29
+ | **post-checkout** | `bundle-install` | ✅ | Run `bundle install` when Gemfile or Gemfile.lock changed after a branch checkout. |
30
+ | **post-checkout** | `db-migrate` | ✅ | Run `rails db:migrate` when migrations or schema changed after a branch checkout. |
31
+ | **post-merge** | `bundle-install` | ✅ | Run `bundle install` when Gemfile or Gemfile.lock changed after a merge. |
32
+ | **post-merge** | `db-migrate` | ✅ | Run `rails db:migrate` when migrations or schema changed after a merge. |
23
33
 
24
34
  ## Quick start
25
35
 
26
- ### 1. Install the gem
36
+ ### 1. Add the gem
27
37
 
28
- **Standalone:**
29
-
30
- ```bash
31
- gem install rails_git_hooks
32
- ```
33
-
34
- **With Bundler** — add to your `Gemfile`:
38
+ **Gemfile:**
35
39
 
36
40
  ```ruby
37
41
  gem "rails_git_hooks"
@@ -43,121 +47,74 @@ Then:
43
47
  bundle install
44
48
  ```
45
49
 
50
+ Or install globally: `gem install rails_git_hooks`.
51
+
46
52
  ### 2. Install hooks
47
53
 
48
- From your project root (inside the git repo):
54
+ From your project root:
49
55
 
50
56
  ```bash
51
57
  bundle exec rails_git_hooks install
52
58
  ```
53
59
 
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.
60
+ With default config this installs **commit-msg**, **pre-commit**, **post-checkout**, and **post-merge** into `.git/hooks/` and copies the runtime there (pre-push is off by default; enable `run-tests` in config to add it).
55
61
 
56
- ### 3. Optional: add more
62
+ ## Configuration
57
63
 
58
- ```bash
59
- # Run tests before every push
60
- rails_git_hooks install pre-push
61
-
62
- # Run RuboCop on staged .rb files before commit
63
- rails_git_hooks enable rubocop-check
64
-
65
- # Reject trailing whitespace and conflict markers in staged files
66
- rails_git_hooks enable whitespace-check
67
- ```
68
-
69
- **Tip:** If a hook doesn’t run, make it executable: `chmod +x .git/hooks/<hook-name>`
64
+ ### Priority (low → high)
70
65
 
71
- ---
66
+ 1. **Built-in defaults** (in the gem’s `config/defaults.yml`)
67
+ 2. **`.rails_git_hooks.yml`** in the repo root (sparse overrides; commit this)
68
+ 3. **`.rails_git_hooks.local.yml`** in the repo root (optional; overrides the above, typically gitignored)
72
69
 
73
- ## Command reference
74
-
75
- All commands are run from the project root.
76
-
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. |
84
-
85
- **Common examples:**
70
+ **Create the main override file:**
86
71
 
87
72
  ```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
73
+ bundle exec rails_git_hooks init
95
74
  ```
96
75
 
97
- Disabled state is stored in `.git/rails_git_hooks_disabled` and persists until you run `enable`.
98
-
99
- ---
76
+ **Optional:** Create `.rails_git_hooks.local.yml` in the repo root for personal overrides (merged on top of `.rails_git_hooks.yml`). Add it to `.gitignore` if you don’t want to commit it.
100
77
 
101
- ## Hooks in detail
78
+ ### Example override file
102
79
 
103
- ### commit-msg — Jira ticket prefix
80
+ ```yaml
81
+ PreCommit:
82
+ DebuggerCheck:
83
+ on_fail: fail
104
84
 
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.
106
-
107
- **Example:**
108
-
109
- - Branch: `task/APD-1234/fix-bug`
110
- - Command: `git commit -m 'fix bug'`
111
- - Result: **`[APD-1234] fix bug`**
112
-
113
- ---
114
-
115
- ### pre-commit — Branch protection and optional checks
116
-
117
- **Always on (when installed):**
118
-
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()`).
121
-
122
- **Optional (off by default):**
123
-
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.
126
-
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`.
129
-
130
- ---
131
-
132
- ### pre-push — Run tests before push
133
-
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`.
135
-
136
- ---
137
-
138
- ## Manual installation (without the gem)
85
+ RuboCop:
86
+ enabled: true
87
+ quiet: true
88
+ include:
89
+ - "app/**/*.rb"
90
+ - "lib/**/*.rb"
91
+ exclude:
92
+ - "db/schema.rb"
93
+ ```
139
94
 
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/`.
95
+ ### Per-check options
141
96
 
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. |
97
+ | Option | Description |
98
+ |--------|--------------|
99
+ | `enabled` | Turn the check on or off. |
100
+ | `quiet` | Suppress normal output unless the check warns or fails. |
101
+ | `on_fail` | `fail`, `warn`, or `pass` when the check fails. |
102
+ | `on_warn` | `warn`, `fail`, or `pass` when the check would warn. |
103
+ | `on_missing_dependency` | Behavior when a required executable/library is missing. |
104
+ | `include` | Glob patterns for files the check applies to. |
105
+ | `exclude` | Glob patterns to exclude from `include`. |
106
+ | `command` | Override the command for checks that run external commands. |
147
107
 
148
- ---
108
+ ## CLI reference
149
109
 
150
- ## Development
110
+ | Command | Description |
111
+ |---------|--------------|
112
+ | `rails_git_hooks install` | Install hooks that have at least one enabled check in the merged config (defaults + .rails_git_hooks.yml + .rails_git_hooks.local.yml). |
113
+ | `rails_git_hooks init` | Create an empty `.rails_git_hooks.yml`. |
151
114
 
152
- ```bash
153
- bundle install
154
- bundle exec rake # run specs
155
- bundle exec rake build # build the gem
156
- bundle exec rake install # install locally
157
- bundle exec rake sync_hooks # copy templates (hooks + shared subdirs) → hooks/
158
- ```
115
+ ## Contributing
159
116
 
160
- ---
117
+ Contributions are welcome. Open an [issue](https://github.com/NikitaNazarov1/rails_git_hooks/issues) for bugs or ideas, or submit a pull request.
161
118
 
162
119
  ## License
163
120
 
@@ -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,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PostCheckout
6
+ class BundleInstall < Base
7
+ include BundleInstallCheck
8
+
9
+ check_definition key: 'bundle-install',
10
+ hook: :post_checkout,
11
+ description: 'Run bundle install when Gemfile or Gemfile.lock changed (branch checkout)',
12
+ **BundleInstallCheck::DEFINITION_OPTIONS
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PostCheckout
6
+ class DbMigrate < Base
7
+ include DbMigrateCheck
8
+
9
+ check_definition key: 'db-migrate',
10
+ hook: :post_checkout,
11
+ description: 'Run db:migrate when migrations or schema changed (branch checkout)',
12
+ **DbMigrateCheck::DEFINITION_OPTIONS
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PostCheckout
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'post_checkout/bundle_install'
11
+ require_relative 'post_checkout/db_migrate'
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PostMerge
6
+ class BundleInstall < Base
7
+ include BundleInstallCheck
8
+
9
+ check_definition key: 'bundle-install',
10
+ hook: :post_merge,
11
+ description: 'Run bundle install when Gemfile or Gemfile.lock changed (after merge)',
12
+ **BundleInstallCheck::DEFINITION_OPTIONS
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PostMerge
6
+ class DbMigrate < Base
7
+ include DbMigrateCheck
8
+
9
+ check_definition key: 'db-migrate',
10
+ hook: :post_merge,
11
+ description: 'Run db:migrate when migrations or schema changed (after merge)',
12
+ **DbMigrateCheck::DEFINITION_OPTIONS
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHooks
4
+ module Checks
5
+ module PostMerge
6
+ end
7
+ end
8
+ end
9
+
10
+ require_relative 'post_merge/bundle_install'
11
+ require_relative 'post_merge/db_migrate'
@@ -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