diff --git a/.git-hooks/pre_commit/master_hooks_match.rb b/.git-hooks/pre_commit/master_hooks_match.rb
new file mode 100644
index 00000000..09a0e34b
--- /dev/null
+++ b/.git-hooks/pre_commit/master_hooks_match.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+require 'fileutils'
+
+module Overcommit::Hook::PreCommit
+ # Ensures all master hooks have the same content.
+ #
+ # This is necessary because we can't use symlinks to link all the hooks in the
+ # template directory to the master `overcommit-hook` file, since symlinks are
+ # not supported on Windows.
+ class MasterHooksMatch < Base
+ def run
+ hooks_dir = File.join('template-dir', 'hooks')
+ master_hook = File.join(hooks_dir, 'overcommit-hook')
+ Dir.glob(File.join(hooks_dir, '*')).each do |hook_path|
+ unless FileUtils.compare_file(master_hook, hook_path)
+ return [
+ :fail,
+ "Template directory hook '#{hook_path}' does not match '#{master_hook}'!\n" \
+ "Run `cp #{master_hook} #{hook_path}`"
+ ]
+ end
+ end
+
+ :pass
+ end
+ end
+end
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 00000000..8cf8b4a0
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,30 @@
+name: Lint
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ overcommit:
+ timeout-minutes: 10
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Ruby
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: 3.3
+ bundler-cache: true
+
+ - name: Prepare environment
+ run: |
+ git config --global user.email "gh-actions@example.com"
+ git config --global user.name "GitHub Actions"
+ bundle exec overcommit --sign
+ bundle exec overcommit --sign pre-commit
+
+ - name: Run pre-commit checks
+ run: bundle exec overcommit --run
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 00000000..0e2aac20
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,61 @@
+name: Tests
+on:
+ push:
+ branches: [main]
+ pull_request:
+ branches: [main]
+
+jobs:
+ rspec:
+ timeout-minutes: 15
+ runs-on: ${{ matrix.os }}-latest
+
+ strategy:
+ fail-fast: false
+ matrix:
+ ruby-version:
+ - "2.6"
+ - "2.7"
+ - "3.0"
+ - "3.1"
+ - "3.2"
+ - "3.3"
+ os:
+ - ubuntu
+ # At the moment of this commit various specs fail on Windows.
+ # Any contributor is welcome to fix them and enable the Windows build.
+ # Please see Issue #836 for more details.
+ # - windows
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Set up Ruby ${{ matrix.ruby-version }}
+ uses: ruby/setup-ruby@v1
+ with:
+ ruby-version: ${{ matrix.ruby-version }}
+ bundler-cache: true
+
+ - name: Run tests
+ run: |
+ git config --global user.email "gh-actions@example.com"
+ git config --global user.name "GitHub Actions"
+ bundle exec rspec
+
+ - name: Code coverage reporting
+ uses: coverallsapp/github-action@v2
+ with:
+ github-token: ${{ secrets.github_token }}
+ flag-name: ruby${{ matrix.ruby-version }}-${{ matrix.os }}
+ parallel: true
+
+ finish:
+ needs: rspec
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Finalize code coverage report
+ uses: coverallsapp/github-action@v2
+ with:
+ github-token: ${{ secrets.github_token }}
+ parallel-finished: true
diff --git a/.gitignore b/.gitignore
index 410188f0..7ea8f1f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,3 +2,6 @@ Gemfile.lock
coverage/
pkg/
.bundle
+.idea
+.history/
+.vscode/
diff --git a/.overcommit.yml b/.overcommit.yml
index e4b4190e..adb9505d 100644
--- a/.overcommit.yml
+++ b/.overcommit.yml
@@ -1,4 +1,10 @@
+gemfile: Gemfile
+
PreCommit:
+ # Disabled since this causes spurious failures on AppVeyor builds
+ BrokenSymlinks:
+ enabled: false
+
BundleCheck:
enabled: true
@@ -12,20 +18,18 @@ PreCommit:
HardTabs:
enabled: true
+ MasterHooksMatch:
+ enabled: true
+ quiet: true
+
RuboCop:
enabled: true
- command: ['bundle', 'exec', 'rubocop']
include:
- '**/*.gemspec'
- '**/*.rb'
- '**/Gemfile'
- template-dir/hooks/overcommit-hook
- TravisLint:
- enabled: true
- command: ['bundle', 'exec', 'travis']
- flags: ['lint', '--skip-version-check']
-
TrailingWhitespace:
enabled: true
diff --git a/.projections.json b/.projections.json
deleted file mode 100644
index 24f5ce34..00000000
--- a/.projections.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
- "lib/overcommit/*.rb": {
- "alternate": "spec/overcommit/{}_spec.rb",
- "type": "source"
- },
- "spec/overcommit/*_spec.rb": {
- "alternate": "lib/overcommit/{}.rb",
- "type": "test"
- }
-}
diff --git a/.rubocop.yml b/.rubocop.yml
index fdb6d02d..1b4e7ad7 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,51 +1,90 @@
-Lint/AmbiguousRegexpLiteral:
+inherit_from: .rubocop_todo.yml
+
+AllCops:
+ TargetRubyVersion: 2.6
+ NewCops: disable
+ SuggestExtensions: false
+
+Layout/ClosingParenthesisIndentation:
Enabled: false
-Lint/AssignmentInCondition:
+Layout/DotPosition:
+ EnforcedStyle: trailing
+
+# Fails on AppVeyor builds
+Layout/EndOfLine:
Enabled: false
-Lint/Void:
+Layout/FirstParameterIndentation:
Enabled: false
-Metrics/AbcSize:
+Layout/FirstArrayElementIndentation:
Enabled: false
-Metrics/LineLength:
+Layout/HeredocIndentation:
+ Enabled: false
+
+Layout/LineLength:
Max: 100
-Metrics/MethodLength:
- Max: 20
+Layout/MultilineMethodCallIndentation:
+ Enabled: false
-Metrics/ModuleLength:
+Layout/MultilineOperationIndentation:
Enabled: false
-# Enforcing this results in a lot of unnecessary indentation.
-Style/ClassAndModuleChildren:
+Layout/SpaceBeforeFirstArg:
+ Exclude:
+ - '*.gemspec'
+
+Lint/AmbiguousBlockAssociation:
Enabled: false
-Style/ClosingParenthesisIndentation:
+Lint/AmbiguousRegexpLiteral:
Enabled: false
-Style/Documentation:
- Exclude:
- - 'spec/overcommit/**/*'
+Lint/AssignmentInCondition:
+ Enabled: false
-Style/DotPosition:
- EnforcedStyle: trailing
+Lint/Void:
+ Enabled: false
+
+Metrics/AbcSize:
+ Enabled: false
-Style/Encoding:
- EnforcedStyle: when_needed
+Metrics/BlockLength:
+ Enabled: false
-Style/FileName:
+Metrics/MethodLength:
+ Max: 20
+
+Metrics/ModuleLength:
+ Enabled: false
+
+Naming/FileName:
Exclude:
- 'template-dir/hooks/*'
- 'Gemfile'
- 'Rakefile'
- '*.gemspec'
-Style/FirstParameterIndentation:
+# Renaming `has_something?` to `something?` obfuscates whether it is a "is-a" or
+# a "has-a" relationship.
+Naming/PredicateName:
+ Enabled: false
+
+# commit_sha1 is indeed how we want to write such a variable, so ignore this cop
+Naming/VariableNumber:
Enabled: false
+# Enforcing this results in a lot of unnecessary indentation.
+Style/ClassAndModuleChildren:
+ Enabled: false
+
+Style/Documentation:
+ Exclude:
+ - 'spec/overcommit/**/*'
+
Style/FormatString:
Enabled: false
@@ -54,9 +93,6 @@ Style/FormatString:
Style/GuardClause:
Enabled: false
-Style/IndentArray:
- Enabled: false
-
Style/IfUnlessModifier:
Enabled: false
@@ -66,13 +102,11 @@ Style/IfUnlessModifier:
Style/Lambda:
Enabled: false
-Style/MultilineMethodCallIndentation:
- Enabled: false
-
-Style/MultilineOperationIndentation:
+Style/Next:
Enabled: false
-Style/Next:
+# Calling .zero? instead of comparing `== 0` seems unnecessarily verbose
+Style/NumericPredicate:
Enabled: false
Style/ParallelAssignment:
@@ -91,10 +125,9 @@ Style/PercentLiteralDelimiters:
'%W': '[]'
'%x': '{}'
-# Renaming `has_something?` to `something?` obfuscates whether it is a "is-a" or
-# a "has-a" relationship.
-Style/PredicateName:
- Enabled: false
+Style/RescueModifier:
+ Exclude:
+ - 'bin/overcommit'
Style/SignalException:
Enabled: false
@@ -104,15 +137,17 @@ Style/SignalException:
Style/SingleLineBlockParams:
Enabled: false
-Style/SpaceBeforeFirstArg:
- Exclude:
- - '*.gemspec'
-
Style/SpecialGlobalVars:
Enabled: false
+Style/SymbolArray:
+ Enabled: false
+
Style/TrailingCommaInArguments:
Enabled: false
-Style/TrailingCommaInLiteral:
+Style/TrailingCommaInArrayLiteral:
+ Enabled: false
+
+Style/TrailingCommaInHashLiteral:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
new file mode 100644
index 00000000..53df9159
--- /dev/null
+++ b/.rubocop_todo.yml
@@ -0,0 +1,137 @@
+# This configuration was generated by
+# `rubocop --auto-gen-config --auto-gen-only-exclude --no-exclude-limit`
+# on 2024-01-10 14:09:00 UTC using RuboCop version 1.59.0.
+# The point is for the user to remove these configuration records
+# one by one as the offenses are removed from the code base.
+# Note that changes in the inspected code, or installation of new
+# versions of RuboCop, may require this file to be generated again.
+
+# Offense count: 1
+# This cop supports safe autocorrection (--autocorrect).
+# Configuration parameters: AllowAliasSyntax, AllowedMethods.
+# AllowedMethods: alias_method, public, protected, private
+Layout/EmptyLinesAroundAttributeAccessor:
+ Exclude:
+ - 'lib/overcommit/hook_context/post_merge.rb'
+
+# Offense count: 6
+# Configuration parameters: AllowedMethods.
+# AllowedMethods: enums
+Lint/ConstantDefinitionInBlock:
+ Exclude:
+ - 'spec/overcommit/message_processor_spec.rb'
+
+# Offense count: 4
+Lint/MixedRegexpCaptureTypes:
+ Exclude:
+ - 'lib/overcommit/hook/pre_commit/dart_analyzer.rb'
+ - 'lib/overcommit/hook/pre_commit/java_checkstyle.rb'
+ - 'lib/overcommit/hook/pre_commit/kt_lint.rb'
+ - 'lib/overcommit/hook/pre_commit/scalastyle.rb'
+
+# Offense count: 2
+# This cop supports safe autocorrection (--autocorrect).
+Lint/RedundantCopDisableDirective:
+ Exclude:
+ - 'lib/overcommit/hook_runner.rb'
+ - 'lib/overcommit/printer.rb'
+
+# Offense count: 1
+# Configuration parameters: CountComments, Max, CountAsOne.
+Metrics/ClassLength:
+ Exclude:
+ - 'lib/overcommit/utils.rb'
+
+# Offense count: 2
+# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
+Metrics/CyclomaticComplexity:
+ Exclude:
+ - 'lib/overcommit/configuration.rb'
+ - 'lib/overcommit/hook_runner.rb'
+
+# Offense count: 3
+# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
+Metrics/PerceivedComplexity:
+ Exclude:
+ - 'lib/overcommit/configuration.rb'
+ - 'lib/overcommit/configuration_validator.rb'
+ - 'lib/overcommit/hook_runner.rb'
+
+# Offense count: 23
+# This cop supports unsafe autocorrection (--autocorrect-all).
+Style/GlobalStdStream:
+ Exclude:
+ - 'bin/overcommit'
+ - 'lib/overcommit/hook/post_commit/git_guilt.rb'
+ - 'template-dir/hooks/commit-msg'
+ - 'template-dir/hooks/overcommit-hook'
+ - 'template-dir/hooks/post-checkout'
+ - 'template-dir/hooks/post-commit'
+ - 'template-dir/hooks/post-merge'
+ - 'template-dir/hooks/post-rewrite'
+ - 'template-dir/hooks/pre-commit'
+ - 'template-dir/hooks/pre-push'
+ - 'template-dir/hooks/pre-rebase'
+ - 'template-dir/hooks/prepare-commit-msg'
+
+# Offense count: 2
+# This cop supports unsafe autocorrection (--autocorrect-all).
+Style/HashTransformValues:
+ Exclude:
+ - 'lib/overcommit/configuration.rb'
+ - 'lib/overcommit/configuration_validator.rb'
+
+# Offense count: 1
+# Configuration parameters: AllowedMethods.
+# AllowedMethods: respond_to_missing?
+Style/OptionalBooleanParameter:
+ Exclude:
+ - 'lib/overcommit/logger.rb'
+
+# Offense count: 2
+# This cop supports safe autocorrection (--autocorrect).
+Style/RedundantBegin:
+ Exclude:
+ - 'lib/overcommit/hook/prepare_commit_msg/replace_branch.rb'
+ - 'lib/overcommit/utils.rb'
+
+# Offense count: 10
+# This cop supports unsafe autocorrection (--autocorrect-all).
+# Configuration parameters: SafeForConstants.
+Style/RedundantFetchBlock:
+ Exclude:
+ - 'lib/overcommit/configuration.rb'
+ - 'lib/overcommit/configuration_validator.rb'
+ - 'lib/overcommit/hook/base.rb'
+ - 'lib/overcommit/hook/pre_commit/chamber_verification.rb'
+ - 'lib/overcommit/logger.rb'
+ - 'spec/support/shell_helpers.rb'
+
+# Offense count: 8
+# This cop supports safe autocorrection (--autocorrect).
+Style/RedundantRegexpEscape:
+ Exclude:
+ - 'lib/overcommit/configuration.rb'
+ - 'lib/overcommit/hook/pre_commit/php_cs.rb'
+ - 'lib/overcommit/hook/pre_commit/php_lint.rb'
+ - 'lib/overcommit/hook/pre_commit/php_stan.rb'
+
+# Offense count: 15
+# This cop supports unsafe autocorrection (--autocorrect-all).
+# Configuration parameters: Mode.
+Style/StringConcatenation:
+ Exclude:
+ - 'lib/overcommit/hook/pre_commit/bundle_check.rb'
+ - 'lib/overcommit/hook_runner.rb'
+ - 'lib/overcommit/message_processor.rb'
+ - 'spec/integration/gemfile_option_spec.rb'
+ - 'spec/overcommit/hook/commit_msg/text_width_spec.rb'
+ - 'spec/overcommit/hook/prepare_commit_msg/base_spec.rb'
+ - 'spec/overcommit/message_processor_spec.rb'
+ - 'spec/spec_helper.rb'
+
+# Offense count: 1
+# This cop supports unsafe autocorrection (--autocorrect-all).
+Style/ZeroLengthPredicate:
+ Exclude:
+ - 'lib/overcommit/hook/pre_commit/ruby_syntax.rb'
diff --git a/.simplecov b/.simplecov
deleted file mode 100644
index 3cc40052..00000000
--- a/.simplecov
+++ /dev/null
@@ -1,6 +0,0 @@
-SimpleCov.start do
- add_filter 'bin/'
- add_filter 'libexec/'
- add_filter 'spec/'
- add_filter 'template-dir/'
-end
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 2312a0d5..00000000
--- a/.travis.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-language: ruby
-
-sudo: false
-
-cache: bundler
-
-rvm:
- - 1.9.3
- - 2.0
- - 2.1
- - 2.2
- - 2.3.0
- - jruby-19mode
- - rbx-2
-
-matrix:
- allow_failures:
- - rvm: rbx-2
-
-before_script:
- - git config --global user.email "travis@travis.ci"
- - git config --global user.name "Travis CI"
-
-script:
- - bundle exec rspec
- - bundle exec overcommit --sign
- - bundle exec overcommit --run
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fbbebc34..ad1cb477 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,461 @@
# Overcommit Changelog
-## master (unreleased)
+## 0.67.1
+
+* Fix `set` gem dependency error when running with `--diff` flag
+
+## 0.67.0
+
+* Fix bug introduced in 0.65.0 that prevented `gemfile: false` from working correctly
+
+## 0.66.0
+
+* Add `--diff` CLI option for running pre-commit hooks against only changed files
+
+## 0.65.0
+
+* Load bundled gems on expected version
+
+## 0.64.1
+
+* Update minimum version of rexml to address [CVE-2024-49761](https://www.ruby-lang.org/en/news/2024/10/28/redos-rexml-cve-2024-49761/)
+
+## 0.64.0
+
+* Add support for `stylelint` 16+
+* Add `changelog_uri` to gemspec
+
+## 0.63.0
+
+* Add `Sorbet` pre-commit hook
+* Add `RSpec` pre-commit hook
+
+## 0.62.0
+
+* Allow version 5 of `childprocess` gem dependency
+
+## 0.61.0
+
+* Allow `ReplaceBranch` to use `skip_if`
+* Fix local Overcommit file merges with existing `.overcommit.yml`
+
+## 0.60.0
+
+* Allow overriding `Gemfile.lock` location for `BundleCheck` pre-commit hook
+* Fix `ReplaceBranch` prepare-commit-msg hook to allow trailing spaces
+* Add `MixFormat` pre-commit hook
+* Add `MixTest` pre-push hook
+* Allow loading custom local configuration from `.local-overcommit.yml`
+* Handle `Psych::DisallowedClass` when running `YamlSyntax` pre-commit hook
+* Add support for specifying custom `encoding` in `RailsSchemaUpToDate` pre-commit hook
+
+## 0.59.1
+
+* Remove `--disable-pending-cops` as default flag to `RuboCop` pre-commit hook.
+* Remove special handling of process output on Windows since it broke on Linux.
+
+## 0.59.0
+
+* Add `--disable-pending-cops` as default flag to `RuboCop` pre-commit hook to ignore non-existent cops. Requires RuboCop `0.82.0` or newer.
+* Fix deprecation warning for `Bundler.with_clean_env`.
+* Fix handling of some kinds of pronto errors in the `Pronto` hook.
+* Fix encoding of process output on Windows.
+* Add support for specifying hook type to `--run` flag.
+* Fix message regex parser for Stylelint.
+* Fix configuration loading on Ruby 3.1.
+* Fix `YamlSyntax` to support aliases when parsing.
+* Fix run output to explicitly flush partial logs.
+
+## 0.58.0
+
+* Add `rexml` dependency explicitly to support Ruby 3.0.
+* Add `DartAnalyzer` pre-commit hook to analyze Dart files.
+* Add `PubTest` and `FlutterTest` pre-push hooks to run `pub test` and `flutter test` for Dart projects, respectively.
+* Update `index-tags` script to support scanning only files tracked by Git.
+* Fix `EsLint` pre-commit hook to not report certain false positives.
+* Update `YamlLint` to `fail` the run instead of `warn` when errors are detected.
+* Update `YamlLint` parse the line number of output so it is line aware.
+* Gracefully handle breaking behavior in upstream Psych gem to support YAML aliases.
+* Fix case where `git` would delete all tracked files when popping stash.
+
+## 0.57.0
+
+* Fix `CommitMsg` hooks to be able to call `modified_lines_in_file`.
+* Add `ErbLint` pre-commit hook to lint ERB files.
+
+## 0.56.0
+
+* Update `ReplaceBranch` prepare-commit-msg hook to avoid running on `--amend` by default.
+* Add support for `modified_files` and `modified_lines_in_file` in `CommitMsg` hooks.
+
+## 0.55.0
+
+* Fix `GoFmt` to not be enabled by default. This was enabled by mistake when introduced in Overcommit `0.52.0`.
+
+## 0.54.1
+
+* Fix `Overcommit::GitRepo.list_files` helper to work with arbitrarily large lists of files.
+* Fix `AuthorName` to allow mononyms to be more inclusive of names.
+
+## 0.54.0
+
+* Fix `YamlLint` pre-commit hook
+* Relax `childprocess` gem version constraint to allow version 4.x
+
+## 0.53.0
+
+* Improve performance in `PhpCs` pre-commit hook
+* Add `Pronto` pre-push hook
+* Remove erroneous extra newline in replacement string for `ReplaceBranch` prepare-commit-msg hook
+* Add note about potentially checking your stash when hook is interrupted
+* Add support for skipping hooks based on command result using the `skip_if` option
+
+## 0.52.1
+
+* Fix case where no standard input is provided to `pre-push` hooks
+
+## 0.52.0
+
+* Fix `Mdl` to properly parse JSON output from `mdl`
+* Add `GolangciLint` pre-commit and pre-push hooks
+* Add `GoTest` pre-push hook
+* Add `GoFmt` pre-commit hook
+* Add `exclude_branches` hook option to disable hooks running on specific branches
+* Add `exclude_remotes` pre-push hook option to disable pre-push hooks running against specific remotes
+* Change default behavior of pre-push hooks to **not** run against deleted remote refs
+* Add `include_remote_ref_deletions` pre-push hook option to allow running for a remote branch deletion
+* Rename `remote_branch_deletion?` pre-push hook helper to `remote_ref_deletion?`
+* Add per-branch `destructive_only` setting to `ProtectedBranches` pre-push hook
+
+## 0.51.0
+
+* Stop stashing in pre-commit hooks when all changes are already staged,
+ avoiding unnecessary file modification
+* Improve instructions for recovering commit message when a `commit-msg` hook
+ fails
+
+## 0.50.0
+
+* Fix Overcommit to display helpful error message when a hook does not inherit
+ from the base class
+* Relax `childprocess` gem constraint to allow up to version 3.x
+* Display a helpful message if hooks do not inherit from the correct base class
+* Fix Overcommit to work with emacs/magit by [disabling literal pathspecs](https://magit.vc/manual/magit/My-Git-hooks-work-on-the-command_002dline-but-not-inside-Magit.html)
+
+## 0.49.1
+
+* Fix Overcommit to run when executed with no parent process
+* Fix `Stylelint` pre-commit hook `required_executable`
+
+## 0.49.0
+
+### New Features
+
+* Add `skipped_commit_types` option to `ReplaceBranch` prepare-commit-msg hook
+* Add `RubySyntax` pre-commit hook
+* Add `CodeSpellCheck` pre-commit hook
+
+### Changes
+
+* Relax `childprocess` dependency to allow version 1.x
+
+### Bug Fixes
+* Fix deadlock which was more likely to occur when setting `parallelize` on a hook to `false`
+* Fix `Mdl` hook to use JSON output and not fail on unexpected output
+
+## 0.48.1
+
+* Fix `Stylelint` hook regex to extract line numbers with more than one digit
+* Fix `CaseConflicts` hook to work with file paths containing double quotes
+
+## 0.48.0
+
+* Drop support for Ruby 2.3 or older
+* Support multi-line matches in `MessageFormat` `commit-msg` hook
+* Add `FileSize` pre-commit hook
+
+## 0.47.0
+
+### New Features
+
+* Add support for `prepare-commit-message` hooks
+* Add [`SwiftLint`](https://github.com/realm/SwiftLint) pre-commit hook
+* Add [`KtLint`](https://github.com/shyiko/ktlint) pre-commit hook
+* Add `TerraformFormat` pre-commit hook
+* Add [`CookStyle`](https://docs.chef.io/cookstyle.html) pre-commit hook
+
+### Changes
+
+* Update `validator_uri` for `W3cHtml` pre-commit hook
+* Update `TsLint` pre-commit hook to support new output format
+* Update `BundleCheck` error message with additional instructions
+
+### Bug Fixes
+
+* Add `--force-exclusion` flag to `Reek` pre-commit hook configuration to
+ ensure excluded files are excluded
+
+## 0.46.0
+
+* Fix `Credo` pre-commit hook to lint applicable files only rather than
+ all files
+* Add `PhpCsFixer` pre-commit hook
+* Add `YardCoverage` pre-commit hook
+* Add `Flay` pre-commit hook
+* Add `Stylelint` pre-commit hook
+* Fix `TsLint` default flags to work with `tslint` 5.11+
+
+## 0.45.0
+
+### New Features
+
+* Add `CargoTest` pre-push hook for running `cargo test`
+* Add `min_subject_width` option to `TextWidth` `commit-msg` hook
+
+### Changes
+
+* Drop support for Ruby versions 2.1 and older
+
+### Bug Fixes
+
+* Fix detection of `.git` directory location on Git versions before 2.5
+
+## 0.44.0
+
+### New Features
+
+* Add support for [worktrees](https://git-scm.com/docs/git-worktree)
+
+### Bug Fixes
+
+* Fix installer to not attempt to remove old hooks directory if non-empty
+* Fix erroneous `fatal` error message from a pre-commit hook run when adding
+ the first submodule to a repo
+
+## 0.43.0
+
+### Changes
+
+* Add [`GitLfs`](https://git-lfs.github.com/) `post-checkout`, `post-commit`
+ and `post-merge` hooks
+* Display commit message when `commit-msg` hooks fail
+* Drop support for JRuby
+* Enhance `pre-push` hooks to expose `modified_lines_in_file`, similar to
+ `pre-commit` hooks
+* Add `YarnCheck` pre-commit hook which checks if `yarn.lock` matches `package.json`
+* Add [`PhpUnit`](https://phpunit.de/) `pre-push` hook
+
+## 0.42.0
+
+### New Features
+
+* Add `YarnInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks
+* Add [`metadata-json-lint`](https://voxpupuli.org/blog/2014/11/06/linting-metadata-json/) pre-commit hook
+* Add [`RstLint`](https://github.com/twolfson/restructuredtext-lint) pre-commit
+ hook
+* Add `YarnInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks
+* Add additional file patterns for `ChamberSecurity` pre-commit hook
+* Add `ChamberCompare` and `ChamberVerification` pre-commit hooks
+* Add `ComposerInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks
+* Add ability to `pre-push` hooks to inspect modified files for pushed refs
+* Add [`PhpStan`](https://github.com/phpstan/phpstan) pre-commit hook
+
+### Changes
+
+* Run `GoLint` pre-commit hook against each file individually
+* Improve performance of `BundleAudit` checking of `Gemfile.lock` file
+* Allow ad hoc hooks to run executables not tracked by Git
+* Drop support for Ruby 2.0
+
+### Bug Fixes
+
+* Fix `LineEndings` pre-commit hook handling of file paths with spaces
+* Fix `Mdl` pre-commit hook message parsing regex
+* Fix `RailsBestPractices` hook to only run against changed files
+* Fix Overcommit installation in submodules
+* Don't print backtrace of signature change for `overcommit --run`
+
+## 0.41.0
+
+* Add [`PhpCs`](http://pear.php.net/package/PHP_CodeSniffer) pre-commit hook
+* Add [`PhpLint`](http://php.net/manual/en/features.commandline.options.php)
+ pre-commit hook
+* Allow toggling colorize output via `OVERCOMMIT_COLOR` environment variable
+
+## 0.40.0
+
+* Add [`Pronto`](https://github.com/mmozuras/pronto) pre-commit hook
+* Add [`hadolint`](https://github.com/lukasmartinelli/hadolint) pre-commit hook
+* Add [`license_finder`](https://github.com/pivotal/LicenseFinder) pre-commit hook
+* Use the `core.hooksPath` Git configuration option when installing hooks
+* Gracefully handle binary files in `LineEndings` pre-commit hook
+* Relax `childprocess` dependency to allow 0.x
+* Gracefully handle gem loading errors when invoking Overcommit in a repo where
+ the `gemfile` specified by the local `.overcommit.yml` references a gem
+ version incompatible with the already-loaded Overcommit
+* Ignore `Makefile` and `*.go` files in `HardTabs` pre-commit hook by default
+
+## 0.39.1
+
+### Bug Fixes
+
+* Update `childprocess` to 0.6.3
+
+## 0.39.0
+
+### New Features
+
+* Add [`GitLfs`](https://git-lfs.github.com/) pre-push hook
+
+### Changes
+
+* Update `childprocess` dependency to 0.6.x series
+* Auto-sign configuration file when installing hooks for the first time
+
+### Bug Fixes
+
+* Fix `forwarding to private method` warning on Ruby 2.4.x
+* Fix potential hang when a hook's `parallelize` option was set to `false`
+* Fix `empty strings as pathspecs` warning introduced in Git 2.11
+
+## 0.38.0
+
+### New Features
+
+* Add `Pytest` pre-push hook
+* Add `RakeTarget` pre-commit and pre-push hook
+* Moved `CommitPlease` from `CommitMsg` to `PostCommit` hook
+* Add `skip_file_checkout` hook setting for `PostCheckout` hooks
+
+### Bug Fixes
+
+* Fix `install_command` for scss_lint gem
+
+## 0.37.0
+
+### New Features
+
+* Add `FixMe` pre-commit hook, to ensure that no "token" words slips through.
+ These strings are things you should fix now, not later
+* Add [`YAMLLint`](https://github.com/adrienverge/yamllint) pre-commit hook
+* Add `LicenseHeader` pre-commit enforcement to ensure open source projects
+ contain proper license comments
+* Add [`Foodcritic`](http://www.foodcritic.io/) pre-commit hook
+* Add `LineEndings` pre-commit hook that allows you to enforcing UNIX- or
+ Windows-style line endings
+
+### Bug Fixes
+
+* Fix `CapitalizedSubject` to not fail when commit message starts with one or
+ more empty lines
+
+## 0.36.0
+
+* Add [`Fasterer`](https://github.com/DamirSvrtan/fasterer) pre-commit hook
+* Add [`Brakeman`](http://brakemanscanner.org/) pre-push hook
+* Add [`TSLint`](http://palantir.github.io/tslint/) pre-commit hook
+* Validate that hook `env` environment configurations have valid names/values
+* Fix a false negative reported by RailsSchemaUpToDate for newly-created Rails
+ projects that don't yet have any migrations
+
+## 0.35.0
+
+* Drop support for Ruby 1.9.3
+* Fix `JavaCheckstyle` pre-commit hook to properly categorize `INFO` and
+ `WARN` messages
+* Add `TestUnit` pre-push hook to run tests with `Test::Unit`
+* Add `BundleAudit` pre-commit hook to scan gems for vulnerabilities with
+ [`bundle-audit`](https://github.com/rubysec/bundler-audit)
+* Copy hook files instead of symlinking
+* Add `Credo` pre-commit hook to check Elixir files
+* Remove `Brakeman` pre-commit hook as it could erroneously report clean
+ runs depending on which files were committed to your repository. You
+ should run this tool in a separate job/task in your CI runs as it doesn't
+ make for a good pre-commit hook.
+* Add `Commitplease` pre-commit hook which checks commit messages with
+ [`commitplease`](https://www.npmjs.com/package/commitplease)
+
+## 0.34.2
+
+* Add `--no-color` flag to all `git diff`/`git show` calls to override local
+ configuration
+* Ignore `commit.gpgsign` configuration option when creating stash commits
+ in pre-commit hooks
+
+## 0.34.1
+
+* Switch template directory hooks from symlinks to regular files so gem can
+ be installed on Windows
+
+## 0.34.0
+
+* Fix `Scalastyle` pre-commit hook to capture messages with no line number
+* Fix `CoffeeLint` pre-commit hook detection of modified lines
+* Fix `Jscs` pre-commit hook to work with `jscs` 3.0.0+
+* Fix `CapitalizedSubject` pre-commit hook to ignore commit message subjects
+ starting with `fixup!` or `squash!` special prefixes
+* Add `BundleOutdated` pre-commit hook to report gems in the `Gemfile.lock`
+ that have newer versions available
+* Add `destructive_only` option to `ProtectedBranches` pre-push hook
+* Include `.ru` files in `RuboCop` pre-commit hook
+* Fix `TextWidth` to ignore special `fixup!`/`squash!` prefixes in commit
+ message subjects when determining width of line
+
+## 0.33.0
+
+### New Features
+
+* Add global `quiet` option which silences all hook output except in the case
+ of warning or error
+
+### Changes
+
+* Official support for Rubinius has been dropped. It will probably still work
+ for most use cases, but parallelized hook runs may be problematic. If someone
+ from the community is willing to step up to support it, we'll gladly add it
+ back
+* Change `overcommit` CLI to automatically run within a Bundler context if the
+ `gemfile` option is specified. This mainly saves you from needing
+ `bundle exec` when running `overcommit --run`
+
+### Bug Fixes
+
+* Fix `AuthorName`/`AuthorEmail` pre-commit hooks to respect
+ `GIT_AUTHOR_NAME`/`GIT_AUTHOR_EMAIL` environment variables, respectively
+* Fix `JavaCheckstyle` pre-commit hook to ignore `[ERROR]` prefix when parsing
+ output messages
+
+## 0.32.0
+
+### New Features
+
+* Hooks are now run in parallel by default
+* Add `concurrency` global option allowing you to specify the number of threads
+ to use when running hooks concurrently
+* Add `parallelize` hook option which specifies whether or not this hook should
+ be run in parallel (default is `true`)
+* Add `processors` hook option allowing you to specify how many processing
+ units a hook should require
+* Add `ForbiddenBranches` pre-commit hook which prevents creating a commit
+ on any blacklisted branch by name/pattern
+* Add `MessageFormat` commit-msg hook to validate commit messages against
+ a regex pattern
+
+### Changes
+
+* Improve error message output when there is a problem processing messages
+ via `extract_messages` pre-commit hook helper
+* Switch `ScssLint` pre-commit hook to use the JSON output formatter instead
+ of the default formatter
+* Change tense of hook descriptions from progressive indicative form ("Running")
+ to indicative present form ("Run") so output reads better in parallel hook
+ runs
+
+### Bug Fixes
* Fix bug where amending a commit with command line arguments containing
Unicode characters could cause a crash due to invalid byte sequences
+* Fix `Minitest` pre-push hook to include all test files
## 0.32.0.rc1
@@ -220,7 +672,7 @@
* Disable almost all hooks by default. You will now need to explicitly enable
almost all hooks yourself in your `.overcommit.yml`. If you are migrating from
`overcommit` 0.23.0 and want to use the default configuration that shipped
- with that version, copy the [default configuration from 0.23.0](https://github.com/brigade/overcommit/blob/9f03e9c82b385d375a836ca7146b117dbde5c822/config/default.yml)
+ with that version, copy the [default configuration from 0.23.0](https://github.com/sds/overcommit/blob/9f03e9c82b385d375a836ca7146b117dbde5c822/config/default.yml)
* Update `ScssLint` pre-commit hook to properly handle special exit code that
signals all files were filtered by exclusions (new as of `scss-lint` 0.36.0)
* Update `childprocess` dependency to minimum 0.5.6
@@ -578,7 +1030,7 @@
## 0.2.6
* Added check for linting HAML files with
- [haml-lint](https://github.com/brigade/haml-lint)
+ [haml-lint](https://github.com/sds/haml-lint)
## 0.2.5
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index edca7acb..102b4032 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -73,7 +73,7 @@ The reasoning for this perhaps odd naming scheme is to strike a balance between
consistency, familiarity for those who already know the tool, and Overcommit's
ability to deduce the name of a hook from its filename and vice versa.
-[1]: https://github.com/brigade/overcommit/issues
+[1]: https://github.com/sds/overcommit/issues
[2]: https://medium.com/brigade-engineering/the-secrets-to-great-commit-messages-106fc0a92a25
[3]: https://travis-ci.org/
@@ -82,4 +82,4 @@ ability to deduce the name of a hook from its filename and vice versa.
This project adheres to the [Open Code of Conduct][code-of-conduct]. By
participating, you are expected to honor this code.
-[code-of-conduct]: https://github.com/brigade/code-of-conduct
+[code-of-conduct]: https://github.com/civiccc/code-of-conduct
diff --git a/Gemfile b/Gemfile
index 95cca1a8..49dc7923 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,9 +1,21 @@
+# frozen_string_literal: true
+
source 'https://rubygems.org'
-# Generate coverage information in Travis builds
-gem 'coveralls'
+gemspec
-# Pin RuboCop for Travis builds.
-gem 'rubocop', '0.36.0'
+# Development dependencies are listed below
-gemspec
+gem 'rspec', '~> 3.0'
+
+gem 'simplecov', '~> 0.21.0'
+gem 'simplecov-lcov', '~> 0.8.0'
+
+# Pin RuboCop for CI builds
+if RUBY_VERSION < '2.7.0'
+ gem 'rubocop', '1.50.0'
+else
+ gem 'rubocop', '1.59.0'
+end
+
+gem 'ffi' if Gem.win_platform?
diff --git a/MIT-LICENSE b/MIT-LICENSE
index 5d9e2dcb..d5312cfb 100644
--- a/MIT-LICENSE
+++ b/MIT-LICENSE
@@ -1,5 +1,4 @@
-Copyright (c) 2013-2015 Brigade Engineering, Aiden Scandella, Shane da Silva
-https://www.brigade.com/
+Copyright (c) 2013-2019 Shane da Silva, Aiden Scandella
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
diff --git a/README.md b/README.md
index 398e2a64..a0d46726 100644
--- a/README.md
+++ b/README.md
@@ -1,68 +1,71 @@
[](https://badge.fury.io/rb/overcommit)
-[](https://travis-ci.org/brigade/overcommit)
-[](https://ci.appveyor.com/project/brigade/overcommit/branch/master)
-[](https://coveralls.io/r/brigade/overcommit)
-[](https://codeclimate.com/github/brigade/overcommit)
-[](https://gemnasium.com/brigade/overcommit)
-[](http://inch-ci.org/github/brigade/overcommit)
+[](https://github.com/sds/overcommit/actions/workflows/tests.yml/badge.svg?branch=main)
+[](https://coveralls.io/github/sds/overcommit?branch=main)
+[](https://codeclimate.com/github/sds/overcommit/maintainability)
+[](http://inch-ci.org/github/sds/overcommit)
-# Overcommit
+
+
+
`overcommit` is a tool to manage and configure
[Git hooks](http://git-scm.com/book/en/Customizing-Git-Git-Hooks).
-
-
In addition to supporting a wide variety of hooks that can be used across
-multiple repositories, you can also define hooks specific to a
-repository, but unlike regular Git hooks are stored in source control. You can
-also easily [add your existing hook scripts](#adding-existing-git-hooks) without
-writing any Ruby code.
-
-* [Requirements](#requirements)
- * [Dependencies](#dependencies)
-* [Installation](#installation)
- * [Automatically Install Overcommit Hooks](#automatically-install-overcommit-hooks)
-* [Usage](#usage)
-* [Continuous Integration](#continuous-integration)
-* [Configuration](#configuration)
- * [Hook Options](#hook-options)
- * [Hook Categories](#hook-categories)
- * [Gemfile](#gemfile)
- * [Plugin Directory](#plugin-directory)
- * [Signature Verification](#signature-verification)
-* [Built-In Hooks](#built-in-hooks)
- * [CommitMsg](#commitmsg)
- * [PostCheckout](#postcheckout)
- * [PostCommit](#postcommit)
- * [PostMerge](#postmerge)
- * [PostRewrite](#postrewrite)
- * [PreCommit](#precommit)
- * [PrePush](#prepush)
- * [PreRebase](#prerebase)
-* [Repo-Specific Hooks](#repo-specific-hooks)
- * [Adding Existing Git Hooks](#adding-existing-git-hooks)
-* [Security](#security)
-* [Contributing](#contributing)
-* [Community](#community)
-* [Changelog](#changelog)
-* [License](#license)
+multiple repositories, you can also define hooks specific to a repository which
+are stored in source control. You can also easily
+[add your existing hook scripts](#adding-existing-git-hooks) without writing
+any Ruby code.
+
+- [Requirements](#requirements)
+ - [Windows](#windows)
+ - [Dependencies](#dependencies)
+- [Installation](#installation)
+ - [Automatically Install Overcommit Hooks](#automatically-install-overcommit-hooks)
+- [Usage](#usage)
+ - [Skipping Hooks](#skipping-hooks)
+ - [Disabling Overcommit](#disabling-overcommit)
+ - [Disabling Colorized Output](#disabling-colorized-output)
+- [Continuous Integration](#continuous-integration)
+- [Configuration](#configuration)
+ - [Hook Options](#hook-options)
+ - [Hook Categories](#hook-categories)
+ - [The `ALL` Hook](#the-all-hook)
+ - [Gemfile](#gemfile)
+ - [Plugin Directory](#plugin-directory)
+ - [Quiet Hook Runs](#quiet-hook-runs)
+ - [Concurrency](#concurrency)
+ - [Signature Verification](#signature-verification)
+- [Built-In Hooks](#built-in-hooks)
+ - [CommitMsg](#commitmsg)
+ - [PostCheckout](#postcheckout)
+ - [PostCommit](#postcommit)
+ - [PostMerge](#postmerge)
+ - [PostRewrite](#postrewrite)
+ - [PreCommit](#precommit)
+ - [WARNING: pre-commit hooks cannot have side effects](#warning-pre-commit-hooks-cannot-have-side-effects)
+ - [PrePush](#prepush)
+ - [PreRebase](#prerebase)
+- [Repo-Specific hooks](#repo-specific-hooks)
+ - [Adding Existing Git Hooks](#adding-existing-git-hooks)
+- [Security](#security)
+ - [Disabling Signature Checking](#disabling-signature-checking)
+- [Contributing](#contributing)
+- [Community](#community)
+- [Changelog](#changelog)
+- [License](#license)
## Requirements
-This project aims to support the following Ruby runtimes:
-
-* MRI 1.9.3 & 2.x
-* JRuby 1.7.x
-* Rubinius 2.x
+This project aims to support the following Ruby runtimes on \*nix (and best effort on Windows):
-Windows is currently supported only for MRI Ruby 2.x
+* Ruby 2.6+
### Dependencies
-Some of the hooks have third-party dependencies. For example, to lint your
-[SCSS](http://sass-lang.com/) files, you're going to need our
-[scss_lint gem](https://github.com/brigade/scss-lint).
+Some hooks have third-party dependencies. For example, to lint your
+[SCSS](http://sass-lang.com/) files, you're going to need the
+[scss_lint gem](https://github.com/sds/scss-lint).
Depending on the hooks you enable/disable for your repository, you'll need to
ensure your development environment already has those dependencies installed.
@@ -74,13 +77,20 @@ available during your hook runs.
## Installation
-`overcommit` is installed via [RubyGems](https://rubygems.org/):
+`overcommit` is installed via [RubyGems](https://rubygems.org/). It is strongly
+recommended that your environment support running `gem install` without
+requiring root user privileges via `sudo` or otherwise. Using a Ruby version
+manager like [`rbenv`](https://github.com/rbenv/rbenv/) or
+[`rvm`](https://rvm.io/) is recommended.
+
+Once you have an environment that allows you to install gems without `sudo`,
+run:
```bash
gem install overcommit
```
-You can then run the `overcommit` command to install hooks into repositories:
+You can then run the `overcommit` command to install hooks into repositories.
```bash
mkdir important-project
@@ -89,9 +99,9 @@ git init
overcommit --install
```
-Any existing hooks for your repository which Overcommit would have replaced
-will be backed up. You can restore everything to the way it was by running
-`overcommit --uninstall`.
+After running `overcommit --install`, any existing hooks for your repository
+which Overcommit will replace will be backed up. You can restore everything to
+the way it was by running `overcommit --uninstall`.
### Automatically Install Overcommit Hooks
@@ -99,7 +109,7 @@ If you want to use `overcommit` for all repositories you create/clone going
forward, add the following to automatically run in your shell environment:
```bash
-export GIT_TEMPLATE_DIR=`overcommit --template-dir`
+export GIT_TEMPLATE_DIR="$(overcommit --template-dir)"
```
The `GIT_TEMPLATE_DIR` provides a directory for Git to use as a template
@@ -121,6 +131,7 @@ Command Line Flag | Description
`-f`/`--force` | Don't bail on install if other hooks already exist--overwrite them
`-l`/`--list-hooks` | Display all available hooks in the current repository
`-r`/`--run` | Run pre-commit hook against all tracked files in repository
+`--diff [` | Run pre-commit hook against all changed files relative to `][`
`-t`/`--template-dir` | Print location of template directory
`-h`/`--help` | Show command-line flag documentation
`-v`/`--version` | Show version
@@ -158,6 +169,16 @@ hooks to run, you can disable Overcommit entirely by setting the
OVERCOMMIT_DISABLE=1 ./my-custom-script
```
+### Disabling Colorized Output
+
+Overcommit automatically colorizes its output based on whether it is outputting
+to a TTY. However, you can manually enable/disable color by setting the
+`OVERCOMMIT_COLOR` environment variable.
+
+```bash
+OVERCOMMIT_COLOR=0 git commit
+```
+
## Continuous Integration
You can run the same set of hooks that would be executed in a pre-commit hook
@@ -191,6 +212,10 @@ PreCommit:
command: ['bundle', 'exec', 'rubocop'] # Invoke within Bundler context
```
+Additionally, you may wish to have repo-specific configurations that are local to your computer that are not part of the shared repo config.
+Adding a `.local-overcommit.yml` file in the top-level directory of the repository adds another configuration file. This file works the same as `.overcommit.yml`.
+Adding this to ignored files in a git repo will allow you to have a local configuration per repo.
+
### Hook Options
Individual hooks expose both built-in configuration options as well as their
@@ -206,17 +231,22 @@ Option | Description
`requires_files` | If `true`, this hook runs only if files that are applicable to it have been modified. See `include` and `exclude` for how to specify applicable files.
`include` | File paths or glob patterns of files that apply to this hook. The hook will only run on the applicable files when they have been modified. Note that the concept of modified varies for different types of hooks. By default, `include` matches every file until you specify a list of patterns.
`exclude` | File paths or glob patterns of files that do not apply to this hook. This is used to exclude any files that would have been matched by `include`.
+`exclude_branches` | List of branch names or glob patterns of branches that this hook should not run against.
+`exclude_remotes` | *`PrePush` hooks only.* List of remote names that the hook should not run against.
+`include_remote_ref_deletions` | *`PrePush` hooks only.* By default, `PrePush` hooks will **not** run for pushes that delete a remote ref (i.e. branches or tags). Set to `true` to have the hook run even for deleted remote ref.
`problem_on_unmodified_line` | How to treat errors reported on lines that weren't modified during the action captured by this hook (e.g. for pre-commit hooks, warnings/errors reported on lines that were not staged with `git add` may not be warnings/errors you care about). Valid values are `report`: report errors/warnings as-is regardless of line location (default); `warn`: report errors as warnings if they are on lines you didn't modify; and `ignore`: don't display errors/warnings at all if they are on lines you didn't modify (`ignore` is _not_ recommended).
`on_fail` | Change the status of a failed hook to `warn` or `pass`. This allows you to treat failures as warnings or potentially ignore them entirely, but you should use caution when doing so as you might be hiding important information.
-`on_warn` | Simliar to `on_fail`, change the status of a hook that returns a warning status to either `pass` (you wish to silence warnings entirely) or `fail` (you wish to treat all warnings as errors).
+`on_warn` | Similar to `on_fail`, change the status of a hook that returns a warning status to either `pass` (you wish to silence warnings entirely) or `fail` (you wish to treat all warnings as errors).
`required_executable` | Name of an executable that must exist in order for the hook to run. If this is a path (e.g. `./bin/ruby`), ensures that the executable file exists at the given location relative to the repository root. Otherwise, if it just the name of an executable (e.g. `ruby`) checks if the executable can be found in one of the directories in the `PATH` environment variable. Set this to a specific path if you want to always use an executable that is stored in your repository. (e.g. RubyGems bin stubs, Node.js binaries, etc.)
`required_library`/`required_libraries` | List of Ruby libraries to load with `Kernel.require` before the hook runs. This is specifically for hooks that integrate with external Ruby libraries.
`command` | Array of arguments to use as the command. How each hook uses this is different, but it allows hooks to change the context with which they run. For example, you can change the command to be `['bundle', 'exec', 'rubocop']` instead of just `rubocop` so that you can use the gem versions specified in your local `Gemfile.lock`. This defaults to the name of the `required_executable`.
`flags` | Array of arguments to append to the `command`. This is useful for customizing the behavior of a tool. It's also useful when a newer version of a tool removes/renames existing flags, so you can update the flags via your `.overcommit.yml` instead of waiting for an upstream fix in Overcommit.
-`env` | Hash of environment variables the hook should be run with. This is intended to be used as a last resort when an executable a hook runs is configured only via an environment variable. Any pre-existing environment variables with the same names as ones defined in `env` will have their original values restored after the hook runs. **WARNING**: If you set the same environment variable for multiple hooks and you've enabled parallel hook runs, since the environment is shared across all threads you could accidentally have these separate hooks trample on each other. In this case, you should disable parallelization for the hook using the `parallelize` option.
+`env` | Hash of environment variables the hook should be run with. This is intended to be used as a last resort when an executable a hook runs is configured only via an environment variable. Any pre-existing environment variables with the same names as ones defined in `env` will have their original values restored after the hook runs. **NOTE:** Currently, only strings are accepted values. Boolean values will raise an error. **WARNING**: If you set the same environment variable for multiple hooks and you've enabled parallel hook runs, since the environment is shared across all threads you could accidentally have these separate hooks trample on each other. In this case, you should disable parallelization for the hook using the `parallelize` option.
`parallelize` | Whether to allow this hook to be run concurrently with other hooks. Disable this if the hook requires access to a shared resource that other hooks may also access and modify (e.g. files, the git index, process environment variables, etc).
`processors` | The number of processing units to reserve for this hook. This does not reserve CPUs, but indicates that out of the total number of possible concurrent hooks allowed by the global `concurrency` option, this hook requires the specified number. Thus in the typical case where `concurrency` is set to the number of available cores (default), and you have a hook that executes an application which itself creates 2 threads (or is otherwise scheduled on 2 cores), you can indicate that Overcommit should allocate 2 `processors` to the hook. Ideally this means your hooks won't put undue load on your available cores.
`install_command` | Command the user can run to install the `required_executable` (or alternately the specified `required_libraries`). This is intended for documentation purposes, as Overcommit does not install software on your behalf since there are too many edge cases where such behavior would result in incorrectly configured installations (e.g. installing a Python package in the global package space instead of in a virtual environment).
+`skip_file_checkout` | Whether to skip this hook for file checkouts (e.g. `git checkout some-ref -- file`). Only applicable to `PostCheckout` hooks.
+`skip_if` | Array of arguments to be executed to determine whether or not the hook should run. For example, setting this to a value of `['bash', '-c', '! which my-executable']` would allow you to skip running this hook if `my-executable` was not in the bin path.
In addition to the built-in configuration options, each hook can expose its
own unique configuration options. The `AuthorEmail` hook, for example, allows
@@ -323,6 +353,13 @@ in your `Gemfile`.
You can change the directory that project-specific hooks are loaded from via
the `plugin_directory` option. The default directory is `.git-hooks`.
+### Quiet Hook Runs
+
+If you prefer to have your hooks be completely silent unless there is a
+problem, you can set the top-level `quiet` option to `true`. Note that if you
+have many hooks or slow hooks this may not be desirable, as you don't get
+visual feedback indicating the general progress of the hook run.
+
### Concurrency
Overcommit runs hooks in parallel by default, with a number of concurrent
@@ -368,6 +405,7 @@ follow [proper formatting guidelines](http://tbaggery.com/2008/04/19/a-note-abou
* [`*`EmptyMessage](lib/overcommit/hook/commit_msg/empty_message.rb)
* [GerritChangeId](lib/overcommit/hook/commit_msg/gerrit_change_id.rb)
* [HardTabs](lib/overcommit/hook/commit_msg/hard_tabs.rb)
+* [MessageFormat](lib/overcommit/hook/commit_msg/message_format.rb)
* [RussianNovel](lib/overcommit/hook/commit_msg/russian_novel.rb)
* [`*`SingleLineSubject](lib/overcommit/hook/commit_msg/single_line_subject.rb)
* [SpellCheck](lib/overcommit/hook/commit_msg/spell_check.rb)
@@ -381,9 +419,11 @@ any time your `HEAD` changes or a file is explicitly checked out.
* [BowerInstall](lib/overcommit/hook/post_checkout/bower_install.rb)
* [BundleInstall](lib/overcommit/hook/post_checkout/bundle_install.rb)
+* [ComposerInstall](lib/overcommit/hook/post_checkout/composer_install.rb)
* [IndexTags](lib/overcommit/hook/post_checkout/index_tags.rb)
* [NpmInstall](lib/overcommit/hook/post_checkout/npm_install.rb)
* [SubmoduleStatus](lib/overcommit/hook/post_checkout/submodule_status.rb)
+* [YarnInstall](lib/overcommit/hook/post_checkout/yarn_install.rb)
### PostCommit
@@ -393,10 +433,13 @@ however, it can be used to alert the user to some issue.
* [BowerInstall](lib/overcommit/hook/post_commit/bower_install.rb)
* [BundleInstall](lib/overcommit/hook/post_commit/bundle_install.rb)
+* [Commitplease](lib/overcommit/hook/post_commit/commitplease.rb)
+* [ComposerInstall](lib/overcommit/hook/post_commit/composer_install.rb)
* [GitGuilt](lib/overcommit/hook/post_commit/git_guilt.rb)
* [IndexTags](lib/overcommit/hook/post_commit/index_tags.rb)
* [NpmInstall](lib/overcommit/hook/post_commit/npm_install.rb)
* [SubmoduleStatus](lib/overcommit/hook/post_commit/submodule_status.rb)
+* [YarnInstall](lib/overcommit/hook/post_commit/yarn_install.rb)
### PostMerge
@@ -406,9 +449,11 @@ already occurred; however, it can be used to alert the user to some issue.
* [BowerInstall](lib/overcommit/hook/post_merge/bower_install.rb)
* [BundleInstall](lib/overcommit/hook/post_merge/bundle_install.rb)
+* [ComposerInstall](lib/overcommit/hook/post_merge/composer_install.rb)
* [IndexTags](lib/overcommit/hook/post_merge/index_tags.rb)
* [NpmInstall](lib/overcommit/hook/post_merge/npm_install.rb)
* [SubmoduleStatus](lib/overcommit/hook/post_merge/submodule_status.rb)
+* [YarnInstall](lib/overcommit/hook/post_merge/yarn_install.rb)
### PostRewrite
@@ -419,9 +464,11 @@ issue.
* [BowerInstall](lib/overcommit/hook/post_rewrite/bower_install.rb)
* [BundleInstall](lib/overcommit/hook/post_rewrite/bundle_install.rb)
+* [ComposerInstall](lib/overcommit/hook/post_rewrite/composer_install.rb)
* [IndexTags](lib/overcommit/hook/post_rewrite/index_tags.rb)
* [NpmInstall](lib/overcommit/hook/post_rewrite/npm_install.rb)
* [SubmoduleStatus](lib/overcommit/hook/post_rewrite/submodule_status.rb)
+* [YarnInstall](lib/overcommit/hook/post_rewrite/yarn_install.rb)
### PreCommit
@@ -443,23 +490,36 @@ instead of whatever contents are in your working tree (as you don't want
unstaged changes to taint your results). Overcommit takes care
of this for you, but to do it in a generalized way introduces this
limitation. See the [thread tracking this
-issue](https://github.com/brigade/overcommit/issues/238) for more details.
+issue](https://github.com/sds/overcommit/issues/238) for more details.
* [`*`AuthorEmail](lib/overcommit/hook/pre_commit/author_email.rb)
* [`*`AuthorName](lib/overcommit/hook/pre_commit/author_name.rb)
* [BerksfileCheck](lib/overcommit/hook/pre_commit/berksfile_check.rb)
-* [Brakeman](lib/overcommit/hook/pre_commit/brakeman.rb)
* [`*`BrokenSymlinks](lib/overcommit/hook/pre_commit/broken_symlinks.rb)
+* [BundleAudit](lib/overcommit/hook/pre_commit/bundle_audit.rb)
* [BundleCheck](lib/overcommit/hook/pre_commit/bundle_check.rb)
+* [BundleOutdated](lib/overcommit/hook/pre_commit/bundle_outdated.rb)
* [`*`CaseConflicts](lib/overcommit/hook/pre_commit/case_conflicts.rb)
* [ChamberSecurity](lib/overcommit/hook/pre_commit/chamber_security.rb)
+* [CodeSpellCheck](lib/overcommit/hook/pre_commit/code_spell_check.rb)
* [CoffeeLint](lib/overcommit/hook/pre_commit/coffee_lint.rb)
+* [Credo](lib/overcommit/hook/pre_commit/credo.rb)
* [CssLint](lib/overcommit/hook/pre_commit/css_lint.rb)
+* [DartAnalyzer](lib/overcommit/hook/pre_commit/dart_analyzer.rb)
* [Dogma](lib/overcommit/hook/pre_commit/dogma.rb)
+* [ErbLint](lib/overcommit/hook/pre_commit/erb_lint.rb)
* [EsLint](lib/overcommit/hook/pre_commit/es_lint.rb)
* [ExecutePermissions](lib/overcommit/hook/pre_commit/execute_permissions.rb)
+* [Fasterer](lib/overcommit/hook/pre_commit/fasterer.rb)
+* [FileSize](lib/overcommit/hook/pre_commit/file_size.rb)
+* [FixMe](lib/overcommit/hook/pre_commit/fix_me.rb)
+* [Flay](lib/overcommit/hook/pre_commit/flay.rb)
+* [Foodcritic](lib/overcommit/hook/pre_commit/foodcritic.rb)
+* [ForbiddenBranches](lib/overcommit/hook/pre_commit/forbidden_branches.rb)
* [GoLint](lib/overcommit/hook/pre_commit/go_lint.rb)
* [GoVet](lib/overcommit/hook/pre_commit/go_vet.rb)
+* [Hadolint](lib/overcommit/hook/pre_commit/hadolint.rb)
+* [LicenseFinder](lib/overcommit/hook/pre_commit/license_finder.rb)
* [HamlLint](lib/overcommit/hook/pre_commit/haml_lint.rb)
* [HardTabs](lib/overcommit/hook/pre_commit/hard_tabs.rb)
* [Hlint](lib/overcommit/hook/pre_commit/hlint.rb)
@@ -472,37 +532,55 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details.
* [JsLint](lib/overcommit/hook/pre_commit/js_lint.rb)
* [Jsl](lib/overcommit/hook/pre_commit/jsl.rb)
* [JsonSyntax](lib/overcommit/hook/pre_commit/json_syntax.rb)
+* [KtLint](lib/overcommit/hook/pre_commit/kt_lint.rb)
+* [LicenseHeader](lib/overcommit/hook/pre_commit/license_header.rb)
+* [LineEndings](lib/overcommit/hook/pre_commit/line_endings.rb)
* [LocalPathsInGemfile](lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb)
* [Mdl](lib/overcommit/hook/pre_commit/mdl.rb)
* [`*`MergeConflicts](lib/overcommit/hook/pre_commit/merge_conflicts.rb)
* [NginxTest](lib/overcommit/hook/pre_commit/nginx_test.rb)
-* [Pep257](lib/overcommit/hook/pre_commit/pep257.rb)
-* [Pep8](lib/overcommit/hook/pre_commit/pep8.rb)
+* [PhpCs](lib/overcommit/hook/pre_commit/php_cs.rb)
+* [PhpCsFixer](lib/overcommit/hook/pre_commit/php_cs_fixer.rb)
+* [PhpLint](lib/overcommit/hook/pre_commit/php_lint.rb)
+* [PhpStan](lib/overcommit/hook/pre_commit/php_stan.rb)
+* [Pronto](lib/overcommit/hook/pre_commit/pronto.rb)
* [PuppetLint](lib/overcommit/hook/pre_commit/puppet_lint.rb)
+* [PuppetMetadataJsonLint](lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb)
+* [Pycodestyle](lib/overcommit/hook/pre_commit/pycodestyle.rb)
+* [Pydocstyle](lib/overcommit/hook/pre_commit/pydocstyle.rb)
* [Pyflakes](lib/overcommit/hook/pre_commit/pyflakes.rb)
* [Pylint](lib/overcommit/hook/pre_commit/pylint.rb)
* [PythonFlake8](lib/overcommit/hook/pre_commit/python_flake8.rb)
+* [RakeTarget](lib/overcommit/hook/pre_commit/rake_target.rb)
* [RailsBestPractices](lib/overcommit/hook/pre_commit/rails_best_practices.rb)
* [RailsSchemaUpToDate](lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb)
* [Reek](lib/overcommit/hook/pre_commit/reek.rb)
* [RuboCop](lib/overcommit/hook/pre_commit/rubo_cop.rb)
* [RubyLint](lib/overcommit/hook/pre_commit/ruby_lint.rb)
+* [RubySyntax](lib/overcommit/hook/pre_commit/ruby_syntax.rb)
+* [SwiftLint](lib/overcommit/hook/pre_commit/swift_lint.rb)
* [Scalariform](lib/overcommit/hook/pre_commit/scalariform.rb)
* [Scalastyle](lib/overcommit/hook/pre_commit/scalastyle.rb)
* [ScssLint](lib/overcommit/hook/pre_commit/scss_lint.rb)
* [SemiStandard](lib/overcommit/hook/pre_commit/semi_standard.rb)
* [ShellCheck](lib/overcommit/hook/pre_commit/shell_check.rb)
* [SlimLint](lib/overcommit/hook/pre_commit/slim_lint.rb)
+* [Sorbet](lib/overcommit/hook/pre_commit/sorbet.rb)
* [Sqlint](lib/overcommit/hook/pre_commit/sqlint.rb)
* [Standard](lib/overcommit/hook/pre_commit/standard.rb)
+* [Stylelint](lib/overcommit/hook/pre_commit/stylelint.rb)
* [TrailingWhitespace](lib/overcommit/hook/pre_commit/trailing_whitespace.rb)
* [TravisLint](lib/overcommit/hook/pre_commit/travis_lint.rb)
+* [TsLint](lib/overcommit/hook/pre_commit/ts_lint.rb)
* [Vint](lib/overcommit/hook/pre_commit/vint.rb)
* [W3cCss](lib/overcommit/hook/pre_commit/w3c_css.rb)
* [W3cHtml](lib/overcommit/hook/pre_commit/w3c_html.rb)
* [XmlLint](lib/overcommit/hook/pre_commit/xml_lint.rb)
* [XmlSyntax](lib/overcommit/hook/pre_commit/xml_syntax.rb)
+* [YamlLint](lib/overcommit/hook/pre_commit/yaml_lint.rb)
* [YamlSyntax](lib/overcommit/hook/pre_commit/yaml_syntax.rb)
+* [YardCoverage](lib/overcommit/hook/pre_commit/yard_coverage.rb)
+* [YarnCheck](lib/overcommit/hook/pre_commit/yarn_check.rb)
### PrePush
@@ -510,9 +588,18 @@ issue](https://github.com/brigade/overcommit/issues/238) for more details.
but before any objects have been transferred. If a hook fails, the push is
aborted.
+* [Brakeman](lib/overcommit/hook/pre_push/brakeman.rb)
+* [FlutterTest](lib/overcommit/hook/pre_push/flutter_test.rb)
* [Minitest](lib/overcommit/hook/pre_push/minitest.rb)
+* [PhpUnit](lib/overcommit/hook/pre_push/php_unit.rb)
+* [Pronto](lib/overcommit/hook/pre_push/pronto.rb)
* [ProtectedBranches](lib/overcommit/hook/pre_push/protected_branches.rb)
+* [PubTest](lib/overcommit/hook/pre_push/pub_test.rb)
+* [Pytest](lib/overcommit/hook/pre_push/pytest.rb)
+* [PythonNose](lib/overcommit/hook/pre_push/python_nose.rb)
+* [RakeTarget](lib/overcommit/hook/pre_push/rake_target.rb)
* [RSpec](lib/overcommit/hook/pre_push/r_spec.rb)
+* [TestUnit](lib/overcommit/hook/pre_push/test_unit.rb)
### PreRebase
@@ -527,9 +614,9 @@ Out of the box, `overcommit` comes with a set of hooks that enforce a variety of
styles and lints. However, some hooks only make sense in the context of a
specific repository.
-At Brigade, for example, we have a number of simple checks that we run
-against our code to catch common errors. For example, since we use
-[RSpec](http://rspec.info/), we want to make sure all spec files contain the
+For example, you can have a number of simple checks that run
+against your code to catch common errors. For example, if you use
+[RSpec](http://rspec.info/), you can make sure all spec files contain the
line `require 'spec_helper'`.
Inside our repository, we can add the file
@@ -566,10 +653,6 @@ PreCommit:
include: '**/*_spec.rb'
```
-You can see a great example of writing custom Overcommit hooks from the
-following blog post: [How to Write a Custom Overcommit PreCommit
-Git Hook in 4 Steps](http://www.guoxiang.me/posts/28-how-to-write-a-custom-overcommit-precommit-git-hook-in-4-steps)
-
### Adding Existing Git Hooks
You might already have hook scripts written which you'd like to integrate with
@@ -590,6 +673,12 @@ executes the command and appends any arguments and standard input stream that
would have been passed to the regular hook. The hook passes or fails based
on the exit status of the command.
+The script is executed as if Git were calling the hook directly. If you want
+to understand which arguments are passed to the script depending on the type
+of hook, see the [git-hooks documentation][GHD].
+
+[GHD]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks
+
## Security
While Overcommit can make managing Git hooks easier and more convenient,
@@ -643,9 +732,7 @@ ensure your thoughts, ideas, or code get merged.
## Community
All major discussion surrounding Overcommit happens on the
-[GitHub issues list](https://github.com/brigade/overcommit/issues).
-
-You can also follow [@git_overcommit on Twitter](https://twitter.com/git_overcommit).
+[GitHub issues list](https://github.com/sds/overcommit/issues).
## Changelog
@@ -655,3 +742,9 @@ of `overcommit`, read the [Overcommit Changelog](CHANGELOG.md).
## License
This project is released under the [MIT license](MIT-LICENSE).
+
+The Overcommit logo is adapted from the [Git Logo by Jason Long][GL], and
+is licensed under the [Creative Commons Attribution 3.0 Unported License][CC3].
+
+[GL]: https://git-scm.com/downloads/logos
+[CC3]: http://creativecommons.org/licenses/by/3.0/
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index c702cfcc..00000000
--- a/Rakefile
+++ /dev/null
@@ -1 +0,0 @@
-require 'bundler/gem_tasks'
diff --git a/appveyor.yml b/appveyor.yml
deleted file mode 100644
index 3d9b96cc..00000000
--- a/appveyor.yml
+++ /dev/null
@@ -1,70 +0,0 @@
-version: "{build}"
-
-clone_depth: 1
-
-environment:
- matrix:
- - RUBY_FOLDER_VERSION: "200"
- RUBY_GEMS_FOLDER: "2.0.0"
- - RUBY_FOLDER_VERSION: "200-x64"
- RUBY_GEMS_FOLDER: "2.0.0"
- - RUBY_FOLDER_VERSION: "21"
- RUBY_GEMS_FOLDER: "2.1.0"
- - RUBY_FOLDER_VERSION: "21-x64"
- RUBY_GEMS_FOLDER: "2.1.0"
- - RUBY_FOLDER_VERSION: "22"
- RUBY_GEMS_FOLDER: "2.2.0"
- - RUBY_FOLDER_VERSION: "22-x64"
- RUBY_GEMS_FOLDER: "2.2.0"
-
-matrix:
- fast_finish: true
-
-cache:
- # Cache installed gems unless dependencies change
- - C:\Ruby%RUBY_FOLDER_VERSION%\bin -> Gemfile,overcommit.gemspec
- - C:\Ruby%RUBY_FOLDER_VERSION%\lib\ruby\gems\%RUBY_GEMS_FOLDER% -> Gemfile,overcommit.gemspec
-
-install:
- # Add ruby executables to PATH
- - set PATH=C:\Ruby%RUBY_FOLDER_VERSION%\bin;%PATH%
- - echo %PATH%
-
- # Print version and location for pre-installed grep
- - grep --version
- - where grep
-
- # Print version and location for pre-installed git
- - git --version
- - where git
-
- # Print version and location for pre-installed ruby
- - ruby --version
- - where ruby
-
- # Install latest version of RubyGems
- - gem update --system --no-ri --no-rdoc
- - gem --version
- - where gem
-
- # Print version and location for pre-installed bundler
- - bundler --version
- - where bundler
-
- # Install ruby dependencies
- - bundle install --retry 3
-
-build: off
-
-before_test:
- # Necessary for AuthorName and AuthorEmail pre-commit hooks to pass
- - git config --global user.email "appveyor@appveyor.ci"
- - git config --global user.name "Appveyor CI"
-
- # Ignore CRLF conversion warnings
- - git config --global core.safecrlf false
-
-test_script:
- - bundle exec rspec --tty --backtrace --color
- - bundle exec overcommit --sign
- - bundle exec overcommit --run
diff --git a/bin/overcommit b/bin/overcommit
index e4737692..d54d179f 100755
--- a/bin/overcommit
+++ b/bin/overcommit
@@ -1,6 +1,50 @@
#!/usr/bin/env ruby
+# frozen_string_literal: true
-require 'overcommit/cli'
+# Check if Overcommit should invoke a Bundler context for loading gems
+require 'yaml'
+if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil
+ ENV['BUNDLE_GEMFILE'] = gemfile
+ require 'bundler'
+
+ begin
+ # We need to temporarily silence STDERR to remove annoying Gem specification
+ # warnings that ultimately don't matter, e.g.
+ # https://github.com/rubygems/rubygems/issues/1070
+ old_stderr = $stderr
+ begin
+ $stderr = File.new(File::NULL, 'w')
+ Bundler.setup
+ ensure
+ $stderr = old_stderr
+ end
+ rescue Bundler::BundlerError => e
+ puts "Problem loading '#{gemfile}': #{e.message}"
+ puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound)
+ exit 78 # EX_CONFIG
+ rescue Gem::LoadError => e
+ # Handle case where user is executing overcommit without `bundle exec` and
+ # whose local Gemfile has a gem requirement that does not match a gem
+ # requirement of the installed version of Overcommit.
+ raise unless e.message =~ /already activated/i
+
+ exec('bundle', 'exec', $0, *ARGV)
+ end
+end
+
+begin
+ require 'overcommit/cli'
+rescue LoadError
+ if gemfile
+ puts 'You have specified the `gemfile` option in your Overcommit ' \
+ 'configuration but have not added the `overcommit` gem to ' \
+ "#{gemfile}."
+ else
+ raise
+ end
+
+ exit 64 # EX_USAGE
+end
logger = Overcommit::Logger.new(STDOUT)
diff --git a/config/default.yml b/config/default.yml
index 26383518..ba8af533 100644
--- a/config/default.yml
+++ b/config/default.yml
@@ -32,6 +32,10 @@
# your repository, and then set the `gemfile` option below to the name you gave
# the file.
# (Generate lock file by running `bundle install --gemfile=.overcommit_gems.rb`)
+#
+# NOTE: the following line will be parsed by a regexp rather than a proper YAML
+# parser, so avoid any values other than false or a string, and don't use inline
+# comments
gemfile: false
# Where to store hook plugins specific to a repository. These are loaded in
@@ -39,6 +43,10 @@ gemfile: false
# to the root of the repository.
plugin_directory: '.git-hooks'
+# Whether to hide hook output by default. This results in completely silent hook
+# runs except in the case of warning or failure.
+quiet: false
+
# Number of hooks that can be run concurrently. Typically this won't need to be
# adjusted, but if you know that some of your hooks themselves use multiple
# processors you can lower this value accordingly. You can define
@@ -62,46 +70,54 @@ CommitMsg:
CapitalizedSubject:
enabled: true
- description: 'Checking subject capitalization'
+ description: 'Check subject capitalization'
EmptyMessage:
enabled: true
- description: 'Checking for empty commit message'
+ description: 'Check for empty commit message'
quiet: true
GerritChangeId:
enabled: false
- description: 'Ensuring Gerrit Change-Id is present'
+ description: 'Ensure Gerrit Change-Id is present'
required: true
HardTabs:
enabled: false
- description: 'Checking for hard tabs'
+ description: 'Check for hard tabs'
+
+ MessageFormat:
+ enabled: false
+ description: 'Check commit message matches expected pattern'
+ pattern: '(.+)[|](.+)[|](.+)'
+ expected_pattern_message: ' | | '
+ sample_message: 'DEFECT-1234 | Refactored Onboarding flow | John Doe'
RussianNovel:
enabled: false
- description: 'Checking length of commit message'
+ description: 'Check length of commit message'
quiet: true
SingleLineSubject:
enabled: true
- description: 'Checking subject line'
+ description: 'Check subject line'
SpellCheck:
enabled: false
- description: 'Checking for misspelled words'
+ description: 'Check for misspelled words'
required_executable: 'hunspell'
flags: ['-a']
TextWidth:
enabled: true
- description: 'Checking text width'
+ description: 'Check text width'
max_subject_width: 60
+ min_subject_width: 0
max_body_width: 72
TrailingPeriod:
enabled: true
- description: 'Checking for trailing periods in subject'
+ description: 'Check for trailing periods in subject'
# Hooks that are run after `git commit` is executed, before the commit message
# editor is displayed. These hooks are ideal for syntax checkers, linters, and
@@ -116,7 +132,7 @@ PreCommit:
AuthorEmail:
enabled: true
- description: 'Checking author email'
+ description: 'Check author email'
requires_files: false
required: true
quiet: true
@@ -124,14 +140,14 @@ PreCommit:
AuthorName:
enabled: true
- description: 'Checking for author name'
+ description: 'Check for author name'
requires_files: false
required: true
quiet: true
BerksfileCheck:
enabled: false
- description: 'Checking Berksfile lock'
+ description: 'Check Berksfile lock'
required_executable: 'berks'
flags: ['list', '--quiet']
install_command: 'gem install berks'
@@ -139,23 +155,20 @@ PreCommit:
- 'Berksfile'
- 'Berksfile.lock'
- Brakeman:
- enabled: false
- description: 'Checking for security vulnerabilities'
- required_executable: 'brakeman'
- flags: ['--exit-on-warn', '--quiet', '--summary', '--only-files']
- install_command: 'gem install brakeman'
- include:
- - '**/*.rb'
-
BrokenSymlinks:
enabled: true
- description: 'Checking for broken symlinks'
+ description: 'Check for broken symlinks'
quiet: true
+ BundleAudit:
+ enabled: false
+ description: 'Check for vulnerable versions of gems'
+ required_executable: 'bundle-audit'
+ install_command: 'gem install bundler-audit'
+
BundleCheck:
enabled: false
- description: 'Checking Gemfile dependencies'
+ description: 'Check Gemfile dependencies'
required_executable: 'bundle'
flags: ['check']
install_command: 'gem install bundler'
@@ -164,49 +177,126 @@ PreCommit:
- 'Gemfile.lock'
- '*.gemspec'
+ BundleOutdated:
+ enabled: false
+ description: 'List installed gems with newer versions available'
+ required_executable: 'bundle'
+ flags: ['outdated', '--strict', '--parseable']
+ install_command: 'gem install bundler'
+
CaseConflicts:
enabled: true
- description: 'Checking for case-insensitivity conflicts'
+ description: 'Check for case-insensitivity conflicts'
quiet: true
+ ChamberCompare:
+ enabled: false
+ description: 'Check that settings are equivalent between namespaces'
+ required_executable: 'chamber'
+ flags: ['compare']
+ install_command: 'gem install chamber'
+ namespaces:
+ - ['development']
+ - ['test']
+ - ['production']
+ exclusions: []
+ include: &chamber_settings_files
+ - 'config/settings*.yml'
+ - 'config/settings*.yml.erb'
+ - 'config/settings/**/*.yml'
+ - 'config/settings/**/*.yml.erb'
+ - 'settings*.yml'
+ - 'settings*.yml.erb'
+ - 'settings/**/*.yml'
+ - 'settings/**/*.yml.erb'
+
ChamberSecurity:
enabled: false
- description: 'Checking that settings have been secured with Chamber'
+ description: 'Check that settings have been secured with Chamber'
required_executable: 'chamber'
flags: ['secure', '--files']
install_command: 'gem install chamber'
+ include: *chamber_settings_files
+
+ ChamberVerification:
+ enabled: false
+ description: 'Verify that all settings changes have been approved'
+ required_executable: 'chamber'
+ flags: ['sign', '--verify']
+ install_command: 'gem install chamber'
+ include: *chamber_settings_files
+
+ CodeSpellCheck:
+ enabled: false
+ description: 'Check if all your code is spell-checked correctly'
+ command: 'alfonsox'
+ install_command: 'gem install alfonsox'
include:
- - 'config/settings.yml'
- - 'config/settings/**/*.yml'
+ - '**/*.rb'
+ - '**/*.erb'
CoffeeLint:
enabled: false
- description: 'Analyzing with coffeelint'
+ description: 'Analyze with coffeelint'
required_executable: 'coffeelint'
flags: ['--reporter=csv']
install_command: 'npm install -g coffeelint'
include: '**/*.coffee'
+ CookStyle:
+ enabled: false
+ description: 'Analyze with CookStyle'
+ required_executable: 'cookstyle'
+ flags: ['--format=emacs', '--force-exclusion', '--display-cop-names']
+ install_command: 'gem install cookstyle'
+ include:
+ - '**/*.rb'
+ - '**/*.erb'
+
+ Credo:
+ enabled: false
+ description: 'Analyze with credo'
+ required_executable: 'mix'
+ flags: ['credo', '--all', '--strict', '--format', 'flycheck']
+ include:
+ - '**/*.ex'
+ - '**/*.exs'
+
CssLint:
enabled: false
- description: 'Analyzing with csslint'
+ description: 'Analyze with csslint'
required_executable: 'csslint'
flags: ['--quiet', '--format=compact']
install_command: 'npm install -g csslint'
include: '**/*.css'
+ DartAnalyzer:
+ enabled: false
+ description: 'Analyze with dartanalyzer'
+ required_executable: 'dartanalyzer'
+ flags: []
+ include:
+ - '**/*.dart'
+
Dogma:
enabled: false
- description: 'Analyzing with dogma'
+ description: 'Analyze with dogma'
required_executable: 'mix'
flags: ['dogma']
include:
- '**/*.ex'
- '**/*.exs'
+ ErbLint:
+ enabled: false
+ description: 'Analyze with ERB Lint'
+ required_executable: 'erblint'
+ install_command: 'bundle install erb_lint'
+ include: '**/*.html.erb'
+
EsLint:
enabled: false
- description: 'Analyzing with ESLint'
+ description: 'Analyze with ESLint'
required_executable: 'eslint'
flags: ['--format=compact']
install_command: 'npm install -g eslint'
@@ -214,62 +304,139 @@ PreCommit:
ExecutePermissions:
enabled: false
- description: 'Checking for file execute permissions'
+ description: 'Check for file execute permissions'
+ quiet: true
+
+ Fasterer:
+ enabled: false
+ description: 'Analyzing for potential speed improvements'
+ required_executable: 'fasterer'
+ install_command: 'gem install fasterer'
+ include: '**/*.rb'
+
+ FixMe:
+ enabled: false
+ description: 'Check for "token" strings'
+ required_executable: 'grep'
+ flags: ['-IEHnw']
+ keywords: ['BROKEN', 'BUG', 'ERROR', 'FIXME', 'HACK', 'NOTE', 'OPTIMIZE', 'REVIEW', 'TODO', 'WTF', 'XXX']
+
+ FileSize:
+ enabled: false
+ description: 'Check for oversized files'
+ size_limit_bytes: 1_000_000
+
+ Flay:
+ enabled: false
+ description: 'Analyze ruby code for structural similarities with Flay'
+ required_executable: 'flay'
+ install_command: 'gem install flay'
+ mass_threshold: 16
+ fuzzy: 1
+ liberal: false
+ include: '**/*.rb'
+
+ Foodcritic:
+ enabled: false
+ description: 'Analyze with Foodcritic'
+ required_executable: 'foodcritic'
+ flags: ['--epic-fail=any']
+ install_command: 'gem install foodcritic'
+
+ ForbiddenBranches:
+ enabled: false
+ description: 'Check for commit to forbidden branch'
quiet: true
+ branch_patterns: ['master']
+
+ GinkgoFocus:
+ enabled: false
+ description: 'Check for "focused" tests'
+ required_executable: 'grep'
+ flags: ['-IEHnw']
+ keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen']
+
+ GoFmt:
+ enabled: false
+ description: 'Fix with go fmt'
+ required_executable: 'go'
+ command: ['go', 'fmt']
+ parallelize: false
+ include: '**/*.go'
+
+ GolangciLint:
+ enabled: false
+ description: 'Analyze with golangci-lint'
+ required_executable: 'golangci-lint'
+ install_command: 'go get github.com/golangci/golangci-lint/cmd/golangci-lint'
+ flags: ['--out-format=line-number', '--print-issued-lines=false']
+ command: ['golangci-lint', 'run']
+ include: '**/*.go'
GoLint:
enabled: false
- description: 'Analyzing with golint'
+ description: 'Analyze with golint'
required_executable: 'golint'
install_command: 'go get github.com/golang/lint/golint'
include: '**/*.go'
GoVet:
enabled: false
- description: 'Analyzing with go vet'
+ description: 'Analyze with go vet'
required_executable: 'go'
flags: ['tool', 'vet']
install_command: 'go get golang.org/x/tools/cmd/vet'
include: '**/*.go'
+ Hadolint:
+ enabled: false
+ description: 'Analyze with hadolint'
+ required_executable: 'hadolint'
+ include:
+ - '**/Dockerfile*'
+
HamlLint:
enabled: false
- description: 'Analyzing with haml-lint'
+ description: 'Analyze with haml-lint'
required_executable: 'haml-lint'
install_command: 'gem install haml-lint'
+ flags: ['--no-summary']
include: '**/*.haml'
HardTabs:
enabled: false
- description: 'Checking for hard tabs'
+ description: 'Check for hard tabs'
quiet: true
required_executable: 'grep'
flags: ['-IHn', "\t"]
+ exclude:
+ - '**/Makefile'
+ - '**/*.go'
Hlint:
enabled: false
- description: 'Analyzing with hlint'
+ description: 'Analyze with hlint'
required_executable: 'hlint'
install_command: 'cabal install hlint'
include: '**/*.hs'
HtmlHint:
enabled: false
- description: 'Analyzing with HTMLHint'
+ description: 'Analyze with HTMLHint'
required_executable: 'htmlhint'
install_command: 'npm install -g htmlhint'
include: '**/*.html'
HtmlTidy:
enabled: false
- description: 'Analyzing HTML with tidy'
+ description: 'Analyze HTML with tidy'
required_executable: 'tidy'
flags: ['-errors', '-quiet', '-utf8']
include: '**/*.html'
ImageOptim:
enabled: false
- description: 'Checking for optimizable images'
+ description: 'Check for optimizable images'
required_executable: 'image_optim'
install_command: 'gem install image_optim'
include:
@@ -281,22 +448,22 @@ PreCommit:
JavaCheckstyle:
enabled: false
- description: 'Analyzing with checkstyle'
+ description: 'Analyze with checkstyle'
required_executable: 'checkstyle'
flags: ['-c', '/sun_checks.xml']
include: '**/*.java'
Jscs:
enabled: false
- description: 'Analyzing with JSCS'
+ description: 'Analyze with JSCS'
required_executable: 'jscs'
- flags: ['--reporter=inline', '--verbose']
+ flags: ['--reporter=inline']
install_command: 'npm install -g jscs'
include: '**/*.js'
JsHint:
enabled: false
- description: 'Analyzing with JSHint'
+ description: 'Analyze with JSHint'
required_executable: 'jshint'
flags: ['--verbose']
install_command: 'npm install -g jshint'
@@ -304,7 +471,7 @@ PreCommit:
JsLint:
enabled: false
- description: 'Analyzing with JSLint'
+ description: 'Analyze with JSLint'
required_executable: 'jslint'
flags: ['--terse']
install_command: 'npm install -g jslint'
@@ -312,63 +479,148 @@ PreCommit:
Jsl:
enabled: false
- description: 'Analyzing with JSL'
+ description: 'Analyze with JSL'
required_executable: 'jsl'
flags: ['-nologo', '-nofilelisting', '-nocontext', '-nosummary']
include: '**/*.js'
JsonSyntax:
enabled: false
- description: 'Validating JSON syntax'
+ description: 'Validate JSON syntax'
required_library: 'json'
install_command: 'gem install json'
include: '**/*.json'
+ KtLint:
+ enabled: false
+ description: 'Analyze with KtLint'
+ required_executable: 'ktlint'
+ flags: []
+ include: '**/*.kt'
+
+ LicenseFinder:
+ enabled: false
+ description: 'Analyze with LicenseFinder'
+ required_executable: 'license_finder'
+ install_command: 'gem install license_finder'
+ include:
+ - 'Gemfile'
+ - 'requirements.txt'
+ - 'package.json'
+ - 'pom.xml'
+ - 'build.gradle'
+ - 'bower.json'
+ - 'Podfile'
+ - 'rebar.config'
+
+ LicenseHeader:
+ enabled: false
+ license_file: 'LICENSE.txt'
+ description: 'Check source files for license headers'
+
LocalPathsInGemfile:
enabled: false
- description: 'Checking for local paths in Gemfile'
+ description: 'Check for local paths in Gemfile'
required_executable: 'grep'
flags: ['-IHnE', "^[^#]*((\\bpath:)|(:path[ \t]*=>))"]
include: '**/Gemfile'
Mdl:
enabled: false
- description: 'Analyzing with mdl'
+ description: 'Analyze markdown files with mdl'
required_executable: 'mdl'
+ flags: ['--json']
install_command: 'gem install mdl'
include: '**/*.md'
MergeConflicts:
enabled: true
- description: 'Checking for merge conflicts'
+ description: 'Check for merge conflicts'
quiet: true
required_executable: 'grep'
flags: ['-IHn', "^<<<<<<<[ \t]"]
+ MixFormat:
+ enabled: false
+ description: 'Check formatting with mix format'
+ required_executable: 'mix'
+ flags: ['format', '--check-formatted']
+ include:
+ - '**/*.ex'
+ - '**/*.heex'
+ - '**/*.exs'
+
+ PuppetMetadataJsonLint:
+ enabled: false
+ description: 'Checking module metadata'
+ flags: ['--strict-license', '--strict-dependencies', '--fail-on-warning']
+ include: 'metadata.json'
+ required_executable: 'metadata-json-lint'
+ install_command: 'gem install metadata-json-lint'
+
NginxTest:
enabled: false
- description: 'Testing nginx configs'
+ description: 'Test nginx configs'
required_executable: 'nginx'
flags: ['-t']
include: '**/nginx.conf'
- Pep257:
+ Pep257: # Deprecated – use Pydocstyle instead.
enabled: false
- description: 'Analyzing docstrings with pep257'
+ description: 'Analyze docstrings with pep257'
required_executable: 'pep257'
install_command: 'pip install pep257'
include: '**/*.py'
- Pep8:
+ Pep8: # Deprecated – use Pycodestyle instead.
enabled: false
- description: 'Analyzing with pep8'
+ description: 'Analyze with pep8'
required_executable: 'pep8'
install_command: 'pip install pep8'
include: '**/*.py'
+ PhpLint:
+ enabled: false
+ description: 'Testing with PHP lint'
+ required_executable: 'php'
+ command: 'php'
+ flags: ['-l']
+ include: '**/*.php'
+
+ PhpCs:
+ enabled: false
+ description: 'Analyze with PHP_CodeSniffer'
+ command: 'vendor/bin/phpcs'
+ flags: ['--standard=PSR2', '--report=csv']
+ include: '**/*.php'
+
+ PhpCsFixer:
+ enabled: false
+ description: 'Fix non compliant PHP files'
+ required_executable: 'php-cs-fixer'
+ command: 'vendor/bin/php-cs-fixer'
+ flags: ['fix', '-v', '--path-mode=intersection']
+ install_command: 'composer global require friendsofphp/php-cs-fixer'
+ include: '**/*.php'
+
+ PhpStan:
+ description: 'Analyze with phpstan'
+ enabled: false
+ command: 'phpstan'
+ flags: ['analyze', '--errorFormat=raw']
+ include:
+ - '**/*.php'
+
+ Pronto:
+ enabled: false
+ description: 'Analyzing with pronto'
+ required_executable: 'pronto'
+ install_command: 'gem install pronto'
+ flags: ['run', '--staged', '--exit-code']
+
PuppetLint:
enabled: false
- description: 'Analyzing with puppet-lint'
+ description: 'Analyze with puppet-lint'
required_executable: 'puppet-lint'
install_command: 'gem install puppet-lint'
flags:
@@ -377,16 +629,30 @@ PreCommit:
- '--error-level=all'
include: '**/*.pp'
+ Pycodestyle:
+ enabled: false
+ description: 'Analyze with pycodestyle'
+ required_executable: 'pycodestyle'
+ install_command: 'pip install pycodestyle'
+ include: '**/*.py'
+
+ Pydocstyle:
+ enabled: false
+ description: 'Analyze docstrings with pydocstyle'
+ required_executable: 'pydocstyle'
+ install_command: 'pip install pydocstyle'
+ include: '**/*.py'
+
Pyflakes:
enabled: false
- description: 'Analyzing with pyflakes'
+ description: 'Analyze with pyflakes'
required_executable: 'pyflakes'
install_command: 'pip install pyflakes'
include: '**/*.py'
Pylint:
enabled: false
- description: 'Analyzing with Pylint'
+ description: 'Analyze with Pylint'
required_executable: 'pylint'
install_command: 'pip install pylint'
flags:
@@ -397,21 +663,31 @@ PreCommit:
PythonFlake8:
enabled: false
- description: 'Analyzing with flake8'
+ description: 'Analyze with flake8'
required_executable: 'flake8'
install_command: 'pip install flake8'
include: '**/*.py'
+ RakeTarget:
+ enabled: false
+ description: 'Run rake targets'
+ # targets:
+ # - 'lint'
+ # - 'validate'
+ # - '...'
+ required_executable: 'rake'
+ install_command: 'gem install rake'
+
RailsBestPractices:
enabled: false
- description: 'Analyzing with RailsBestPractices'
+ description: 'Analyze with RailsBestPractices'
required_executable: 'rails_best_practices'
flags: ['--without-color']
install_command: 'gem install rails_best_practices'
RailsSchemaUpToDate:
enabled: false
- description: 'Checking if database schema is up to date'
+ description: 'Check if database schema is up to date'
include:
- 'db/migrate/*.rb'
- 'db/schema.rb'
@@ -419,9 +695,9 @@ PreCommit:
Reek:
enabled: false
- description: 'Analyzing with Reek'
+ description: 'Analyze with Reek'
required_executable: 'reek'
- flags: ['--single-line', '--no-color']
+ flags: ['--single-line', '--no-color', '--force-exclusion']
install_command: 'gem install reek'
include:
- '**/*.gemspec'
@@ -430,9 +706,21 @@ PreCommit:
- '**/Gemfile'
- '**/Rakefile'
+ RstLint:
+ enabled: false
+ description: 'Analyze reStructuredText files with rst-lint'
+ required_executable: 'rst-lint'
+ install_command: 'pip install restructuredtext_lint'
+ include: '**/*.rst'
+
+ RSpec:
+ enabled: false
+ description: 'Run tests with Rspec'
+ required_executable: 'rspec'
+
RuboCop:
enabled: false
- description: 'Analyzing with RuboCop'
+ description: 'Analyze with RuboCop'
required_executable: 'rubocop'
flags: ['--format=emacs', '--force-exclusion', '--display-cop-names']
install_command: 'gem install rubocop'
@@ -440,12 +728,13 @@ PreCommit:
- '**/*.gemspec'
- '**/*.rake'
- '**/*.rb'
+ - '**/*.ru'
- '**/Gemfile'
- '**/Rakefile'
RubyLint:
enabled: false
- description: 'Analyzing with ruby-lint'
+ description: 'Analyze with ruby-lint'
required_executable: 'ruby-lint'
flags: ['--presenter=syntastic', '--levels=error,warning']
install_command: 'gem install ruby-lint'
@@ -453,29 +742,44 @@ PreCommit:
- '**/*.gemspec'
- '**/*.rb'
+ RubySyntax:
+ enabled: false
+ description: 'Check ruby syntax'
+ required_executable: 'ruby'
+ command: [
+ 'ruby',
+ '-e',
+ 'ARGV.each { |applicable_file| ruby_c_output = `ruby -c #{applicable_file}`; puts ruby_c_output unless $?.success? }'
+ ]
+ include:
+ - '**/*.gemspec'
+ - '**/*.rb'
+
Scalariform:
enabled: false
- description: 'Checking formatting with Scalariform'
+ description: 'Check formatting with Scalariform'
required_executable: 'scalariform'
flags: ['--test']
include: '**/*.scala'
Scalastyle:
enabled: false
- description: 'Analyzing with Scalastyle'
+ description: 'Analyze with Scalastyle'
required_executable: 'scalastyle'
include: '**/*.scala'
ScssLint:
enabled: false
- description: 'Analyzing with scss-lint'
+ description: 'Analyze with scss-lint'
+ required_library: 'json'
required_executable: 'scss-lint'
- install_command: 'gem install scss-lint'
+ flags: ['--format', 'JSON']
+ install_command: 'gem install scss_lint'
include: '**/*.scss'
SemiStandard:
enabled: false
- description: 'Analyzing with semistandard'
+ description: 'Analyze with semistandard'
required_executable: 'semistandard'
flags: ['--verbose']
install_command: 'npm install -g semistandard'
@@ -483,42 +787,83 @@ PreCommit:
ShellCheck:
enabled: false
- description: 'Analyzing with ShellCheck'
+ description: 'Analyze with ShellCheck'
required_executable: 'shellcheck'
flags: ['--format=gcc']
include: '**/*.sh'
SlimLint:
enabled: false
- description: 'Analyzing with slim-lint'
+ description: 'Analyze with slim-lint'
required_executable: 'slim-lint'
install_command: 'gem install slim_lint'
include: '**/*.slim'
+ Sorbet:
+ enabled: false
+ description: 'Analyze with Sorbet'
+ required_executable: 'srb'
+ install_command: 'gem install sorbet'
+ command: ['srb', 'tc']
+ include: '**/*.rb'
+
Sqlint:
enabled: false
- description: 'Analyzing with sqlint'
+ description: 'Analyze with sqlint'
required_executable: 'sqlint'
install_command: 'gem install sqlint'
include: '**/*.sql'
Standard:
enabled: false
- description: 'Analyzing with standard'
+ description: 'Analyze with standard'
required_executable: 'standard'
flags: ['--verbose']
install_command: 'npm install -g standard'
include: '**/*.js'
+ Stylelint:
+ enabled: false
+ description: 'Check styles with Stylelint'
+ required_executable: 'stylelint'
+ flags: ['-f', 'compact']
+ install_command: 'npm install -g stylelint'
+ include:
+ - '**/*.scss'
+ - '**/*.css'
+ - '**/*.less'
+
+ SwiftLint:
+ enabled: false
+ description: 'Analyze with SwiftLint'
+ required_executable: 'swiftlint'
+ flags: ['lint', '--strict']
+ install_command: 'brew install swiftlint'
+ include: '**/*.swift'
+
+ TerraformFormat:
+ enabled: false
+ description: 'Analyze with Terraform'
+ required_executable: 'terraform'
+ flags: ['fmt', '-check=true', '-diff=false']
+ include: '**/*.tf'
+
+ TsLint:
+ enabled: false
+ description: 'Analyze with TSLint'
+ required_executable: 'tslint'
+ install_command: 'npm install -g tslint typescript'
+ include: '**/*.ts'
+
TrailingWhitespace:
enabled: false
- description: 'Checking for trailing whitespace'
+ description: 'Check for trailing whitespace'
required_executable: 'grep'
flags: ['-IHn', "[ \t]$"]
TravisLint:
enabled: false
- description: 'Checking Travis CI configuration'
+ description: 'Check Travis CI configuration'
required_executable: 'travis'
flags: ['lint']
install_command: 'gem install travis'
@@ -526,7 +871,7 @@ PreCommit:
Vint:
enabled: false
- description: 'Analyzing with Vint'
+ description: 'Analyze with Vint'
required_executable: 'vint'
install_command: 'pip install vim-vint'
include:
@@ -535,7 +880,7 @@ PreCommit:
W3cCss:
enabled: false
- description: 'Analyzing with W3C CSS validation service'
+ description: 'Analyze with W3C CSS validation service'
required_library: 'w3c_validators'
install_command: 'gem install w3c_validators'
validator_uri: 'http://jigsaw.w3.org/css-validator/validator'
@@ -547,18 +892,23 @@ PreCommit:
W3cHtml:
enabled: false
- description: 'Analyzing with W3C HTML validation service'
+ description: 'Analyze with W3C HTML validation service'
required_library: 'w3c_validators'
install_command: 'gem install w3c_validators'
- validator_uri: 'http://validator.w3.org/check'
+ validator_uri: 'https://validator.w3.org/nu'
charset: 'utf-8'
doctype: 'HTML5'
include:
- '**/*.html'
+ LineEndings:
+ description: 'Check line endings'
+ enabled: false
+ eol: "\n" # or "\r\n" for Windows-style newlines
+
XmlLint:
enabled: false
- description: 'Analyzing with xmllint'
+ description: 'Analyze with xmllint'
required_executable: 'xmllint'
flags: ['--noout']
include:
@@ -567,29 +917,61 @@ PreCommit:
XmlSyntax:
enabled: false
- description: 'Checking XML syntax'
+ description: 'Check XML syntax'
required_library: 'rexml/document'
include:
- '**/*.xml'
- '**/*.svg'
+ YamlLint:
+ enabled: false
+ description: 'Analyze with YAMLlint'
+ required_executable: 'yamllint'
+ flags: ['--format=parsable', '--strict']
+ install_command: 'pip install yamllint'
+ include:
+ - '**/*.yaml'
+ - '**/*.yml'
+
YamlSyntax:
enabled: false
- description: 'Checking YAML syntax'
+ description: 'Check YAML syntax'
required_library: 'yaml'
include:
- '**/*.yaml'
- '**/*.yml'
+ YardCoverage:
+ enabled: false
+ description: 'Checking for yard coverage'
+ command: ['yard', 'stats', '--list-undoc', '--compact']
+ flags: ['--private', '--protected']
+ required_executable: 'yard'
+ install_command: 'gem install yard'
+ min_coverage_percentage: 100
+ include:
+ - '/**/*.rb'
+
+ YarnCheck:
+ enabled: false
+ description: 'Check yarn.lock dependencies'
+ required_executable: 'yarn'
+ flags: ['check', '--silent', '--no-progress', '--non-interactive']
+ install_command: 'npm install --global yarn'
+ include:
+ - 'package.json'
+ - 'yarn.lock'
+
# Hooks that run after HEAD changes or a file is explicitly checked out.
PostCheckout:
ALL:
required: false
quiet: false
+ skip_file_checkout: true
BowerInstall:
enabled: false
- description: 'Installing bower dependencies'
+ description: 'Install bower dependencies'
requires_files: true
required_executable: 'bower'
install_command: 'npm install -g bower'
@@ -598,7 +980,7 @@ PostCheckout:
BundleInstall:
enabled: false
- description: 'Installing Bundler dependencies'
+ description: 'Install Bundler dependencies'
requires_files: true
required_executable: 'bundle'
install_command: 'gem install bundler'
@@ -608,15 +990,30 @@ PostCheckout:
- 'Gemfile.lock'
- '*.gemspec'
+ ComposerInstall:
+ enabled: false
+ description: 'Install composer dependencies'
+ requires_files: true
+ required_executable: 'composer'
+ install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer'
+ flags: ['install']
+ include: 'composer.json'
+
+ GitLfs:
+ enabled: false
+ description: 'Check status of lockable files tracked by Git LFS'
+ required_executable: 'git-lfs'
+ install_command: 'brew install git-lfs'
+
IndexTags:
enabled: false
- description: 'Generating tags file from source'
+ description: 'Generate tags file from source'
quiet: true
required_executable: 'ctags'
NpmInstall:
enabled: false
- description: 'Installing NPM dependencies'
+ description: 'Install NPM dependencies'
requires_files: true
required_executable: 'npm'
flags: ['install']
@@ -626,10 +1023,20 @@ PostCheckout:
SubmoduleStatus:
enabled: false
- description: 'Checking submodule status'
+ description: 'Check submodule status'
quiet: true
recursive: false
+ YarnInstall:
+ enabled: false
+ description: 'Install Yarn dependencies'
+ requires_files: true
+ required_executable: 'yarn'
+ flags: ['install']
+ include:
+ - 'package.json'
+ - 'yarn.lock'
+
# Hooks that run after a commit is created.
PostCommit:
ALL:
@@ -639,7 +1046,7 @@ PostCommit:
BowerInstall:
enabled: false
- description: 'Installing bower dependencies'
+ description: 'Install bower dependencies'
requires_files: true
required_executable: 'bower'
install_command: 'npm install -g bower'
@@ -648,7 +1055,7 @@ PostCommit:
BundleInstall:
enabled: false
- description: 'Installing Bundler dependencies'
+ description: 'Install Bundler dependencies'
requires_files: true
required_executable: 'bundle'
install_command: 'gem install bundler'
@@ -658,23 +1065,45 @@ PostCommit:
- 'Gemfile.lock'
- '*.gemspec'
+ Commitplease:
+ enabled: false
+ description: 'Analyze with Commitplease'
+ required_executable: './node_modules/.bin/commitplease'
+ install_command: 'npm install --save-dev commitplease'
+ flags: ['-1']
+
+ ComposerInstall:
+ enabled: false
+ description: 'Install composer dependencies'
+ requires_files: true
+ required_executable: 'composer'
+ install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer'
+ flags: ['install']
+ include: 'composer.json'
+
GitGuilt:
enabled: false
- description: 'Calculating changes in blame since last commit'
+ description: 'Calculate changes in blame since last commit'
requires_files: true
required_executable: 'git-guilt'
flags: ['HEAD~', 'HEAD']
install_command: 'npm install -g git-guilt'
+ GitLfs:
+ enabled: false
+ description: 'Check status of lockable files tracked by Git LFS'
+ required_executable: 'git-lfs'
+ install_command: 'brew install git-lfs'
+
IndexTags:
enabled: false
- description: 'Generating tags file from source'
+ description: 'Generate tags file from source'
quiet: true
required_executable: 'ctags'
NpmInstall:
enabled: false
- description: 'Installing NPM dependencies'
+ description: 'Install NPM dependencies'
requires_files: true
required_executable: 'npm'
flags: ['install']
@@ -684,10 +1113,20 @@ PostCommit:
SubmoduleStatus:
enabled: false
- description: 'Checking submodule status'
+ description: 'Check submodule status'
quiet: true
recursive: false
+ YarnInstall:
+ enabled: false
+ description: 'Install Yarn dependencies'
+ requires_files: true
+ required_executable: 'yarn'
+ flags: ['install']
+ include:
+ - 'package.json'
+ - 'yarn.lock'
+
# Hooks that run after `git merge` executes successfully (no merge conflicts).
PostMerge:
ALL:
@@ -696,7 +1135,7 @@ PostMerge:
BowerInstall:
enabled: false
- description: 'Installing bower dependencies'
+ description: 'Install bower dependencies'
requires_files: true
required_executable: 'bower'
install_command: 'npm install -g bower'
@@ -705,7 +1144,7 @@ PostMerge:
BundleInstall:
enabled: false
- description: 'Installing Bundler dependencies'
+ description: 'Install Bundler dependencies'
requires_files: true
required_executable: 'bundle'
install_command: 'gem install bundler'
@@ -715,15 +1154,30 @@ PostMerge:
- 'Gemfile.lock'
- '*.gemspec'
+ ComposerInstall:
+ enabled: false
+ description: 'Install composer dependencies'
+ requires_files: true
+ required_executable: 'composer'
+ install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer'
+ flags: ['install']
+ include: 'composer.json'
+
+ GitLfs:
+ enabled: false
+ description: 'Check status of lockable files tracked by Git LFS'
+ required_executable: 'git-lfs'
+ install_command: 'brew install git-lfs'
+
IndexTags:
enabled: false
- description: 'Generating tags file from source'
+ description: 'Generate tags file from source'
quiet: true
required_executable: 'ctags'
NpmInstall:
enabled: false
- description: 'Installing NPM dependencies'
+ description: 'Install NPM dependencies'
requires_files: true
required_executable: 'npm'
flags: ['install']
@@ -733,10 +1187,20 @@ PostMerge:
SubmoduleStatus:
enabled: false
- description: 'Checking submodule status'
+ description: 'Check submodule status'
quiet: true
recursive: false
+ YarnInstall:
+ enabled: false
+ description: 'Install Yarn dependencies'
+ requires_files: true
+ required_executable: 'yarn'
+ flags: ['install']
+ include:
+ - 'package.json'
+ - 'yarn.lock'
+
# Hooks that run after a commit is modified by an amend or rebase.
PostRewrite:
ALL:
@@ -745,7 +1209,7 @@ PostRewrite:
BowerInstall:
enabled: false
- description: 'Installing bower dependencies'
+ description: 'Install bower dependencies'
requires_files: true
required_executable: 'bower'
install_command: 'npm install -g bower'
@@ -754,7 +1218,7 @@ PostRewrite:
BundleInstall:
enabled: false
- description: 'Installing Bundler dependencies'
+ description: 'Install Bundler dependencies'
requires_files: true
required_executable: 'bundle'
install_command: 'gem install bundler'
@@ -764,15 +1228,24 @@ PostRewrite:
- 'Gemfile.lock'
- '*.gemspec'
+ ComposerInstall:
+ enabled: false
+ description: 'Install composer dependencies'
+ requires_files: true
+ required_executable: 'composer'
+ install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer'
+ flags: ['install']
+ include: 'composer.json'
+
IndexTags:
enabled: false
- description: 'Generating tags file from source'
+ description: 'Generate tags file from source'
quiet: true
required_executable: 'ctags'
NpmInstall:
enabled: false
- description: 'Installing NPM dependencies'
+ description: 'Install NPM dependencies'
requires_files: true
required_executable: 'npm'
flags: ['install']
@@ -782,10 +1255,40 @@ PostRewrite:
SubmoduleStatus:
enabled: false
- description: 'Checking submodule status'
+ description: 'Check submodule status'
quiet: true
recursive: false
+ YarnInstall:
+ enabled: false
+ description: 'Install Yarn dependencies'
+ requires_files: true
+ required_executable: 'yarn'
+ flags: ['install']
+ include:
+ - 'package.json'
+ - 'yarn.lock'
+
+# Hooks that run during the `prepare-commit-msg` hook.
+PrepareCommitMsg:
+ ALL:
+ requires_files: false
+ required: false
+ quiet: false
+
+ ReplaceBranch:
+ enabled: false
+ description: 'Prepends the commit message with text based on the branch name'
+ branch_pattern: '\A(\d+)-(\w+).*\z'
+ replacement_text: '[#\1]'
+ skipped_commit_types:
+ - 'message' # if message is given via `-m`, `-F`
+ - 'template' # if `-t` is given or `commit.template` is set
+ - 'commit' # if `-c`, `-C`, or `--amend` is given
+ - 'merge' # if merging
+ - 'squash' # if squashing
+ on_fail: warn
+
# Hooks that run during `git push`, after remote refs have been updated but
# before any objects have been transferred.
PrePush:
@@ -794,21 +1297,115 @@ PrePush:
required: false
quiet: false
+ Brakeman:
+ enabled: false
+ description: 'Check for security vulnerabilities'
+ required_executable: 'brakeman'
+ flags: ['--exit-on-warn', '--quiet', '--summary']
+ install_command: 'gem install brakeman'
+
+ CargoTest:
+ enabled: false
+ description: 'Run tests with cargo'
+ required_executable: 'cargo'
+ flags: ['test']
+ include: 'src/**/*.rs'
+
+ FlutterTest:
+ enabled: false
+ description: 'Run flutter test suite'
+ required_executable: 'flutter'
+ flags: ['test']
+
+ GitLfs:
+ enabled: false
+ description: 'Upload files tracked by Git LFS'
+ required_executable: 'git-lfs'
+ install_command: 'brew install git-lfs'
+
+ GolangciLint:
+ enabled: false
+ description: 'Analyze with golangci-lint'
+ required_executable: 'golangci-lint'
+ install_command: 'go get github.com/golangci/golangci-lint/cmd/golangci-lint'
+ flags: ['--out-format=line-number', '--print-issued-lines=false']
+ command: ['golangci-lint', 'run']
+
+ GoTest:
+ enabled: false
+ description: 'Run go test suite'
+ required_executable: 'go'
+ command: ['go', 'test', './...']
+
+ Minitest:
+ enabled: false
+ description: 'Run Minitest test suite'
+ command: ['ruby', '-Ilib:test', '-rminitest', "-e 'exit! Minitest.run'"]
+ include: 'test/**/*_test.rb'
+
+ MixTest:
+ enabled: false
+ description: 'Run mix test suite'
+ required_executable: 'mix'
+ flags: ['test']
+
+ PhpUnit:
+ enabled: false
+ description: 'Run PhpUnit test suite'
+ command: 'vendor/bin/phpunit'
+ flags: ['--bootstrap', 'vendor/autoload.php', 'tests']
+ install_command: 'composer require --dev phpunit/phpunit'
+
+ Pronto:
+ enabled: false
+ description: 'Analyzing with pronto'
+ required_executable: 'pronto'
+ install_command: 'gem install pronto'
+ flags: ['run', '--exit-code']
+
ProtectedBranches:
enabled: false
- description: 'Checking for illegal pushes to protected branches'
+ description: 'Check for illegal pushes to protected branches'
+ destructive_only: true
branches: ['master']
+ PubTest:
+ enabled: false
+ description: 'Run pub test suite'
+ required_executable: 'pub'
+ flags: ['run', 'test']
+
+ Pytest:
+ enabled: false
+ description: 'Run pytest test suite'
+ required_executable: 'pytest'
+ install_command: 'pip install -U pytest'
+
+ PythonNose:
+ enabled: false
+ description: 'Run nose test suite'
+ required_executable: 'nosetests'
+ install_command: 'pip install -U nose'
+
RSpec:
enabled: false
- description: 'Running RSpec test suite'
+ description: 'Run RSpec test suite'
required_executable: 'rspec'
- Minitest:
+ RakeTarget:
+ enabled: false
+ description: 'Run rake targets'
+ # targets:
+ # - 'lint'
+ # - 'validate'
+ # - '...'
+ required_executable: 'rake'
+ install_command: 'gem install rake'
+
+ TestUnit:
enabled: false
- description: 'Running Minitest test suite'
- command: ['ruby', '-Ilib:test', 'test']
- required_library: 'minitest'
+ description: 'Run Test::Unit test suite'
+ command: ['ruby', '-Ilib:test', '-rtest/unit', "-e 'exit! Test::Unit::AutoRunner.run'"]
# Hooks that run during `git rebase`, before any commits are rebased.
# If a hook fails, the rebase is aborted.
@@ -820,5 +1417,5 @@ PreRebase:
MergedCommits:
enabled: false
- description: 'Checking for commits that have already been merged'
+ description: 'Check for commits that have already been merged'
branches: ['master']
diff --git a/config/starter.yml b/config/starter.yml
index 30c88f01..c2bc6865 100644
--- a/config/starter.yml
+++ b/config/starter.yml
@@ -1,6 +1,6 @@
# Use this file to configure the Overcommit hooks you wish to use. This will
# extend the default configuration defined in:
-# https://github.com/brigade/overcommit/blob/master/config/default.yml
+# https://github.com/sds/overcommit/blob/master/config/default.yml
#
# At the topmost level of this YAML file is a key representing type of hook
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
@@ -8,10 +8,10 @@
# `include`), whether to only display output if it fails (via `quiet`), etc.
#
# For a complete list of hooks, see:
-# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook
+# https://github.com/sds/overcommit/tree/master/lib/overcommit/hook
#
# For a complete list of options that you can use to customize hooks, see:
-# https://github.com/brigade/overcommit#configuration
+# https://github.com/sds/overcommit#configuration
#
# Uncomment the following lines to make the configuration take effect.
@@ -21,6 +21,7 @@
# on_warn: fail # Treat all warnings as failures
#
# TrailingWhitespace:
+# enabled: true
# exclude:
# - '**/db/structure.sql' # Ignore trailing whitespace in generated files
#
diff --git a/lib/overcommit.rb b/lib/overcommit.rb
index b97d257d..15fef94f 100644
--- a/lib/overcommit.rb
+++ b/lib/overcommit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'overcommit/os'
require 'overcommit/constants'
require 'overcommit/exceptions'
diff --git a/lib/overcommit/cli.rb b/lib/overcommit/cli.rb
index fdc3dd5b..dafc545a 100644
--- a/lib/overcommit/cli.rb
+++ b/lib/overcommit/cli.rb
@@ -1,12 +1,15 @@
+# frozen_string_literal: true
+
require 'overcommit'
require 'optparse'
module Overcommit
# Responsible for parsing command-line options and executing appropriate
# application logic based on those options.
- class CLI # rubocop:disable ClassLength
+ class CLI # rubocop:disable Metrics/ClassLength
def initialize(arguments, input, logger)
@arguments = arguments
+ @cli_options = {}
@input = input
@log = logger
@options = {}
@@ -26,9 +29,14 @@ def run
sign
when :run_all
run_all
- end
- rescue Overcommit::Exceptions::HookContextLoadError => ex
- puts ex
+ when :diff
+ diff
+ end
+ rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e
+ puts e
+ exit 78 # EX_CONFIG
+ rescue Overcommit::Exceptions::HookContextLoadError => e
+ puts e
exit 64 # EX_USAGE
end
@@ -40,15 +48,15 @@ def parse_arguments
@parser = create_option_parser
begin
- @parser.parse!(@arguments)
+ @parser.parse!(@arguments, into: @cli_options)
# Default action is to install
@options[:action] ||= :install
# Unconsumed arguments are our targets
@options[:targets] = @arguments
- rescue OptionParser::InvalidOption => ex
- print_help @parser.help, ex
+ rescue OptionParser::InvalidOption => e
+ print_help @parser.help, e
end
end
@@ -89,8 +97,14 @@ def add_installation_options(opts)
@options[:force] = true
end
- opts.on('-r', '--run', 'Run pre-commit hook against all git tracked files') do
+ opts.on('-r [hook]', '--run [hook]', 'Run specified hook against all git tracked files. Defaults to `pre_commit`.') do |arg| # rubocop:disable Layout/LineLength
@options[:action] = :run_all
+ @options[:hook_to_run] = arg ? arg.to_s : 'run-all'
+ end
+
+ opts.on('--diff [ref]', 'Run pre_commit hooks against the diff between a given ref. Defaults to `main`.') do |arg| # rubocop:disable Layout/LineLength
+ @options[:action] = :diff
+ arg
end
end
@@ -118,15 +132,13 @@ def install_or_uninstall
end
@options[:targets].each do |target|
- begin
- Installer.new(log).run(target, @options)
- rescue Overcommit::Exceptions::InvalidGitRepo => error
- log.warning "Invalid repo #{target}: #{error}"
- halt 69 # EX_UNAVAILABLE
- rescue Overcommit::Exceptions::PreExistingHooks => error
- log.warning "Unable to install into #{target}: #{error}"
- halt 73 # EX_CANTCREAT
- end
+ Installer.new(log).run(target, @options)
+ rescue Overcommit::Exceptions::InvalidGitRepo => e
+ log.warning "Invalid repo #{target}: #{e}"
+ halt 69 # EX_UNAVAILABLE
+ rescue Overcommit::Exceptions::PreExistingHooks => e
+ log.warning "Unable to install into #{target}: #{e}"
+ halt 73 # EX_CANTCREAT
end
end
@@ -194,10 +206,23 @@ def sign
def run_all
empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input
- context = Overcommit::HookContext.create('run-all', config, @arguments, empty_stdin)
+ context = Overcommit::HookContext.create(@options[:hook_to_run], config, @arguments, empty_stdin) # rubocop:disable Layout/LineLength
+ config.apply_environment!(context, ENV)
+
+ printer = Overcommit::Printer.new(config, log, context)
+ runner = Overcommit::HookRunner.new(config, log, context, printer)
+
+ status = runner.run
+
+ halt(status ? 0 : 65)
+ end
+
+ def diff
+ empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input
+ context = Overcommit::HookContext.create('diff', config, @arguments, empty_stdin, **@cli_options) # rubocop:disable Layout/LineLength
config.apply_environment!(context, ENV)
- printer = Overcommit::Printer.new(log, context)
+ printer = Overcommit::Printer.new(config, log, context)
runner = Overcommit::HookRunner.new(config, log, context, printer)
status = runner.run
diff --git a/lib/overcommit/command_splitter.rb b/lib/overcommit/command_splitter.rb
index 3b40ff34..c334b6f9 100644
--- a/lib/overcommit/command_splitter.rb
+++ b/lib/overcommit/command_splitter.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit
# Distributes a list of arguments over multiple invocations of a command.
#
@@ -105,8 +107,10 @@ def arguments_under_limit(splittable_args, start_index, byte_limit)
loop do
break if index > splittable_args.length - 1
+
total_bytes += splittable_args[index].bytesize
break if total_bytes > byte_limit # Not enough room
+
index += 1
end
diff --git a/lib/overcommit/configuration.rb b/lib/overcommit/configuration.rb
index 7354c444..72086457 100644
--- a/lib/overcommit/configuration.rb
+++ b/lib/overcommit/configuration.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'digest'
require 'json'
module Overcommit
# Stores configuration for Overcommit and the hooks it runs.
- class Configuration # rubocop:disable ClassLength
+ class Configuration # rubocop:disable Metrics/ClassLength
# Creates a configuration from the given hash.
#
# @param hash [Hash] loaded YAML config file as a hash
@@ -41,7 +43,7 @@ def concurrency
@concurrency ||=
begin
cores = Overcommit::Utils.processor_count
- content = @hash.fetch('concurrency', '%{processors}')
+ content = @hash.fetch('concurrency') { '%d' }
if content.is_a?(String)
concurrency_expr = content % { processors: cores }
@@ -112,7 +114,7 @@ def all_plugin_hook_configs
# Returns the built-in hooks that have been enabled for a hook type.
def enabled_builtin_hooks(hook_context)
@hash[hook_context.hook_class_name].keys.
- select { |hook_name| hook_name != 'ALL' }.
+ reject { |hook_name| hook_name == 'ALL' }.
select { |hook_name| built_in_hook?(hook_context, hook_name) }.
select { |hook_name| hook_enabled?(hook_context, hook_name) }
end
@@ -120,7 +122,7 @@ def enabled_builtin_hooks(hook_context)
# Returns the ad hoc hooks that have been enabled for a hook type.
def enabled_ad_hoc_hooks(hook_context)
@hash[hook_context.hook_class_name].keys.
- select { |hook_name| hook_name != 'ALL' }.
+ reject { |hook_name| hook_name == 'ALL' }.
select { |hook_name| ad_hoc_hook?(hook_context, hook_name) }.
select { |hook_name| hook_enabled?(hook_context, hook_name) }
end
@@ -154,7 +156,7 @@ def merge(config)
# environment variables.
def apply_environment!(hook_context, env)
skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']} #{env['SKIP_HOOKS']}".split(/[:, ]/)
- only_hooks = env.fetch('ONLY', '').split(/[:, ]/)
+ only_hooks = env.fetch('ONLY') { '' }.split(/[:, ]/)
hook_type = hook_context.hook_class_name
if only_hooks.any? || skipped_hooks.include?('all') || skipped_hooks.include?('ALL')
@@ -252,7 +254,7 @@ def update_signature!
private
def ad_hoc_hook?(hook_context, hook_name)
- ad_hoc_conf = @hash.fetch(hook_context.hook_class_name, {}).fetch(hook_name, {})
+ ad_hoc_conf = @hash.fetch(hook_context.hook_class_name) { {} }.fetch(hook_name) { {} }
# Ad hoc hooks are neither built-in nor have a plugin file written but
# still have a `command` specified to be run
@@ -282,7 +284,7 @@ def hook_enabled?(hook_context_or_type, hook_name)
hook_context_or_type.hook_class_name
end
- individual_enabled = @hash[hook_type].fetch(hook_name, {})['enabled']
+ individual_enabled = @hash[hook_type].fetch(hook_name) { {} }['enabled']
return individual_enabled unless individual_enabled.nil?
all_enabled = @hash[hook_type]['ALL']['enabled']
diff --git a/lib/overcommit/configuration_loader.rb b/lib/overcommit/configuration_loader.rb
index a785b525..8fd68a01 100644
--- a/lib/overcommit/configuration_loader.rb
+++ b/lib/overcommit/configuration_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'yaml'
module Overcommit
@@ -10,7 +12,7 @@ class << self
#
# @return [Overcommit::Configuration]
def default_configuration
- @default_config ||= load_from_file(DEFAULT_CONFIG_PATH, default: true, verify: false)
+ @default_configuration ||= load_from_file(DEFAULT_CONFIG_PATH, default: true, verify: false)
end
# Loads configuration from file.
@@ -22,13 +24,16 @@ def default_configuration
# @option logger [Overcommit::Logger]
# @return [Overcommit::Configuration]
def load_from_file(file, options = {})
- hash =
- if yaml = YAML.load_file(file)
- yaml.to_hash
- else
- {}
+ # Psych 4 introduced breaking behavior that doesn't support aliases by
+ # default. Explicitly enable aliases if the option is available.
+ yaml =
+ begin
+ YAML.load_file(file, aliases: true)
+ rescue ArgumentError
+ YAML.load_file(file)
end
+ hash = yaml ? yaml.to_hash : {}
Overcommit::Configuration.new(hash, options)
end
end
@@ -48,10 +53,14 @@ def initialize(logger, options = {})
#
# @return [Overcommit::Configuration]
def load_repo_config
+ overcommit_local_yml = File.join(Overcommit::Utils.repo_root,
+ Overcommit::LOCAL_CONFIG_FILE_NAME)
overcommit_yml = File.join(Overcommit::Utils.repo_root,
Overcommit::CONFIG_FILE_NAME)
- if File.exist?(overcommit_yml)
+ if File.exist?(overcommit_local_yml) && File.exist?(overcommit_yml)
+ load_file(overcommit_yml, overcommit_local_yml)
+ elsif File.exist?(overcommit_yml)
load_file(overcommit_yml)
else
self.class.default_configuration
@@ -59,19 +68,23 @@ def load_repo_config
end
# Loads a configuration, ensuring it extends the default configuration.
- def load_file(file)
- config = self.class.load_from_file(file, default: false, logger: @log)
- config = self.class.default_configuration.merge(config)
+ def load_file(file, local_file = nil)
+ overcommit_config = self.class.load_from_file(file, default: false, logger: @log)
+ l_config = self.class.load_from_file(local_file, default: false, logger: @log) if local_file
+ config = self.class.default_configuration.merge(overcommit_config)
+ config = config.merge(l_config) if l_config
- if @options.fetch(:verify, config.verify_signatures?)
+ if @options.fetch(:verify) { config.verify_signatures? }
verify_signatures(config)
end
config
- rescue => error
+ rescue Overcommit::Exceptions::ConfigurationSignatureChanged
+ raise
+ rescue StandardError => e
raise Overcommit::Exceptions::ConfigurationError,
- "Unable to load configuration from '#{file}': #{error}",
- error.backtrace
+ "Unable to load configuration from '#{file}': #{e}",
+ e.backtrace
end
private
diff --git a/lib/overcommit/configuration_validator.rb b/lib/overcommit/configuration_validator.rb
index 3b43a72e..ce1eb317 100644
--- a/lib/overcommit/configuration_validator.rb
+++ b/lib/overcommit/configuration_validator.rb
@@ -1,3 +1,6 @@
+# frozen_string_literal: true
+
+# rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength
module Overcommit
# Validates and normalizes a configuration.
class ConfigurationValidator
@@ -16,6 +19,7 @@ def validate(config, hash, options)
hash = convert_nils_to_empty_hashes(hash)
ensure_hook_type_sections_exist(hash)
check_hook_name_format(hash)
+ check_hook_env(hash)
check_for_missing_enabled_option(hash) unless @options[:default]
check_for_too_many_processors(config, hash)
check_for_verify_plugin_signatures_option(hash)
@@ -51,16 +55,54 @@ def convert_nils_to_empty_hashes(hash)
end
end
+ def check_hook_env(hash)
+ errors = []
+
+ Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
+ hash.fetch(hook_type) { {} }.each do |hook_name, hook_config|
+ hook_env = hook_config.fetch('env') { {} }
+
+ unless hook_env.is_a?(Hash)
+ errors << "#{hook_type}::#{hook_name} has an invalid `env` specified: " \
+ 'must be a hash of environment variable name to string value.'
+ next
+ end
+
+ hook_env.each do |var_name, var_value|
+ if var_name.include?('=')
+ errors << "#{hook_type}::#{hook_name} has an invalid `env` specified: " \
+ "variable name `#{var_name}` cannot contain `=`."
+ end
+
+ unless var_value.nil? || var_value.is_a?(String)
+ errors << "#{hook_type}::#{hook_name} has an invalid `env` specified: " \
+ "value of `#{var_name}` must be a string or `nil`, but was " \
+ "#{var_value.inspect} (#{var_value.class})"
+ end
+ end
+ end
+ end
+
+ if errors.any?
+ if @log
+ @log.error errors.join("\n")
+ @log.newline
+ end
+ raise Overcommit::Exceptions::ConfigurationError,
+ 'One or more hooks had an invalid `env` configuration option'
+ end
+ end
+
# Prints an error message and raises an exception if a hook has an
# invalid name, since this can result in strange errors elsewhere.
def check_hook_name_format(hash)
errors = []
Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
- hash.fetch(hook_type, {}).each do |hook_name, _|
+ hash.fetch(hook_type) { {} }.each_key do |hook_name|
next if hook_name == 'ALL'
- unless hook_name =~ /\A[A-Za-z0-9]+\z/
+ unless hook_name.match?(/\A[A-Za-z0-9]+\z/)
errors << "#{hook_type}::#{hook_name} has an invalid name " \
"#{hook_name}. It must contain only alphanumeric " \
'characters (no underscores or dashes, etc.)'
@@ -69,8 +111,10 @@ def check_hook_name_format(hash)
end
if errors.any?
- @log.error errors.join("\n") if @log
- @log.newline if @log
+ if @log
+ @log.error errors.join("\n")
+ @log.newline
+ end
raise Overcommit::Exceptions::ConfigurationError,
'One or more hooks had invalid names'
end
@@ -84,7 +128,7 @@ def check_for_missing_enabled_option(hash)
any_warnings = false
Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
- hash.fetch(hook_type, {}).each do |hook_name, hook_config|
+ hash.fetch(hook_type) { {} }.each do |hook_name, hook_config|
next if hook_name == 'ALL'
if hook_config['enabled'].nil?
@@ -105,8 +149,8 @@ def check_for_too_many_processors(config, hash)
errors = []
Overcommit::Utils.supported_hook_type_classes.each do |hook_type|
- hash.fetch(hook_type, {}).each do |hook_name, hook_config|
- processors = hook_config.fetch('processors', 1)
+ hash.fetch(hook_type) { {} }.each do |hook_name, hook_config|
+ processors = hook_config.fetch('processors') { 1 }
if processors > concurrency
errors << "#{hook_type}::#{hook_name} `processors` value " \
"(#{processors}) is larger than the global `concurrency` " \
@@ -116,8 +160,10 @@ def check_for_too_many_processors(config, hash)
end
if errors.any?
- @log.error errors.join("\n") if @log
- @log.newline if @log
+ if @log
+ @log.error errors.join("\n")
+ @log.newline
+ end
raise Overcommit::Exceptions::ConfigurationError,
'One or more hooks had invalid `processor` value configured'
end
@@ -137,3 +183,4 @@ def check_for_verify_plugin_signatures_option(hash)
end
end
end
+# rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength
diff --git a/lib/overcommit/constants.rb b/lib/overcommit/constants.rb
index ff2c9311..e7c19728 100644
--- a/lib/overcommit/constants.rb
+++ b/lib/overcommit/constants.rb
@@ -3,10 +3,11 @@
# Global application constants.
module Overcommit
HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze
- CONFIG_FILE_NAME = '.overcommit.yml'.freeze
+ CONFIG_FILE_NAME = '.overcommit.yml'
+ LOCAL_CONFIG_FILE_NAME = '.local-overcommit.yml'
HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook').freeze
- REPO_URL = 'https://github.com/brigade/overcommit'.freeze
- BUG_REPORT_URL = "#{REPO_URL}/issues".freeze
+ REPO_URL = 'https://github.com/sds/overcommit'
+ BUG_REPORT_URL = "#{REPO_URL}/issues"
end
diff --git a/lib/overcommit/exceptions.rb b/lib/overcommit/exceptions.rb
index dda9f8f0..050982fb 100644
--- a/lib/overcommit/exceptions.rb
+++ b/lib/overcommit/exceptions.rb
@@ -1,47 +1,55 @@
+# frozen_string_literal: true
+
module Overcommit::Exceptions
+ # Base error class.
+ class Error < StandardError; end
+
# Raised when a {Configuration} could not be loaded from a file.
- class ConfigurationError < StandardError; end
+ class ConfigurationError < Error; end
# Raised when the Overcommit configuration file signature has changed.
- class ConfigurationSignatureChanged < StandardError; end
+ class ConfigurationSignatureChanged < Error; end
# Raised when trying to read/write to/from the local repo git config fails.
- class GitConfigError < StandardError; end
+ class GitConfigError < Error; end
# Raised when there was a problem reading submodule information for a repo.
- class GitSubmoduleError < StandardError; end
+ class GitSubmoduleError < Error; end
# Raised when there was a problem reading git revision information with `rev-list`.
- class GitRevListError < StandardError; end
+ class GitRevListError < Error; end
# Raised when a {HookContext} is unable to setup the environment before a run.
- class HookSetupFailed < StandardError; end
+ class HookSetupFailed < Error; end
# Raised when a {HookContext} is unable to clean the environment after a run.
- class HookCleanupFailed < StandardError; end
+ class HookCleanupFailed < Error; end
# Raised when a hook run was cancelled by the user.
- class HookCancelled < StandardError; end
+ class HookCancelled < Error; end
# Raised when a hook could not be loaded by a {HookRunner}.
- class HookLoadError < StandardError; end
+ class HookLoadError < Error; end
# Raised when a {HookRunner} could not be loaded.
- class HookContextLoadError < StandardError; end
+ class HookContextLoadError < Error; end
# Raised when a pipe character is used in the `execute` helper, as this was
# likely used in error.
- class InvalidCommandArgs < StandardError; end
+ class InvalidCommandArgs < Error; end
# Raised when an installation target is not a valid git repository.
- class InvalidGitRepo < StandardError; end
+ class InvalidGitRepo < Error; end
# Raised when a hook was defined incorrectly.
- class InvalidHookDefinition < StandardError; end
+ class InvalidHookDefinition < Error; end
# Raised when one or more hook plugin signatures have changed.
- class InvalidHookSignature < StandardError; end
+ class InvalidHookSignature < Error; end
+
+ # Raised when there is a problem processing output into {Hook::Messages}s.
+ class MessageProcessingError < Error; end
# Raised when an installation target already contains non-Overcommit hooks.
- class PreExistingHooks < StandardError; end
+ class PreExistingHooks < Error; end
end
diff --git a/lib/overcommit/git_config.rb b/lib/overcommit/git_config.rb
index d3b85ec8..c1243861 100644
--- a/lib/overcommit/git_config.rb
+++ b/lib/overcommit/git_config.rb
@@ -1,3 +1,7 @@
+# frozen_string_literal: true
+
+require 'overcommit/utils'
+
module Overcommit
# Get configuration options from git
module GitConfig
@@ -8,5 +12,12 @@ def comment_character
char = '#' if char == ''
char
end
+
+ def hooks_path
+ path = `git config --get core.hooksPath`.chomp
+ return File.join(Overcommit::Utils.git_dir, 'hooks') if path.empty?
+
+ File.expand_path(path, Dir.pwd)
+ end
end
end
diff --git a/lib/overcommit/git_repo.rb b/lib/overcommit/git_repo.rb
index 8f8f4237..1af73e81 100644
--- a/lib/overcommit/git_repo.rb
+++ b/lib/overcommit/git_repo.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require 'iniparse'
+require 'shellwords'
module Overcommit
# Provide a set of utilities for certain interactions with `git`.
@@ -11,7 +14,7 @@ module GitRepo
[^\s]+\s # Ignore old file range
\+(\d+)(?:,(\d+))? # Extract range of hunk containing start line and number of lines
\s@@.*$
- /x
+ /x.freeze
# Regular expression used to extract information from lines of
# `git submodule status` output
@@ -19,7 +22,7 @@ module GitRepo
^\s*(?[-+U]?)(?\w+)
\s(?[^\s]+?)
(?:\s\((?.+)\))?$
- /x
+ /x.freeze
# Struct encapsulating submodule information extracted from the
# output of `git submodule status`
@@ -69,7 +72,7 @@ def extract_modified_lines(file_path, options)
refs = options[:refs]
subcmd = options[:subcmd] || 'diff'
- `git #{subcmd} --no-ext-diff -U0 #{flags} #{refs} -- "#{file_path}"`.
+ `git #{subcmd} --no-color --no-ext-diff -U0 #{flags} #{refs} -- "#{file_path}"`.
scan(DIFF_HUNK_REGEX) do |start_line, lines_added|
lines_added = (lines_added || 1).to_i # When blank, one line was added
cur_line = start_line.to_i
@@ -106,8 +109,16 @@ def modified_files(options)
# @return [Array] list of absolute file paths
def list_files(paths = [], options = {})
ref = options[:ref] || 'HEAD'
- `git ls-tree --name-only #{ref} "#{paths.join('" "')}"`.
- split(/\n/).
+
+ result = Overcommit::Utils.execute(%W[git ls-tree --name-only #{ref}], args: paths)
+ unless result.success?
+ raise Overcommit::Exceptions::Error,
+ "Error listing files. EXIT STATUS(es): #{result.statuses}.\n" \
+ "STDOUT(s): #{result.stdouts}.\n" \
+ "STDERR(s): #{result.stderrs}."
+ end
+
+ result.stdout.split(/\n/).
map { |relative_file| File.expand_path(relative_file) }.
reject { |file| File.directory?(file) } # Exclude submodule directories
end
@@ -235,7 +246,7 @@ def submodules(options = {})
ref = options[:ref]
modules = []
- IniParse.parse(`git show #{ref}:.gitmodules`).each do |section|
+ IniParse.parse(`git show #{ref}:.gitmodules 2> #{File::NULL}`).each do |section|
# git < 1.8.5 does not update the .gitmodules file with submodule
# changes, so when we are looking at the current state of the work tree,
# we need to check if the submodule actually exists via another method,
@@ -251,9 +262,9 @@ def submodules(options = {})
end
modules
- rescue IniParse::IniParseError => ex
+ rescue IniParse::IniParseError => e
raise Overcommit::Exceptions::GitSubmoduleError,
- "Unable to read submodule information from #{ref}:.gitmodules file: #{ex.message}"
+ "Unable to read submodule information from #{ref}:.gitmodules file: #{e.message}"
end
# Returns the names of all branches containing the given commit.
@@ -266,5 +277,11 @@ def branches_containing_commit(commit_ref)
split(/\s+/).
reject { |s| s.empty? || s == '*' }
end
+
+ # Returns the name of the currently checked out branch.
+ # @return [String]
+ def current_branch
+ `git symbolic-ref --short -q HEAD`.chomp
+ end
end
end
diff --git a/lib/overcommit/git_version.rb b/lib/overcommit/git_version.rb
index ab5021ac..ac4044c3 100644
--- a/lib/overcommit/git_version.rb
+++ b/lib/overcommit/git_version.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
# Returns the version of the available git binary.
#
# This is intended to be used to conveniently execute code based on a specific
diff --git a/lib/overcommit/hook/base.rb b/lib/overcommit/hook/base.rb
index 3083b633..3c2eaeb3 100644
--- a/lib/overcommit/hook/base.rb
+++ b/lib/overcommit/hook/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
require 'overcommit/message_processor'
@@ -17,7 +19,7 @@ def to_s
class Base # rubocop:disable Metrics/ClassLength
extend Forwardable
- def_delegators :@context, :modified_files
+ def_delegators :@context, :all_files, :modified_files
attr_reader :config
# @param config [Overcommit::Configuration]
@@ -42,7 +44,7 @@ def run_and_transform
if output = check_for_requirements
status = :fail
else
- result = Overcommit::Utils.with_environment(@config.fetch('env', {})) { run }
+ result = Overcommit::Utils.with_environment(@config.fetch('env') { {} }) { run }
status, output = process_hook_return_value(result)
end
@@ -54,7 +56,7 @@ def name
end
def description
- @config['description'] || "Running #{name}"
+ @config['description'] || "Run #{name}"
end
def required?
@@ -66,7 +68,7 @@ def parallelize?
end
def processors
- @config.fetch('processors', 1)
+ @config.fetch('processors') { 1 }
end
def quiet?
@@ -77,12 +79,18 @@ def enabled?
@config['enabled'] != false
end
+ def excluded?
+ exclude_branches.any? { |p| File.fnmatch(p, current_branch) }
+ end
+
def skip?
- @config['skip']
+ @config['skip'] ||
+ (@config['skip_if'] ? execute(@config['skip_if']).success? : false)
end
def run?
enabled? &&
+ !excluded? &&
!(@config['requires_files'] && applicable_files.empty?)
end
@@ -155,11 +163,21 @@ def flags
# Gets a list of staged files that apply to this hook based on its
# configured `include` and `exclude` lists.
def applicable_files
- @applicable_files ||= modified_files.select { |file| applicable_file?(file) }.sort
+ @applicable_files ||= select_applicable(modified_files)
+ end
+
+ # Gets a list of all files that apply to this hook based on its
+ # configured `include` and `exclude` lists.
+ def included_files
+ @included_files ||= select_applicable(all_files)
end
private
+ def select_applicable(list)
+ list.select { |file| applicable_file?(file) }.sort
+ end
+
def applicable_file?(file)
includes = Array(@config['include']).flatten.map do |glob|
Overcommit::Utils.convert_glob_to_absolute(glob)
@@ -192,11 +210,8 @@ def check_for_requirements
def check_for_executable
return unless required_executable && !in_path?(required_executable)
- output = "'#{required_executable}' is not installed, not in your PATH, " \
- 'or does not have execute permissions'
- output << install_command_prompt
-
- output
+ "'#{required_executable}' is not installed, not in your PATH, " \
+ "or does not have execute permissions#{install_command_prompt}"
end
def install_command_prompt
@@ -213,14 +228,12 @@ def check_for_libraries
output = []
required_libraries.each do |library|
- begin
- require library
- rescue LoadError
- install_command = @config['install_command']
- install_command = " -- install via #{install_command}" if install_command
-
- output << "Unable to load '#{library}'#{install_command}"
- end
+ require library
+ rescue LoadError
+ install_command = @config['install_command']
+ install_command = " -- install via #{install_command}" if install_command
+
+ output << "Unable to load '#{library}'#{install_command}"
end
return if output.empty?
@@ -260,12 +273,20 @@ def process_hook_return_value(hook_return_value)
def transform_status(status)
case status
when :fail
- @config.fetch('on_fail', :fail).to_sym
+ @config.fetch('on_fail') { :fail }.to_sym
when :warn
- @config.fetch('on_warn', :warn).to_sym
+ @config.fetch('on_warn') { :warn }.to_sym
else
status
end
end
+
+ def exclude_branches
+ @config['exclude_branches'] || []
+ end
+
+ def current_branch
+ @current_branch ||= Overcommit::GitRepo.current_branch
+ end
end
end
diff --git a/lib/overcommit/hook/commit_msg/base.rb b/lib/overcommit/hook/commit_msg/base.rb
index 24241640..f99f53fc 100644
--- a/lib/overcommit/hook/commit_msg/base.rb
+++ b/lib/overcommit/hook/commit_msg/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
module Overcommit::Hook::CommitMsg
@@ -7,6 +9,6 @@ class Base < Overcommit::Hook::Base
def_delegators :@context, :empty_message?, :commit_message,
:update_commit_message, :commit_message_lines,
- :commit_message_file
+ :commit_message_file, :modified_lines_in_file
end
end
diff --git a/lib/overcommit/hook/commit_msg/capitalized_subject.rb b/lib/overcommit/hook/commit_msg/capitalized_subject.rb
index 62b200a7..1b783bc1 100644
--- a/lib/overcommit/hook/commit_msg/capitalized_subject.rb
+++ b/lib/overcommit/hook/commit_msg/capitalized_subject.rb
@@ -1,15 +1,25 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Ensures commit message subject lines start with a capital letter.
class CapitalizedSubject < Base
def run
return :pass if empty_message?
- first_letter = commit_message_lines[0].to_s.match(/^[[:punct:]]*(.)/)[1]
- unless first_letter =~ /[[:upper:]]/
+ # Git treats the first non-empty line as the subject
+ subject = commit_message_lines.find { |line| !line.strip.empty? }.to_s
+ first_letter = subject.match(/^[[:punct:]]*(.)/)[1]
+ unless special_prefix?(subject) || first_letter =~ /[[:upper:]]/
return :warn, 'Subject should start with a capital letter'
end
:pass
end
+
+ private
+
+ def special_prefix?(subject)
+ subject =~ /^(fixup|squash)!/
+ end
end
end
diff --git a/lib/overcommit/hook/commit_msg/empty_message.rb b/lib/overcommit/hook/commit_msg/empty_message.rb
index b0cd0310..a0f3bfc7 100644
--- a/lib/overcommit/hook/commit_msg/empty_message.rb
+++ b/lib/overcommit/hook/commit_msg/empty_message.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Checks that the commit message is not empty
class EmptyMessage < Base
diff --git a/lib/overcommit/hook/commit_msg/gerrit_change_id.rb b/lib/overcommit/hook/commit_msg/gerrit_change_id.rb
index 567b6c0b..f8514849 100644
--- a/lib/overcommit/hook/commit_msg/gerrit_change_id.rb
+++ b/lib/overcommit/hook/commit_msg/gerrit_change_id.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Ensures a Gerrit Change-Id line is included in the commit message.
#
diff --git a/lib/overcommit/hook/commit_msg/hard_tabs.rb b/lib/overcommit/hook/commit_msg/hard_tabs.rb
index dcd0356e..e8ff5aeb 100644
--- a/lib/overcommit/hook/commit_msg/hard_tabs.rb
+++ b/lib/overcommit/hook/commit_msg/hard_tabs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Checks for hard tabs in commit messages.
class HardTabs < Base
diff --git a/lib/overcommit/hook/commit_msg/message_format.rb b/lib/overcommit/hook/commit_msg/message_format.rb
new file mode 100644
index 00000000..f5093c24
--- /dev/null
+++ b/lib/overcommit/hook/commit_msg/message_format.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::CommitMsg
+ # Ensures the commit message follows a specific format.
+ class MessageFormat < Base
+ def run
+ error_msg = validate_pattern(commit_message_lines.join("\n"))
+ return :fail, error_msg if error_msg
+
+ :pass
+ end
+
+ private
+
+ def validate_pattern(message)
+ pattern = config['pattern']
+ return if pattern.empty?
+
+ expected_pattern_message = config['expected_pattern_message']
+ sample_message = config['sample_message']
+
+ unless message.match?(/#{pattern}/m)
+ [
+ 'Commit message pattern mismatch.',
+ "Expected : #{expected_pattern_message}",
+ "Sample : #{sample_message}"
+ ].join("\n")
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/commit_msg/russian_novel.rb b/lib/overcommit/hook/commit_msg/russian_novel.rb
index 35c89402..26ec7ff5 100644
--- a/lib/overcommit/hook/commit_msg/russian_novel.rb
+++ b/lib/overcommit/hook/commit_msg/russian_novel.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Checks for long commit messages (not good or bad--just fun to point out)
class RussianNovel < Base
diff --git a/lib/overcommit/hook/commit_msg/single_line_subject.rb b/lib/overcommit/hook/commit_msg/single_line_subject.rb
index 8e939536..e374d541 100644
--- a/lib/overcommit/hook/commit_msg/single_line_subject.rb
+++ b/lib/overcommit/hook/commit_msg/single_line_subject.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Ensures commit message subject lines are followed by a blank line.
class SingleLineSubject < Base
diff --git a/lib/overcommit/hook/commit_msg/spell_check.rb b/lib/overcommit/hook/commit_msg/spell_check.rb
index e021c766..ec6c4d4a 100644
--- a/lib/overcommit/hook/commit_msg/spell_check.rb
+++ b/lib/overcommit/hook/commit_msg/spell_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'tempfile'
module Overcommit::Hook::CommitMsg
@@ -7,7 +9,7 @@ module Overcommit::Hook::CommitMsg
class SpellCheck < Base
Misspelling = Struct.new(:word, :suggestions)
- MISSPELLING_REGEX = /^[]\s(?\w+)(?:.+?:\s(?.*))?/
+ MISSPELLING_REGEX = /^[]\s(?\w+)(?:.+?:\s(?.*))?/.freeze
def run
result = execute(command + [uncommented_commit_msg_file])
diff --git a/lib/overcommit/hook/commit_msg/text_width.rb b/lib/overcommit/hook/commit_msg/text_width.rb
index 46de2b87..52de3bd7 100644
--- a/lib/overcommit/hook/commit_msg/text_width.rb
+++ b/lib/overcommit/hook/commit_msg/text_width.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Ensures the number of columns the subject and commit message lines occupy is
# under the preferred limits.
@@ -18,10 +20,20 @@ def run
private
def find_errors_in_subject(subject)
- max_subject_width = config['max_subject_width']
- return unless subject.length > max_subject_width
+ max_subject_width =
+ config['max_subject_width'] +
+ special_prefix_length(subject)
+
+ if subject.length > max_subject_width
+ @errors << "Commit message subject must be <= #{max_subject_width} characters"
+ return
+ end
- @errors << "Please keep the subject <= #{max_subject_width} characters"
+ min_subject_width = config['min_subject_width']
+ if subject.length < min_subject_width
+ @errors << "Commit message subject must be >= #{min_subject_width} characters"
+ nil
+ end
end
def find_errors_in_body(lines)
@@ -29,12 +41,16 @@ def find_errors_in_body(lines)
max_body_width = config['max_body_width']
- lines[2..-1].each_with_index do |line, index|
+ lines[2..].each_with_index do |line, index|
if line.chomp.size > max_body_width
@errors << "Line #{index + 3} of commit message has > " \
"#{max_body_width} characters"
end
end
end
+
+ def special_prefix_length(subject)
+ subject.match(/^(fixup|squash)! /) { |match| match[0].length } || 0
+ end
end
end
diff --git a/lib/overcommit/hook/commit_msg/trailing_period.rb b/lib/overcommit/hook/commit_msg/trailing_period.rb
index ea8725b8..d3c8b276 100644
--- a/lib/overcommit/hook/commit_msg/trailing_period.rb
+++ b/lib/overcommit/hook/commit_msg/trailing_period.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::CommitMsg
# Ensures commit message subject lines do not have a trailing period
class TrailingPeriod < Base
diff --git a/lib/overcommit/hook/post_checkout/base.rb b/lib/overcommit/hook/post_checkout/base.rb
index f83951bb..78798b9e 100644
--- a/lib/overcommit/hook/post_checkout/base.rb
+++ b/lib/overcommit/hook/post_checkout/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
module Overcommit::Hook::PostCheckout
@@ -7,5 +9,15 @@ class Base < Overcommit::Hook::Base
def_delegators :@context,
:previous_head, :new_head, :branch_checkout?, :file_checkout?
+
+ def skip_file_checkout?
+ @config['skip_file_checkout'] != false
+ end
+
+ def enabled?
+ return false if file_checkout? && skip_file_checkout?
+
+ super
+ end
end
end
diff --git a/lib/overcommit/hook/post_checkout/bower_install.rb b/lib/overcommit/hook/post_checkout/bower_install.rb
index cc93e074..9280baea 100644
--- a/lib/overcommit/hook/post_checkout/bower_install.rb
+++ b/lib/overcommit/hook/post_checkout/bower_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bower_install'
module Overcommit::Hook::PostCheckout
# Runs `bower install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BowerInstall}
+ # @see Overcommit::Hook::Shared::BowerInstall
class BowerInstall < Base
include Overcommit::Hook::Shared::BowerInstall
end
diff --git a/lib/overcommit/hook/post_checkout/bundle_install.rb b/lib/overcommit/hook/post_checkout/bundle_install.rb
index f6cfde78..2641ded6 100644
--- a/lib/overcommit/hook/post_checkout/bundle_install.rb
+++ b/lib/overcommit/hook/post_checkout/bundle_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bundle_install'
module Overcommit::Hook::PostCheckout
# Runs `bundle install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BundleInstall}
+ # @see Overcommit::Hook::Shared::BundleInstall
class BundleInstall < Base
include Overcommit::Hook::Shared::BundleInstall
end
diff --git a/lib/overcommit/hook/post_checkout/composer_install.rb b/lib/overcommit/hook/post_checkout/composer_install.rb
new file mode 100644
index 00000000..d5b9abed
--- /dev/null
+++ b/lib/overcommit/hook/post_checkout/composer_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/composer_install'
+
+module Overcommit::Hook::PostCheckout
+ # Runs `composer install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::ComposerInstall
+ class ComposerInstall < Base
+ include Overcommit::Hook::Shared::ComposerInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_checkout/index_tags.rb b/lib/overcommit/hook/post_checkout/index_tags.rb
index 5d7784b7..eb3456ba 100644
--- a/lib/overcommit/hook/post_checkout/index_tags.rb
+++ b/lib/overcommit/hook/post_checkout/index_tags.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/index_tags'
module Overcommit::Hook::PostCheckout
# Updates ctags index for all source code in the repository.
#
- # @see {Overcommit::Hook::Shared::IndexTags}
+ # @see Overcommit::Hook::Shared::IndexTags
class IndexTags < Base
include Overcommit::Hook::Shared::IndexTags
end
diff --git a/lib/overcommit/hook/post_checkout/npm_install.rb b/lib/overcommit/hook/post_checkout/npm_install.rb
index e726469f..66b1d425 100644
--- a/lib/overcommit/hook/post_checkout/npm_install.rb
+++ b/lib/overcommit/hook/post_checkout/npm_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/npm_install'
module Overcommit::Hook::PostCheckout
# Runs `npm install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::NpmInstall}
+ # @see Overcommit::Hook::Shared::NpmInstall
class NpmInstall < Base
include Overcommit::Hook::Shared::NpmInstall
end
diff --git a/lib/overcommit/hook/post_checkout/submodule_status.rb b/lib/overcommit/hook/post_checkout/submodule_status.rb
index 59f16a73..139301fb 100644
--- a/lib/overcommit/hook/post_checkout/submodule_status.rb
+++ b/lib/overcommit/hook/post_checkout/submodule_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/submodule_status'
module Overcommit::Hook::PostCheckout
diff --git a/lib/overcommit/hook/post_checkout/yarn_install.rb b/lib/overcommit/hook/post_checkout/yarn_install.rb
new file mode 100644
index 00000000..94a59e63
--- /dev/null
+++ b/lib/overcommit/hook/post_checkout/yarn_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/yarn_install'
+
+module Overcommit::Hook::PostCheckout
+ # Runs `yarn install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::YarnInstall
+ class YarnInstall < Base
+ include Overcommit::Hook::Shared::YarnInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_commit/base.rb b/lib/overcommit/hook/post_commit/base.rb
index 3d22c91c..9acd77d3 100644
--- a/lib/overcommit/hook/post_commit/base.rb
+++ b/lib/overcommit/hook/post_commit/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
module Overcommit::Hook::PostCommit
diff --git a/lib/overcommit/hook/post_commit/bower_install.rb b/lib/overcommit/hook/post_commit/bower_install.rb
index 72cc1cbc..e35ae231 100644
--- a/lib/overcommit/hook/post_commit/bower_install.rb
+++ b/lib/overcommit/hook/post_commit/bower_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bower_install'
module Overcommit::Hook::PostCommit
# Runs `bower install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BowerInstall}
+ # @see Overcommit::Hook::Shared::BowerInstall
class BowerInstall < Base
include Overcommit::Hook::Shared::BowerInstall
end
diff --git a/lib/overcommit/hook/post_commit/bundle_install.rb b/lib/overcommit/hook/post_commit/bundle_install.rb
index 3505023e..1c4abb32 100644
--- a/lib/overcommit/hook/post_commit/bundle_install.rb
+++ b/lib/overcommit/hook/post_commit/bundle_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bundle_install'
module Overcommit::Hook::PostCommit
# Runs `bundle install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BundleInstall}
+ # @see Overcommit::Hook::Shared::BundleInstall
class BundleInstall < Base
include Overcommit::Hook::Shared::BundleInstall
end
diff --git a/lib/overcommit/hook/post_commit/commitplease.rb b/lib/overcommit/hook/post_commit/commitplease.rb
new file mode 100644
index 00000000..3674dc12
--- /dev/null
+++ b/lib/overcommit/hook/post_commit/commitplease.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PostCommit
+ # Check that a commit message conforms to a certain style
+ #
+ # @see https://www.npmjs.com/package/commitplease
+ class Commitplease < Base
+ def run
+ result = execute(command)
+ output = result.stderr
+ return :pass if result.success? && output.empty?
+
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/post_commit/composer_install.rb b/lib/overcommit/hook/post_commit/composer_install.rb
new file mode 100644
index 00000000..adbdc33a
--- /dev/null
+++ b/lib/overcommit/hook/post_commit/composer_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/composer_install'
+
+module Overcommit::Hook::PostCommit
+ # Runs `composer install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::ComposerInstall
+ class ComposerInstall < Base
+ include Overcommit::Hook::Shared::ComposerInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_commit/git_guilt.rb b/lib/overcommit/hook/post_commit/git_guilt.rb
index 874f925c..b4f1ead8 100644
--- a/lib/overcommit/hook/post_commit/git_guilt.rb
+++ b/lib/overcommit/hook/post_commit/git_guilt.rb
@@ -1,14 +1,17 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PostCommit
# Calculates the change in blame since the last revision.
#
# @see https://www.npmjs.com/package/git-guilt
class GitGuilt < Base
- PLUS_MINUS_REGEX = /^(.*?)(?:(\++)|(-+))$/
+ PLUS_MINUS_REGEX = /^(.*?)(?:(\++)|(-+))$/.freeze
GREEN = 32
RED = 31
def run
return :pass if initial_commit?
+
result = execute(command)
return :fail, result.stderr unless result.success?
diff --git a/lib/overcommit/hook/post_commit/index_tags.rb b/lib/overcommit/hook/post_commit/index_tags.rb
index 802411cd..175bbf6f 100644
--- a/lib/overcommit/hook/post_commit/index_tags.rb
+++ b/lib/overcommit/hook/post_commit/index_tags.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/index_tags'
module Overcommit::Hook::PostCommit
# Updates ctags index for all source code in the repository.
#
- # @see {Overcommit::Hook::Shared::IndexTags}
+ # @see Overcommit::Hook::Shared::IndexTags
class IndexTags < Base
include Overcommit::Hook::Shared::IndexTags
end
diff --git a/lib/overcommit/hook/post_commit/npm_install.rb b/lib/overcommit/hook/post_commit/npm_install.rb
index 9fd61461..df2f5af3 100644
--- a/lib/overcommit/hook/post_commit/npm_install.rb
+++ b/lib/overcommit/hook/post_commit/npm_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/npm_install'
module Overcommit::Hook::PostCommit
# Runs `npm install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::NpmInstall}
+ # @see Overcommit::Hook::Shared::NpmInstall
class NpmInstall < Base
include Overcommit::Hook::Shared::NpmInstall
end
diff --git a/lib/overcommit/hook/post_commit/submodule_status.rb b/lib/overcommit/hook/post_commit/submodule_status.rb
index 5921a19f..7cb313dd 100644
--- a/lib/overcommit/hook/post_commit/submodule_status.rb
+++ b/lib/overcommit/hook/post_commit/submodule_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/submodule_status'
module Overcommit::Hook::PostCommit
diff --git a/lib/overcommit/hook/post_commit/yarn_install.rb b/lib/overcommit/hook/post_commit/yarn_install.rb
new file mode 100644
index 00000000..69ae7670
--- /dev/null
+++ b/lib/overcommit/hook/post_commit/yarn_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/yarn_install'
+
+module Overcommit::Hook::PostCommit
+ # Runs `yarn install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::YarnInstall
+ class YarnInstall < Base
+ include Overcommit::Hook::Shared::YarnInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_merge/base.rb b/lib/overcommit/hook/post_merge/base.rb
index 32cc2638..e4d3620f 100644
--- a/lib/overcommit/hook/post_merge/base.rb
+++ b/lib/overcommit/hook/post_merge/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
module Overcommit::Hook::PostMerge
diff --git a/lib/overcommit/hook/post_merge/bower_install.rb b/lib/overcommit/hook/post_merge/bower_install.rb
index 07d9b601..982e8152 100644
--- a/lib/overcommit/hook/post_merge/bower_install.rb
+++ b/lib/overcommit/hook/post_merge/bower_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bower_install'
module Overcommit::Hook::PostMerge
# Runs `bower install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BowerInstall}
+ # @see Overcommit::Hook::Shared::BowerInstall
class BowerInstall < Base
include Overcommit::Hook::Shared::BowerInstall
end
diff --git a/lib/overcommit/hook/post_merge/bundle_install.rb b/lib/overcommit/hook/post_merge/bundle_install.rb
index 5557428b..b9b147bd 100644
--- a/lib/overcommit/hook/post_merge/bundle_install.rb
+++ b/lib/overcommit/hook/post_merge/bundle_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bundle_install'
module Overcommit::Hook::PostMerge
# Runs `bundle install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BundleInstall}
+ # @see Overcommit::Hook::Shared::BundleInstall
class BundleInstall < Base
include Overcommit::Hook::Shared::BundleInstall
end
diff --git a/lib/overcommit/hook/post_merge/composer_install.rb b/lib/overcommit/hook/post_merge/composer_install.rb
new file mode 100644
index 00000000..80d8fb94
--- /dev/null
+++ b/lib/overcommit/hook/post_merge/composer_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/composer_install'
+
+module Overcommit::Hook::PostMerge
+ # Runs `composer install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::ComposerInstall
+ class ComposerInstall < Base
+ include Overcommit::Hook::Shared::ComposerInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_merge/index_tags.rb b/lib/overcommit/hook/post_merge/index_tags.rb
index 19133336..a7da71a1 100644
--- a/lib/overcommit/hook/post_merge/index_tags.rb
+++ b/lib/overcommit/hook/post_merge/index_tags.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/index_tags'
module Overcommit::Hook::PostMerge
# Updates ctags index for all source code in the repository.
#
- # @see {Overcommit::Hook::Shared::IndexTags}
+ # @see Overcommit::Hook::Shared::IndexTags
class IndexTags < Base
include Overcommit::Hook::Shared::IndexTags
end
diff --git a/lib/overcommit/hook/post_merge/npm_install.rb b/lib/overcommit/hook/post_merge/npm_install.rb
index 72420ec5..099e838c 100644
--- a/lib/overcommit/hook/post_merge/npm_install.rb
+++ b/lib/overcommit/hook/post_merge/npm_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/npm_install'
module Overcommit::Hook::PostMerge
# Runs `npm install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::NpmInstall}
+ # @see Overcommit::Hook::Shared::NpmInstall
class NpmInstall < Base
include Overcommit::Hook::Shared::NpmInstall
end
diff --git a/lib/overcommit/hook/post_merge/submodule_status.rb b/lib/overcommit/hook/post_merge/submodule_status.rb
index d33bef71..beced8f1 100644
--- a/lib/overcommit/hook/post_merge/submodule_status.rb
+++ b/lib/overcommit/hook/post_merge/submodule_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/submodule_status'
module Overcommit::Hook::PostMerge
diff --git a/lib/overcommit/hook/post_merge/yarn_install.rb b/lib/overcommit/hook/post_merge/yarn_install.rb
new file mode 100644
index 00000000..d47199db
--- /dev/null
+++ b/lib/overcommit/hook/post_merge/yarn_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/yarn_install'
+
+module Overcommit::Hook::PostMerge
+ # Runs `yarn install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::YarnInstall
+ class YarnInstall < Base
+ include Overcommit::Hook::Shared::YarnInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_rewrite/base.rb b/lib/overcommit/hook/post_rewrite/base.rb
index 9dfcaf27..39f4fb6f 100644
--- a/lib/overcommit/hook/post_rewrite/base.rb
+++ b/lib/overcommit/hook/post_rewrite/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
module Overcommit::Hook::PostRewrite
diff --git a/lib/overcommit/hook/post_rewrite/bower_install.rb b/lib/overcommit/hook/post_rewrite/bower_install.rb
index c755aae1..9974e629 100644
--- a/lib/overcommit/hook/post_rewrite/bower_install.rb
+++ b/lib/overcommit/hook/post_rewrite/bower_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bower_install'
module Overcommit::Hook::PostRewrite
# Runs `bower install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BowerInstall}
+ # @see Overcommit::Hook::Shared::BowerInstall
class BowerInstall < Base
include Overcommit::Hook::Shared::BowerInstall
end
diff --git a/lib/overcommit/hook/post_rewrite/bundle_install.rb b/lib/overcommit/hook/post_rewrite/bundle_install.rb
index d3ae92e1..b6409338 100644
--- a/lib/overcommit/hook/post_rewrite/bundle_install.rb
+++ b/lib/overcommit/hook/post_rewrite/bundle_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/bundle_install'
module Overcommit::Hook::PostRewrite
# Runs `bundle install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::BundleInstall}
+ # @see Overcommit::Hook::Shared::BundleInstall
class BundleInstall < Base
include Overcommit::Hook::Shared::BundleInstall
end
diff --git a/lib/overcommit/hook/post_rewrite/composer_install.rb b/lib/overcommit/hook/post_rewrite/composer_install.rb
new file mode 100644
index 00000000..40a23f54
--- /dev/null
+++ b/lib/overcommit/hook/post_rewrite/composer_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/composer_install'
+
+module Overcommit::Hook::PostRewrite
+ # Runs `composer install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::ComposerInstall
+ class ComposerInstall < Base
+ include Overcommit::Hook::Shared::ComposerInstall
+ end
+end
diff --git a/lib/overcommit/hook/post_rewrite/index_tags.rb b/lib/overcommit/hook/post_rewrite/index_tags.rb
index 51c4a558..a3a54550 100644
--- a/lib/overcommit/hook/post_rewrite/index_tags.rb
+++ b/lib/overcommit/hook/post_rewrite/index_tags.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/index_tags'
module Overcommit::Hook::PostRewrite
# Updates ctags index for all source code in the repository.
#
- # @see {Overcommit::Hook::Shared::IndexTags}
+ # @see Overcommit::Hook::Shared::IndexTags
class IndexTags < Base
include Overcommit::Hook::Shared::IndexTags
diff --git a/lib/overcommit/hook/post_rewrite/npm_install.rb b/lib/overcommit/hook/post_rewrite/npm_install.rb
index 382ace9d..3c94d365 100644
--- a/lib/overcommit/hook/post_rewrite/npm_install.rb
+++ b/lib/overcommit/hook/post_rewrite/npm_install.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/npm_install'
module Overcommit::Hook::PostRewrite
# Runs `npm install` when a change is detected in the repository's
# dependencies.
#
- # @see {Overcommit::Hook::Shared::NpmInstall}
+ # @see Overcommit::Hook::Shared::NpmInstall
class NpmInstall < Base
include Overcommit::Hook::Shared::NpmInstall
end
diff --git a/lib/overcommit/hook/post_rewrite/submodule_status.rb b/lib/overcommit/hook/post_rewrite/submodule_status.rb
index 06c77b24..7fd25b7a 100644
--- a/lib/overcommit/hook/post_rewrite/submodule_status.rb
+++ b/lib/overcommit/hook/post_rewrite/submodule_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'overcommit/hook/shared/submodule_status'
module Overcommit::Hook::PostRewrite
diff --git a/lib/overcommit/hook/post_rewrite/yarn_install.rb b/lib/overcommit/hook/post_rewrite/yarn_install.rb
new file mode 100644
index 00000000..fdb64d3e
--- /dev/null
+++ b/lib/overcommit/hook/post_rewrite/yarn_install.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/yarn_install'
+
+module Overcommit::Hook::PostRewrite
+ # Runs `yarn install` when a change is detected in the repository's
+ # dependencies.
+ #
+ # @see Overcommit::Hook::Shared::YarnInstall
+ class YarnInstall < Base
+ include Overcommit::Hook::Shared::YarnInstall
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/author_email.rb b/lib/overcommit/hook/pre_commit/author_email.rb
index 61dc9aec..37fbb478 100644
--- a/lib/overcommit/hook/pre_commit/author_email.rb
+++ b/lib/overcommit/hook/pre_commit/author_email.rb
@@ -1,15 +1,23 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks the format of an author's email address.
class AuthorEmail < Base
def run
- result = execute(%w[git config --get user.email])
- email = result.stdout.chomp
+ email =
+ if ENV.key?('GIT_AUTHOR_EMAIL')
+ ENV['GIT_AUTHOR_EMAIL']
+ else
+ result = execute(%w[git config --get user.email])
+ result.stdout.chomp
+ end
- unless email =~ /#{config['pattern']}/
+ unless email.match?(/#{config['pattern']}/)
return :fail,
"Author has an invalid email address: '#{email}'\n" \
'Set your email with ' \
- '`git config --global user.email your_email@example.com`'
+ '`git config --global user.email your_email@example.com` ' \
+ 'or via the GIT_AUTHOR_EMAIL environment variable'
end
:pass
diff --git a/lib/overcommit/hook/pre_commit/author_name.rb b/lib/overcommit/hook/pre_commit/author_name.rb
index 6beddaa6..fc89792a 100644
--- a/lib/overcommit/hook/pre_commit/author_name.rb
+++ b/lib/overcommit/hook/pre_commit/author_name.rb
@@ -1,14 +1,22 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Ensures that a commit author has a name with at least first and last names.
class AuthorName < Base
def run
- result = execute(%w[git config --get user.name])
- name = result.stdout.chomp
+ name =
+ if ENV.key?('GIT_AUTHOR_NAME')
+ ENV['GIT_AUTHOR_NAME']
+ else
+ result = execute(%w[git config --get user.name])
+ result.stdout.chomp
+ end
- unless name.split(' ').count >= 2
+ if name.empty?
return :fail,
- "Author must have at least first and last name, but was: #{name}.\n" \
- 'Set your name with `git config --global user.name "Your Name"`'
+ "Author name must be non-0 in length.\n" \
+ 'Set your name with `git config --global user.name "Your Name"` ' \
+ 'or via the GIT_AUTHOR_NAME environment variable'
end
:pass
diff --git a/lib/overcommit/hook/pre_commit/base.rb b/lib/overcommit/hook/pre_commit/base.rb
index c7c7456a..4e944db5 100644
--- a/lib/overcommit/hook/pre_commit/base.rb
+++ b/lib/overcommit/hook/pre_commit/base.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require 'forwardable'
+require 'overcommit/utils/messages_utils'
module Overcommit::Hook::PreCommit
# Functionality common to all pre-commit hooks.
@@ -9,65 +12,8 @@ class Base < Overcommit::Hook::Base
private
- # Extract file, line number, and type of message from an error/warning
- # messages in output.
- #
- # Assumes each element of `output` is a separate error/warning with all
- # information necessary to identify it.
- #
- # @param output_messages [Array] unprocessed error/warning messages
- # @param regex [Regexp] regular expression defining `file`, `line` and
- # `type` capture groups used to extract file locations and error/warning
- # type from each line of output
- # @param type_categorizer [Proc] function executed against the `type`
- # capture group to convert it to a `:warning` or `:error` symbol. Assumes
- # `:error` if `nil`.
- # @raise [RuntimeError] line of output did not match regex
- # @return [Array]
- def extract_messages(output_messages, regex, type_categorizer = nil)
- output_messages.map do |message|
- unless match = message.match(regex)
- raise 'Unexpected output: unable to determine line number or type ' \
- "of error/warning for message '#{message}'"
- end
-
- file = extract_file(match, message)
- line = extract_line(match, message) if match.names.include?('line') && match[:line]
- type = extract_type(match, message, type_categorizer)
-
- Overcommit::Hook::Message.new(type, file, line, message)
- end
- end
-
- def extract_file(match, message)
- return unless match.names.include?('file')
-
- if match[:file].to_s.empty?
- raise "Unexpected output: no file found in '#{message}'"
- end
-
- match[:file]
- end
-
- def extract_line(match, message)
- return unless match.names.include?('line')
- Integer(match[:line])
- rescue ArgumentError, TypeError
- raise "Unexpected output: invalid line number found in '#{message}'"
- end
-
- def extract_type(match, message, type_categorizer)
- if type_categorizer
- type_match = match.names.include?('type') ? match[:type] : nil
- type = type_categorizer.call(type_match)
- unless Overcommit::Hook::MESSAGE_TYPES.include?(type)
- raise "Invalid message type '#{type}' for '#{message}': must " \
- "be one of #{Overcommit::Hook::MESSAGE_TYPES.inspect}"
- end
- type
- else
- :error # Assume error since no categorizer was defined
- end
+ def extract_messages(*args)
+ Overcommit::Utils::MessagesUtils.extract_messages(*args)
end
end
end
diff --git a/lib/overcommit/hook/pre_commit/berksfile_check.rb b/lib/overcommit/hook/pre_commit/berksfile_check.rb
index 80049e2d..a8040976 100644
--- a/lib/overcommit/hook/pre_commit/berksfile_check.rb
+++ b/lib/overcommit/hook/pre_commit/berksfile_check.rb
@@ -6,7 +6,7 @@ module Overcommit::Hook::PreCommit
#
# @see http://berkshelf.com/
class BerksfileCheck < Base
- LOCK_FILE = 'Berksfile.lock'.freeze
+ LOCK_FILE = 'Berksfile.lock'
def run
# Ignore if Berksfile.lock is not tracked by git
diff --git a/lib/overcommit/hook/pre_commit/broken_symlinks.rb b/lib/overcommit/hook/pre_commit/broken_symlinks.rb
index c2a62c8e..50dbbee2 100644
--- a/lib/overcommit/hook/pre_commit/broken_symlinks.rb
+++ b/lib/overcommit/hook/pre_commit/broken_symlinks.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for broken symlinks.
class BrokenSymlinks < Base
diff --git a/lib/overcommit/hook/pre_commit/bundle_audit.rb b/lib/overcommit/hook/pre_commit/bundle_audit.rb
new file mode 100644
index 00000000..1f9f24c7
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/bundle_audit.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Checks for vulnerable versions of gems in Gemfile.lock.
+ #
+ # @see https://github.com/rubysec/bundler-audit
+ class BundleAudit < Base
+ LOCK_FILE = 'Gemfile.lock'
+
+ def run
+ # Ignore if Gemfile.lock is not tracked by git
+ ignored_files = execute(%W[git ls-files -o -i --exclude-standard -- #{LOCK_FILE}]).
+ stdout.split("\n")
+ return :pass if ignored_files.include?(LOCK_FILE)
+
+ result = execute(command)
+ if result.success?
+ :pass
+ else
+ [:warn, result.stdout]
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/bundle_check.rb b/lib/overcommit/hook/pre_commit/bundle_check.rb
index d2ddd406..70f5fa2e 100644
--- a/lib/overcommit/hook/pre_commit/bundle_check.rb
+++ b/lib/overcommit/hook/pre_commit/bundle_check.rb
@@ -6,7 +6,7 @@ module Overcommit::Hook::PreCommit
#
# @see http://bundler.io/
class BundleCheck < Base
- LOCK_FILE = 'Gemfile.lock'.freeze
+ LOCK_FILE = File.basename(ENV['BUNDLE_GEMFILE'] || 'Gemfile') + '.lock'
def run
# Ignore if Gemfile.lock is not tracked by git
@@ -22,7 +22,8 @@ def run
new_lockfile = File.read(LOCK_FILE) if File.exist?(LOCK_FILE)
if previous_lockfile != new_lockfile
- return :fail, "#{LOCK_FILE} is not up-to-date -- run `#{command.join(' ')}`"
+ return :fail, "#{LOCK_FILE} is not up-to-date -- run \
+ `#{command.join(' ')}` or add the Gemfile and/or Gemfile.lock".squeeze
end
:pass
diff --git a/lib/overcommit/hook/pre_commit/bundle_outdated.rb b/lib/overcommit/hook/pre_commit/bundle_outdated.rb
new file mode 100644
index 00000000..afa36c23
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/bundle_outdated.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Check if any gems in Gemfile.lock have newer versions, unless the
+ # Gemfile.lock is ignored by Git.
+ #
+ # @see http://bundler.io/bundle_outdated.html
+ class BundleOutdated < Base
+ LOCK_FILE = 'Gemfile.lock'
+
+ def run
+ # Ignore if Gemfile.lock is not tracked by git
+ ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n")
+ return :pass if ignored_files.include?(LOCK_FILE)
+
+ result = execute(command)
+ warn_msgs = result.stdout.split("\n").
+ reject { |str| str.strip.empty? }.
+ reject { |str| (str.strip =~ /^(\[|\()?warning|deprecation/i) }
+ warnings = warn_msgs.map { |msg| Overcommit::Hook::Message.new(:warning, nil, nil, msg) }
+
+ warnings.empty? ? :pass : warnings
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/case_conflicts.rb b/lib/overcommit/hook/pre_commit/case_conflicts.rb
index 84e7af95..ee6e4161 100644
--- a/lib/overcommit/hook/pre_commit/case_conflicts.rb
+++ b/lib/overcommit/hook/pre_commit/case_conflicts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for files that would conflict in case-insensitive filesystems
# Adapted from https://github.com/pre-commit/pre-commit-hooks
diff --git a/lib/overcommit/hook/pre_commit/chamber_compare.rb b/lib/overcommit/hook/pre_commit/chamber_compare.rb
new file mode 100644
index 00000000..b3e0366c
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/chamber_compare.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `chamber compare` against a configurable set of namespaces.
+ #
+ # @see https://github.com/thekompanee/chamber/wiki/Git-Commit-Hooks#chamber-compare-pre-commit-hook
+ # rubocop:disable Metrics/MethodLength
+ class ChamberCompare < Base
+ def run
+ config['namespaces'].each_index do |index|
+ first = config['namespaces'][index]
+ second = config['namespaces'][index + 1]
+
+ next unless second
+
+ result = execute(
+ command,
+ args: [
+ "--first=#{first.join(' ')}",
+ "--second=#{second.join(' ')}",
+ ],
+ )
+
+ unless result.stdout.empty?
+ trimmed_result = result.stdout.split("\n")
+ 5.times { trimmed_result.shift }
+ trimmed_result = trimmed_result.join("\n")
+
+ return [
+ :warn,
+ "It appears your namespace settings between #{first} and " \
+ "#{second} are not in sync:\n\n#{trimmed_result}\n\n" \
+ "Run: chamber compare --first=#{first.join(' ')} " \
+ "--second=#{second.join(' ')}",
+ ]
+ end
+ end
+
+ :pass
+ end
+ end
+ # rubocop:enable Metrics/MethodLength
+end
diff --git a/lib/overcommit/hook/pre_commit/chamber_security.rb b/lib/overcommit/hook/pre_commit/chamber_security.rb
index b70148b3..c639fbf8 100644
--- a/lib/overcommit/hook/pre_commit/chamber_security.rb
+++ b/lib/overcommit/hook/pre_commit/chamber_security.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `chamber secure` against any modified Chamber settings files.
#
@@ -7,6 +9,7 @@ def run
result = execute(command, args: applicable_files)
return :pass if result.stdout.empty?
+
[:fail, "These settings appear to need to be secured but were not: #{result.stdout}"]
end
end
diff --git a/lib/overcommit/hook/pre_commit/chamber_verification.rb b/lib/overcommit/hook/pre_commit/chamber_verification.rb
new file mode 100644
index 00000000..8581c063
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/chamber_verification.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `chamber sign --verify`.
+ #
+ # @see https://github.com/thekompanee/chamber/wiki/Git-Commit-Hooks#chamber-verification-pre-commit-hook
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+ class ChamberVerification < Base
+ def run
+ approver_name = config.fetch('approver_name') { 'your approver' }
+ approver_email = config['approver_email'] ? " (#{config['approver_email']})" : nil
+
+ result = execute(command)
+
+ return :pass if result.stdout.empty? && result.stderr.empty?
+ return :pass if result.stderr =~ /no signature key was found/
+
+ output = [
+ result.stdout.empty? ? nil : result.stdout,
+ result.stderr.empty? ? nil : result.stderr,
+ ].
+ compact.
+ join("\n\n")
+
+ output = "\n\n#{output}" unless output.empty?
+
+ [
+ :warn,
+ "One or more of your settings files does not match the signature.\n" \
+ "Talk to #{approver_name}#{approver_email} about getting them " \
+ "approved.#{output}",
+ ]
+ end
+ end
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
+end
diff --git a/lib/overcommit/hook/pre_commit/code_spell_check.rb b/lib/overcommit/hook/pre_commit/code_spell_check.rb
new file mode 100644
index 00000000..a1132797
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/code_spell_check.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `alfonsox` spell-checking tool against any modified code file.
+ #
+ # @see https://github.com/diegojromerolopez/alfonsox
+ class CodeSpellCheck < Base
+ def run
+ # Create default file config if it does not exist
+
+ # Run spell-check
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ spellchecking_errors = result.stderr.split("\n")
+ spellchecking_errors.pop
+
+ error_messages(spellchecking_errors)
+ end
+
+ private
+
+ # Create the error messages
+ def error_messages(spellchecking_errors)
+ messages = []
+ spellchecking_errors.each do |spellchecking_error_i|
+ error_location, word = spellchecking_error_i.split(' ')
+ error_file_path, line = error_location.split(':')
+ messages << Overcommit::Hook::Message.new(
+ :error, error_file_path, line, "#{error_location}: #{word}"
+ )
+ end
+ messages
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/coffee_lint.rb b/lib/overcommit/hook/pre_commit/coffee_lint.rb
index 2f56ca0b..57c80a58 100644
--- a/lib/overcommit/hook/pre_commit/coffee_lint.rb
+++ b/lib/overcommit/hook/pre_commit/coffee_lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `coffeelint` against any modified CoffeeScript files.
#
@@ -8,7 +10,7 @@ class CoffeeLint < Base
,(?\d*),\d*
,(?\w+)
,(?.+)$
- /x
+ /x.freeze
MESSAGE_TYPE_CATEGORIZER = lambda do |type|
type.include?('w') ? :warning : :error
@@ -23,6 +25,7 @@ def run
def parse_messages(output)
output.scan(MESSAGE_REGEX).map do |file, line, type, msg|
+ line = line.to_i
type = MESSAGE_TYPE_CATEGORIZER.call(type)
text = "#{file}:#{line}:#{type} #{msg}"
Overcommit::Hook::Message.new(type, file, line, text)
diff --git a/lib/overcommit/hook/pre_commit/cook_style.rb b/lib/overcommit/hook/pre_commit/cook_style.rb
new file mode 100644
index 00000000..e3242645
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/cook_style.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `cookstyle` against any modified Chef Ruby files.
+ #
+ # @see https://docs.chef.io/cookstyle.html
+ class CookStyle < Base
+ GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ type.match?(/^warn/) ? :warning : :error
+ end
+
+ COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ type.include?('W') ? :warning : :error
+ end
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ generic_messages = extract_messages(
+ result.stderr.split("\n"),
+ /^(?[a-z]+)/i,
+ GENERIC_MESSAGE_TYPE_CATEGORIZER,
+ )
+
+ cop_messages = extract_messages(
+ result.stdout.split("\n"),
+ /^(?(?:\w:)?[^:]+):(?\d+):[^ ]+ (?[^ ]+)/,
+ COP_MESSAGE_TYPE_CATEGORIZER,
+ )
+
+ generic_messages + cop_messages
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/credo.rb b/lib/overcommit/hook/pre_commit/credo.rb
new file mode 100644
index 00000000..b6d4143f
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/credo.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `credo` against any modified ex files.
+ #
+ # @see https://github.com/rrrene/credo
+ class Credo < Base
+ # example message:
+ # lib/file1.ex:1:11: R: Modules should have a @moduledoc tag.
+ # lib/file2.ex:12:81: R: Line is too long (max is 80, was 81).
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ result.stdout.split("\n").map(&:strip).reject(&:empty?).
+ map { |error| message(error) }
+ end
+
+ private
+
+ def message(error)
+ file, line = error.split(':')
+ Overcommit::Hook::Message.new(:error, file, Integer(line), error)
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/css_lint.rb b/lib/overcommit/hook/pre_commit/css_lint.rb
index 27ffd7eb..b110d074 100644
--- a/lib/overcommit/hook/pre_commit/css_lint.rb
+++ b/lib/overcommit/hook/pre_commit/css_lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `csslint` against any modified CSS files.
#
@@ -7,7 +9,7 @@ class CssLint < Base
^(?(?:\w:)?[^:]+):\s
(?:line\s(?\d+)[^EW]+)?
(?Error|Warning)
- /x
+ /x.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/dart_analyzer.rb b/lib/overcommit/hook/pre_commit/dart_analyzer.rb
new file mode 100644
index 00000000..38002f22
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/dart_analyzer.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `dartanalyzer` against modified Dart files.
+ # @see https://dart.dev/tools/dartanalyzer
+ class DartAnalyzer < Base
+ MESSAGE_REGEX = /(?.*)•\ (?[^•]+)•\ (?[^:]+):(?\d+):(\d+)\.*/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ extract_messages(
+ result.stdout.split("\n").grep(MESSAGE_REGEX),
+ MESSAGE_REGEX,
+ lambda do |type|
+ type.include?('error') ? :error : :warning
+ end
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/dogma.rb b/lib/overcommit/hook/pre_commit/dogma.rb
index e9c217fb..744553a2 100644
--- a/lib/overcommit/hook/pre_commit/dogma.rb
+++ b/lib/overcommit/hook/pre_commit/dogma.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `dogma` against any modified ex files.
#
diff --git a/lib/overcommit/hook/pre_commit/erb_lint.rb b/lib/overcommit/hook/pre_commit/erb_lint.rb
new file mode 100644
index 00000000..ae5af164
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/erb_lint.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `erblint` against any modified ERB files.
+ #
+ # @see https://github.com/Shopify/erb-lint
+ class ErbLint < Base
+ MESSAGE_REGEX = /(?.+)\nIn file: (?.+):(?\d+)/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ extract_messages(
+ result.stdout.split("\n\n")[1..],
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/es_lint.rb b/lib/overcommit/hook/pre_commit/es_lint.rb
index 01466b6f..240d749d 100644
--- a/lib/overcommit/hook/pre_commit/es_lint.rb
+++ b/lib/overcommit/hook/pre_commit/es_lint.rb
@@ -1,20 +1,35 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `eslint` against any modified JavaScript files.
#
+ # Protip: if you have an npm script set up to run eslint, you can configure
+ # this hook to run eslint via your npm script by using the `command` option in
+ # your .overcommit.yml file. This can be useful if you have some eslint
+ # configuration built into your npm script that you don't want to repeat
+ # somewhere else. Example:
+ #
+ # EsLint:
+ # required_executable: 'npm'
+ # enabled: true
+ # command: ['npm', 'run', 'lint', '--', '-f', 'compact']
+ #
+ # Note: This hook supports only compact format.
+ #
# @see http://eslint.org/
class EsLint < Base
def run
+ eslint_regex = /^(?[^\s](?:\w:)?[^:]+):[^\d]+(?\d+).*?(?Error|Warning)/
result = execute(command, args: applicable_files)
output = result.stdout.chomp
+ messages = output.split("\n").grep(eslint_regex)
+
+ return [:fail, result.stderr] if messages.empty? && !result.success?
return :pass if result.success? && output.empty?
# example message:
# path/to/file.js: line 1, col 0, Error - Error message (ruleName)
- extract_messages(
- output.split("\n").grep(/Warning|Error/),
- /^(?(?:\w:)?[^:]+):[^\d]+(?\d+).*?(?Error|Warning)/,
- lambda { |type| type.downcase.to_sym }
- )
+ extract_messages(messages, eslint_regex, lambda { |type| type.downcase.to_sym })
end
end
end
diff --git a/lib/overcommit/hook/pre_commit/execute_permissions.rb b/lib/overcommit/hook/pre_commit/execute_permissions.rb
index 249f76cb..ae4cabd9 100644
--- a/lib/overcommit/hook/pre_commit/execute_permissions.rb
+++ b/lib/overcommit/hook/pre_commit/execute_permissions.rb
@@ -1,7 +1,19 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for files with execute permissions, which are usually not necessary
# in source code files (and are typically caused by a misconfigured editor
# assigning incorrect default permissions).
+ #
+ # Protip: if you have some files that you want to allow execute permissions
+ # on, you can disable this hook for those files by using the `exclude` option
+ # on your .overcommit.yml file. Example:
+ #
+ # ExecutePermissions:
+ # enabled: true
+ # exclude:
+ # - 'path/to/my/file/that/should/have/execute/permissions.sh'
+ # - 'directory/that/should/have/execute/permissions/**/*'
class ExecutePermissions < Base
def run
file_modes = {}
@@ -42,7 +54,7 @@ def extract_from_git_tree(file_modes)
end
def extract_from_git_index(file_modes)
- result = execute(%w[git diff --raw --cached --], args: applicable_files)
+ result = execute(%w[git diff --raw --cached --no-color --], args: applicable_files)
raise 'Unable to access git index' unless result.success?
result.stdout.split("\n").each do |line|
diff --git a/lib/overcommit/hook/pre_commit/fasterer.rb b/lib/overcommit/hook/pre_commit/fasterer.rb
new file mode 100644
index 00000000..871be80f
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/fasterer.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `fasterer` against any modified Ruby files.
+ #
+ # @see https://github.com/DamirSvrtan/fasterer
+ class Fasterer < Base
+ def run
+ result = execute(command, args: applicable_files)
+ output = result.stdout
+
+ if extract_offense_num(output) == 0
+ :pass
+ else
+ [:warn, output]
+ end
+ end
+
+ private
+
+ def extract_offense_num(raw_output)
+ raw_output.scan(/(\d+) offense detected/).flatten.map(&:to_i).inject(0, :+)
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/file_size.rb b/lib/overcommit/hook/pre_commit/file_size.rb
new file mode 100644
index 00000000..f81154ef
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/file_size.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Checks for oversized files before committing.
+ class FileSize < Base
+ def run
+ return :pass if oversized_files.empty?
+
+ oversized_files.map do |file|
+ error_message_for(file)
+ end
+ end
+
+ def description
+ "Check for files over #{size_limit_bytes} bytes"
+ end
+
+ private
+
+ def oversized_files
+ @oversized_files ||= build_oversized_file_list
+ end
+
+ def build_oversized_file_list
+ applicable_files.select do |file|
+ File.exist?(file) && file_size(file) > size_limit_bytes
+ end
+ end
+
+ def size_limit_bytes
+ config.fetch('size_limit_bytes')
+ end
+
+ def error_message_for(file)
+ Overcommit::Hook::Message.new(
+ :error,
+ file,
+ nil,
+ "#{file} is #{file_size(file)} bytes"
+ )
+ end
+
+ def file_size(file)
+ File.size(file)
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/fix_me.rb b/lib/overcommit/hook/pre_commit/fix_me.rb
new file mode 100644
index 00000000..81dad3f9
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/fix_me.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Check for "token" strings
+ class FixMe < Base
+ def run
+ keywords = config['keywords']
+ result = execute(command, args: [keywords.join('|')] + applicable_files)
+
+ extract_messages(
+ result.stdout.split("\n"),
+ /^(?(?:\w:)?[^:]+):(?\d+)/,
+ lambda { |_type| :warning }
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/flay.rb b/lib/overcommit/hook/pre_commit/flay.rb
new file mode 100644
index 00000000..9876739a
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/flay.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `flay` against any modified files.
+ #
+ # @see https://github.com/seattlerb/flay
+ class Flay < Base
+ # Flay prints two kinds of messages:
+ #
+ # 1) IDENTICAL code found in :defn (mass*2 = MASS)
+ # file_path_1.rb:LINE_1
+ # file_path_2.rb:LINE_2
+ #
+ # 2) Similar code found in :defn (mass = MASS)
+ # file_path_1.rb:LINE_1
+ # file_path_2.rb:LINE_2
+ #
+
+ def run
+ command = ['flay', '--mass', @config['mass_threshold'].to_s, '--fuzzy', @config['fuzzy'].to_s]
+ # Use a more liberal detection method
+ command += ['--liberal'] if @config['liberal']
+ messages = []
+ # Run the command for each file
+ applicable_files.each do |file|
+ result = execute(command, args: [file])
+ results = result.stdout.split("\n\n")
+ results.shift
+ unless results.empty?
+ error_message = results.join("\n").gsub(/^\d+\)\s*/, '')
+ message = Overcommit::Hook::Message.new(:error, nil, nil, error_message)
+ messages << message
+ end
+ end
+ messages
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/foodcritic.rb b/lib/overcommit/hook/pre_commit/foodcritic.rb
new file mode 100644
index 00000000..b40adfbe
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/foodcritic.rb
@@ -0,0 +1,151 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `foodcritic` against any modified Ruby files from Chef directory structure.
+ #
+ # @see http://www.foodcritic.io/
+ #
+ # There are two "modes" you can run this hook in based on the repo:
+ #
+ # SINGLE COOKBOOK REPO MODE
+ # -------------------------
+ # The default. Use this if your repository contains just a single cookbook,
+ # i.e. the top-level repo directory contains directories called `attributes`,
+ # `libraries`, `recipes`, etc.
+ #
+ # To get this to work well, you'll want to set your Overcommit configuration
+ # for this hook to something like:
+ #
+ # PreCommit:
+ # Foodcritic:
+ # enabled: true
+ # include:
+ # - 'attributes/**/*'
+ # - 'definitions/**/*'
+ # - 'files/**/*'
+ # - 'libraries/**/*'
+ # - 'providers/**/*'
+ # - 'recipes/**/*'
+ # - 'resources/**/*'
+ # - 'templates/**/*'
+ #
+ # MONOLITHIC REPO MODE
+ # --------------------
+ # Use this if you store multiple cookbooks, environments, and roles (or any
+ # combination thereof) in a single repository.
+ #
+ # There are three configuration options relevant here:
+ #
+ # * `cookbooks_directory`
+ # When set, hook will treat the path as a directory containing cookbooks.
+ # Each subdirectory of this directory will be treated as a separate
+ # cookbook.
+ #
+ # * `environments_directory`
+ # When set, hook will treat the path as a directory containing environment
+ # files.
+ #
+ # * `roles_directory`
+ # When set, hook will treat the given path as a directory containing role
+ # files.
+ #
+ # In order to run in monolithic repo mode, YOU MUST SET `cookbooks_directory`.
+ # The other configuration options are optional, if you happen to store
+ # environments/roles in another repo.
+ #
+ # To get this to work well, you'll want to set your Overcommit configuration
+ # for this hook to something like:
+ #
+ # PreCommit:
+ # Foodcritic:
+ # enabled: true
+ # cookbooks_directory: 'cookbooks'
+ # environments_directory: 'environments'
+ # roles_directory: 'roles'
+ # include:
+ # - 'cookbooks/**/*'
+ # - 'environments/**/*'
+ # - 'roles/**/*'
+ #
+ # ADDITIONAL CONFIGURATION
+ # ------------------------
+ # You can disable rules using the `flags` hook option. For example:
+ #
+ # PreCommit:
+ # Foodcritic:
+ # enabled: true
+ # ...
+ # flags:
+ # - '--epic-fail=any'
+ # - '-t~FC011' # Missing README in markdown format
+ # - '-t~FC064' # Ensure issues_url is set in metadata
+ #
+ # Any other command line flag supported by the `foodcritic` executable can be
+ # specified here.
+ #
+ # If you want the hook run to fail (and not just warn), set the `on_warn`
+ # option for the hook to `fail`:
+ #
+ # PreCommit:
+ # Foodcritic:
+ # enabled: true
+ # on_warn: fail
+ # ...
+ #
+ # This will treat any warnings as failures and cause the hook to exit
+ # unsuccessfully.
+ class Foodcritic < Base
+ def run
+ args = modified_cookbooks_args + modified_environments_args + modified_roles_args
+ result = execute(command, args: args)
+
+ if result.success?
+ :pass
+ else
+ [:warn, result.stderr + result.stdout]
+ end
+ end
+
+ private
+
+ def directories_changed(dir_prefix)
+ applicable_files.
+ select { |path| path.start_with?(dir_prefix) }.
+ map { |path| path.gsub(%r{^#{dir_prefix}/}, '') }.
+ group_by { |path| path.split('/').first }.
+ keys.
+ map { |path| File.join(dir_prefix, path) }
+ end
+
+ def modified_environments_args
+ modified('environments').map { |env| %W[-E #{env}] }.flatten
+ end
+
+ def modified_roles_args
+ modified('roles').map { |role| %W[-R #{role}] }.flatten
+ end
+
+ def modified_cookbooks_args
+ # Return the repo root if repository contains a single cookbook
+ if !config['cookbooks_directory'] || config['cookbooks_directory'].empty?
+ ['-B', Overcommit::Utils.repo_root]
+ else
+ # Otherwise return all modified cookbooks in the cookbook directory
+ modified('cookbooks').map { |cookbook| ['-B', cookbook] }.flatten
+ end
+ end
+
+ def modified(type)
+ return [] if !config["#{type}_directory"] || config["#{type}_directory"].empty?
+
+ @modified ||= {}
+ @modified[type] ||= directories_changed(full_directory_path("#{type}_directory"))
+ end
+
+ def full_directory_path(config_option)
+ return config[config_option] if config[config_option].start_with?(File::SEPARATOR)
+
+ File.absolute_path(File.join(Overcommit::Utils.repo_root, config[config_option]))
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/forbidden_branches.rb b/lib/overcommit/hook/pre_commit/forbidden_branches.rb
new file mode 100644
index 00000000..6969742f
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/forbidden_branches.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Prevents commits to branches matching one of the configured patterns.
+ class ForbiddenBranches < Base
+ def run
+ return :pass unless forbidden_commit?
+
+ [:fail, "Committing to #{current_branch} is forbidden"]
+ end
+
+ private
+
+ def forbidden_commit?
+ forbidden_branch_patterns.any? { |p| File.fnmatch(p, current_branch) }
+ end
+
+ def forbidden_branch_patterns
+ @forbidden_branch_patterns ||= Array(config['branch_patterns'])
+ end
+
+ def current_branch
+ @current_branch ||= Overcommit::GitRepo.current_branch
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/ginkgo_focus.rb b/lib/overcommit/hook/pre_commit/ginkgo_focus.rb
new file mode 100644
index 00000000..8528926e
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/ginkgo_focus.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Check for "focused" tests
+ class GinkgoFocus < Base
+ def run
+ keywords = config['keywords']
+ result = execute(command, args: [keywords.join('|')] + applicable_files)
+
+ extract_messages(
+ result.stdout.split("\n"),
+ /^(?(?:\w:)?[^:]+):(?\d+)/,
+ lambda { |_type| :warning }
+ )
+ end
+
+ def applicable_test_files
+ applicable_files.select do |f|
+ f if f =~ /_test\.go/
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/go_fmt.rb b/lib/overcommit/hook/pre_commit/go_fmt.rb
new file mode 100644
index 00000000..0f46a18e
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/go_fmt.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs go fmt for all modified Go files
+ class GoFmt < Base
+ def run
+ errors = []
+ applicable_files.each do |file|
+ result = execute(command, args: [file])
+ errors << (result.stdout + result.stderr) unless result.success?
+ end
+ return :pass if errors.empty?
+
+ [:fail, errors.join("\n")]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/go_lint.rb b/lib/overcommit/hook/pre_commit/go_lint.rb
index e5b93c3b..c610edb4 100644
--- a/lib/overcommit/hook/pre_commit/go_lint.rb
+++ b/lib/overcommit/hook/pre_commit/go_lint.rb
@@ -1,11 +1,20 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `golint` against any modified Golang files.
#
# @see https://github.com/golang/lint
class GoLint < Base
def run
- result = execute(command, args: applicable_files)
- output = result.stdout + result.stderr
+ output = ''
+
+ # golint doesn't accept multiple file arguments if
+ # they belong to different packages
+ applicable_files.each do |gofile|
+ result = execute(command, args: Array(gofile))
+ output += result.stdout + result.stderr
+ end
+
# Unfortunately the exit code is always 0
return :pass if output.empty?
diff --git a/lib/overcommit/hook/pre_commit/go_vet.rb b/lib/overcommit/hook/pre_commit/go_vet.rb
index 7ddfa840..92814648 100644
--- a/lib/overcommit/hook/pre_commit/go_vet.rb
+++ b/lib/overcommit/hook/pre_commit/go_vet.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `go vet` against any modified Golang files.
#
@@ -7,7 +9,7 @@ def run
result = execute(command, args: applicable_files)
return :pass if result.success?
- if result.stderr =~ /no such tool "vet"/
+ if result.stderr.match?(/no such tool "vet"/)
return :fail, "`go tool vet` is not installed#{install_command_prompt}"
end
diff --git a/lib/overcommit/hook/pre_commit/golangci_lint.rb b/lib/overcommit/hook/pre_commit/golangci_lint.rb
new file mode 100644
index 00000000..6dffe659
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/golangci_lint.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `golangci-lint run` against any modified packages
+ #
+ # @see https://github.com/golangci/golangci-lint
+ class GolangciLint < Base
+ def run
+ packages = applicable_files.map { |f| File.dirname(f) }.uniq
+ result = execute(command, args: packages)
+ return :pass if result.success?
+ return [:fail, result.stderr] unless result.stderr.empty?
+
+ extract_messages(
+ result.stdout.split("\n"),
+ /^(?(?:\w:)?[^:]+):(?\d+)/,
+ nil
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/hadolint.rb b/lib/overcommit/hook/pre_commit/hadolint.rb
new file mode 100644
index 00000000..dd26b2bb
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/hadolint.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `hadolint` against any modified Dockefile files.
+ #
+ # @see http://hadolint.lukasmartinelli.ch/
+ class Hadolint < Base
+ def run
+ output = ''
+ success = true
+
+ # hadolint doesn't accept multiple arguments
+ applicable_files.each do |dockerfile|
+ result = execute(command, args: Array(dockerfile))
+ output += result.stdout
+ success &&= result.success?
+ end
+
+ return :pass if success
+
+ extract_messages(
+ output.split("\n"),
+ /^(?[^:]+):(?\d+)/,
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/haml_lint.rb b/lib/overcommit/hook/pre_commit/haml_lint.rb
index f91dc303..af8d801a 100644
--- a/lib/overcommit/hook/pre_commit/haml_lint.rb
+++ b/lib/overcommit/hook/pre_commit/haml_lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `haml-lint` against any modified HAML files.
#
diff --git a/lib/overcommit/hook/pre_commit/hard_tabs.rb b/lib/overcommit/hook/pre_commit/hard_tabs.rb
index 854337ee..0c94d4eb 100644
--- a/lib/overcommit/hook/pre_commit/hard_tabs.rb
+++ b/lib/overcommit/hook/pre_commit/hard_tabs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for hard tabs in files.
class HardTabs < Base
diff --git a/lib/overcommit/hook/pre_commit/hlint.rb b/lib/overcommit/hook/pre_commit/hlint.rb
index fcbfa122..a52d1b62 100644
--- a/lib/overcommit/hook/pre_commit/hlint.rb
+++ b/lib/overcommit/hook/pre_commit/hlint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `hlint` against any modified Haskell files.
#
@@ -8,7 +10,7 @@ class Hlint < Base
:(?\d+)
:\d+
:\s*(?\w+)
- /x
+ /x.freeze
MESSAGE_TYPE_CATEGORIZER = lambda do |type|
type.include?('W') ? :warning : :error
diff --git a/lib/overcommit/hook/pre_commit/html_hint.rb b/lib/overcommit/hook/pre_commit/html_hint.rb
index 600e2c52..ddbe37d1 100644
--- a/lib/overcommit/hook/pre_commit/html_hint.rb
+++ b/lib/overcommit/hook/pre_commit/html_hint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `htmlhint` against any modified HTML files.
#
@@ -12,7 +14,7 @@ def run
lines = group.split("\n").map(&:strip)
file = lines[0][/(.+):/, 1]
extract_messages(
- lines[1..-1].map { |msg| "#{file}: #{msg}" },
+ lines[1..].map { |msg| "#{file}: #{msg}" },
/^(?(?:\w:)?[^:]+): line (?\d+)/
)
end.flatten
diff --git a/lib/overcommit/hook/pre_commit/html_tidy.rb b/lib/overcommit/hook/pre_commit/html_tidy.rb
index e8c172e2..2667e2c1 100644
--- a/lib/overcommit/hook/pre_commit/html_tidy.rb
+++ b/lib/overcommit/hook/pre_commit/html_tidy.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `tidy` against any modified HTML files.
#
@@ -8,7 +10,7 @@ class HtmlTidy < Base
line\s(?\d+)\s
column\s(?\d+)\s-\s
(?Error|Warning):\s(?.+)$
- /x
+ /x.freeze
def run
# example message:
diff --git a/lib/overcommit/hook/pre_commit/image_optim.rb b/lib/overcommit/hook/pre_commit/image_optim.rb
index a4cd75af..200a83f7 100644
--- a/lib/overcommit/hook/pre_commit/image_optim.rb
+++ b/lib/overcommit/hook/pre_commit/image_optim.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for images that can be optimized with `image_optim`.
#
diff --git a/lib/overcommit/hook/pre_commit/java_checkstyle.rb b/lib/overcommit/hook/pre_commit/java_checkstyle.rb
index d4a519d5..5d627a64 100644
--- a/lib/overcommit/hook/pre_commit/java_checkstyle.rb
+++ b/lib/overcommit/hook/pre_commit/java_checkstyle.rb
@@ -1,20 +1,26 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `checkstyle` against any modified Java files.
#
# @see http://checkstyle.sourceforge.net/
class JavaCheckstyle < Base
- MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+)/
+ MESSAGE_REGEX = /^(\[(?[^\]]+)\]\s+)?(?(?:\w:)?[^:]+):(?\d+)/.freeze
+
+ MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ %w[WARN INFO].include?(type.to_s) ? :warning : :error
+ end
def run
result = execute(command, args: applicable_files)
output = result.stdout.chomp
- return :pass if result.success?
# example message:
# path/to/file.java:3:5: Error message
extract_messages(
output.split("\n").grep(MESSAGE_REGEX),
- MESSAGE_REGEX
+ MESSAGE_REGEX,
+ MESSAGE_TYPE_CATEGORIZER
)
end
end
diff --git a/lib/overcommit/hook/pre_commit/js_hint.rb b/lib/overcommit/hook/pre_commit/js_hint.rb
index 943e64ca..6c22c538 100644
--- a/lib/overcommit/hook/pre_commit/js_hint.rb
+++ b/lib/overcommit/hook/pre_commit/js_hint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `jshint` against any modified JavaScript files.
#
diff --git a/lib/overcommit/hook/pre_commit/js_lint.rb b/lib/overcommit/hook/pre_commit/js_lint.rb
index 4e2e6bab..956dfd2c 100644
--- a/lib/overcommit/hook/pre_commit/js_lint.rb
+++ b/lib/overcommit/hook/pre_commit/js_lint.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `jslint` against any modified JavaScript files.
#
# @see http://www.jslint.com/
class JsLint < Base
- MESSAGE_REGEX = /(?(?:\w:)?[^:]+):(?\d+)/
+ MESSAGE_REGEX = /(?(?:\w:)?[^:]+):(?\d+)/.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/jscs.rb b/lib/overcommit/hook/pre_commit/jscs.rb
index 23a63f1c..db3ee57b 100644
--- a/lib/overcommit/hook/pre_commit/jscs.rb
+++ b/lib/overcommit/hook/pre_commit/jscs.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `jscs` (JavaScript Code Style Checker) against any modified JavaScript
# files.
diff --git a/lib/overcommit/hook/pre_commit/jsl.rb b/lib/overcommit/hook/pre_commit/jsl.rb
index 97a00057..98eb7175 100644
--- a/lib/overcommit/hook/pre_commit/jsl.rb
+++ b/lib/overcommit/hook/pre_commit/jsl.rb
@@ -1,12 +1,14 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `jsl` against any modified JavaScript files.
#
# @see http://www.javascriptlint.com/
class Jsl < Base
- MESSAGE_REGEX = /(?(?:\w:)?.+)\((?\d+)\):(?[^:]+)/
+ MESSAGE_REGEX = /(?(?:\w:)?.+)\((?\d+)\):(?[^:]+)/.freeze
MESSAGE_TYPE_CATEGORIZER = lambda do |type|
- type =~ /warning/ ? :warning : :error
+ type.match?(/warning/) ? :warning : :error
end
def run
diff --git a/lib/overcommit/hook/pre_commit/json_syntax.rb b/lib/overcommit/hook/pre_commit/json_syntax.rb
index bb6f785f..bd162f7d 100644
--- a/lib/overcommit/hook/pre_commit/json_syntax.rb
+++ b/lib/overcommit/hook/pre_commit/json_syntax.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks the syntax of any modified JSON files.
class JsonSyntax < Base
@@ -5,12 +7,10 @@ def run
messages = []
applicable_files.each do |file|
- begin
- JSON.parse(IO.read(file))
- rescue JSON::ParserError => e
- error = "#{e.message} parsing #{file}"
- messages << Overcommit::Hook::Message.new(:error, file, nil, error)
- end
+ JSON.parse(IO.read(file))
+ rescue JSON::ParserError => e
+ error = "#{e.message} parsing #{file}"
+ messages << Overcommit::Hook::Message.new(:error, file, nil, error)
end
messages
diff --git a/lib/overcommit/hook/pre_commit/kt_lint.rb b/lib/overcommit/hook/pre_commit/kt_lint.rb
new file mode 100644
index 00000000..4b81ed21
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/kt_lint.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `ktlint` against modified Kotlin files.
+ # @see https://github.com/shyiko/ktlint
+ class KtLint < Base
+ MESSAGE_REGEX = /((?[^:]+):(?\d+):(\d+):(?.+))/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ extract_messages(
+ result.stdout.split("\n").grep(MESSAGE_REGEX),
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/license_finder.rb b/lib/overcommit/hook/pre_commit/license_finder.rb
new file mode 100644
index 00000000..e2b8611b
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/license_finder.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs LicenseFinder if any of your package manager declaration files have changed
+ # See more about LicenseFinder at https://github.com/pivotal/LicenseFinder
+ class LicenseFinder < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/license_header.rb b/lib/overcommit/hook/pre_commit/license_header.rb
new file mode 100644
index 00000000..f129caf8
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/license_header.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Checks for license headers in source files
+ class LicenseHeader < Base
+ def run
+ begin
+ license_contents = license_lines
+ rescue Errno::ENOENT
+ return :fail, "Unable to load license file #{license_file}"
+ end
+
+ messages = applicable_files.map do |file|
+ check_file(file, license_contents)
+ end.compact
+
+ return :fail, messages.join("\n") if messages.any?
+
+ :pass
+ end
+
+ def check_file(file, license_contents)
+ File.readlines(file).each_with_index do |l, i|
+ if i >= license_contents.length
+ break
+ end
+
+ l.chomp!
+ unless l.end_with?(license_contents[i])
+ message = "#{file} missing header contents from line #{i} of "\
+ "#{license_file}: #{license_contents[i]}"
+ return message
+ end
+ end
+ end
+
+ def license_file
+ config['license_file']
+ end
+
+ def license_lines
+ @license_lines ||= begin
+ file_root = Overcommit::Utils.convert_glob_to_absolute(license_file)
+ File.read(file_root).split("\n")
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/line_endings.rb b/lib/overcommit/hook/pre_commit/line_endings.rb
new file mode 100644
index 00000000..ab66001b
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/line_endings.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Checks for line endings in files.
+ #
+ # WARNING: Works with Git 2.10.0 or newer.
+ class LineEndings < Base
+ def run
+ messages = []
+
+ offending_files.map do |file_name|
+ file = File.open(file_name)
+ begin
+ messages += check_file(file, file_name)
+ rescue ArgumentError => e
+ # File is likely a binary file which this check should ignore, but
+ # print a warning just in case
+ messages << Overcommit::Hook::Message.new(
+ :warning,
+ file_name,
+ file.lineno,
+ "#{file_name}:#{file.lineno}:#{e.message}"
+ )
+ end
+ end
+
+ messages
+ end
+
+ private
+
+ def check_file(file, file_name)
+ messages_for_file = []
+
+ file.each_line do |line|
+ # Remove configured line-ending
+ line.gsub!(/#{config['eol']}/, '')
+
+ # Detect any left over line-ending characters
+ next unless line.end_with?("\n", "\r")
+
+ messages_for_file << Overcommit::Hook::Message.new(
+ :error,
+ file_name,
+ file.lineno,
+ "#{file_name}:#{file.lineno}:#{line.inspect}"
+ )
+ end
+
+ messages_for_file
+ end
+
+ def offending_files
+ result = execute(%w[git ls-files --eol -z --], args: applicable_files)
+ raise 'Unable to access git tree' unless result.success?
+
+ result.stdout.split("\0").map do |file_info|
+ info, path = file_info.split("\t")
+ i = info.split.first
+ next if i == 'l/-text' # ignore binary files
+ next if i == "l/#{eol}"
+
+ path
+ end.compact
+ end
+
+ def eol
+ @eol ||= case config['eol']
+ when "\n"
+ 'lf'
+ when "\r\n"
+ 'crlf'
+ else
+ raise 'Invalid `eol` option specified: must be "\n" or "\r\n"'
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb b/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb
index 8ca6694e..bf3db4ed 100644
--- a/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb
+++ b/lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for local paths in files and issues a warning
class LocalPathsInGemfile < Base
diff --git a/lib/overcommit/hook/pre_commit/mdl.rb b/lib/overcommit/hook/pre_commit/mdl.rb
index 444e8652..56349d72 100644
--- a/lib/overcommit/hook/pre_commit/mdl.rb
+++ b/lib/overcommit/hook/pre_commit/mdl.rb
@@ -1,10 +1,10 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `mdl` against any modified Markdown files
#
# @see https://github.com/mivok/markdownlint
class Mdl < Base
- MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+)/
-
def run
result = execute(command, args: applicable_files)
output = result.stdout.chomp
@@ -13,11 +13,17 @@ def run
return [:fail, result.stderr] unless result.stderr.empty?
# example message:
- # path/to/file.md:1: MD001 Error message
- extract_messages(
- output.split("\n"),
- MESSAGE_REGEX
- )
+ # [{"filename":"file1.md","line":1,"rule":"MD013","aliases":["line-length"],
+ # "description":"Line length"}]
+ json_messages = JSON.parse(output)
+ json_messages.map do |message|
+ Overcommit::Hook::Message.new(
+ :error,
+ message['filename'],
+ message['line'],
+ "#{message['filename']}:#{message['line']} #{message['rule']} #{message['description']}"
+ )
+ end
end
end
end
diff --git a/lib/overcommit/hook/pre_commit/merge_conflicts.rb b/lib/overcommit/hook/pre_commit/merge_conflicts.rb
index 33965bd8..1619d3c6 100644
--- a/lib/overcommit/hook/pre_commit/merge_conflicts.rb
+++ b/lib/overcommit/hook/pre_commit/merge_conflicts.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for unresolved merge conflicts
class MergeConflicts < Base
diff --git a/lib/overcommit/hook/pre_commit/mix_format.rb b/lib/overcommit/hook/pre_commit/mix_format.rb
new file mode 100644
index 00000000..2fa07551
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/mix_format.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `mix format --check-formatted` against any modified ex/heex/exs files.
+ #
+ # @see https://hexdocs.pm/mix/main/Mix.Tasks.Format.html
+ class MixFormat < Base
+ # example message:
+ # ** (Mix) mix format failed due to --check-formatted.
+ # The following files are not formatted:
+ #
+ # * lib/file1.ex
+ # * lib/file2.ex
+ FILES_REGEX = /^\s+\*\s+(?.+)$/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ result.stderr.scan(FILES_REGEX).flatten.
+ map { |file| message(file) }
+ end
+
+ private
+
+ def message(file)
+ Overcommit::Hook::Message.new(:error, file, nil, file)
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/nginx_test.rb b/lib/overcommit/hook/pre_commit/nginx_test.rb
index 553ef0f5..b681ded0 100644
--- a/lib/overcommit/hook/pre_commit/nginx_test.rb
+++ b/lib/overcommit/hook/pre_commit/nginx_test.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `nginx -t` against any modified Nginx config files.
#
# @see https://www.nginx.com/resources/wiki/start/topics/tutorials/commandline/
class NginxTest < Base
- MESSAGE_REGEX = /^nginx: .+ in (?.+):(?\d+)$/
+ MESSAGE_REGEX = /^nginx: .+ in (?.+):(?\d+)$/.freeze
def run
messages = []
diff --git a/lib/overcommit/hook/pre_commit/pep257.rb b/lib/overcommit/hook/pre_commit/pep257.rb
index 339de9ea..01562039 100644
--- a/lib/overcommit/hook/pre_commit/pep257.rb
+++ b/lib/overcommit/hook/pre_commit/pep257.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `pep257` against any modified Python files.
#
diff --git a/lib/overcommit/hook/pre_commit/pep8.rb b/lib/overcommit/hook/pre_commit/pep8.rb
index 954b2f91..5c42a6d5 100644
--- a/lib/overcommit/hook/pre_commit/pep8.rb
+++ b/lib/overcommit/hook/pre_commit/pep8.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `pep8` against any modified Python files.
#
diff --git a/lib/overcommit/hook/pre_commit/php_cs.rb b/lib/overcommit/hook/pre_commit/php_cs.rb
new file mode 100644
index 00000000..41d2f2b4
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/php_cs.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `phpcs` against any modified PHP files.
+ class PhpCs < Base
+ # Parse `phpcs` csv mode output
+ MESSAGE_REGEX = /^\"(?.+)\",(?\d+),\d+,(?.+),\"(?.+)\"/.freeze
+ MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ 'error'.include?(type) ? :error : :warning
+ end
+
+ def run
+ messages = []
+
+ result = execute(command, args: applicable_files)
+ if result.status
+ messages = result.stdout.split("\n")
+ # Discard the csv header
+ messages.shift
+ end
+
+ return :fail if messages.empty? && !result.success?
+ return :pass if messages.empty?
+
+ parse_messages(messages)
+ end
+
+ # Transform the CSV output into a tidy human readable message
+ def parse_messages(messages)
+ output = []
+
+ messages.map do |message|
+ message.scan(MESSAGE_REGEX).map do |file, line, type, msg|
+ type = MESSAGE_TYPE_CATEGORIZER.call(type)
+ text = " #{file}:#{line}\n #{msg}"
+ output << Overcommit::Hook::Message.new(type, file, line.to_i, text)
+ end
+ end
+
+ output
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/php_cs_fixer.rb b/lib/overcommit/hook/pre_commit/php_cs_fixer.rb
new file mode 100644
index 00000000..87e63744
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/php_cs_fixer.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `php-cs-fixer` against any modified PHP files.
+ class PhpCsFixer < Base
+ MESSAGE_REGEX = /\s+\d+\)\s+(?.*\.php)(?\s+\(\w+(?:,\s+)?\))?/.freeze
+
+ def run
+ messages = []
+ feedback = ''
+
+ # Exit status for all of the runs. Should be zero!
+ exit_status_sum = 0
+
+ applicable_files.each do |file|
+ result = execute(command, args: [file])
+ output = result.stdout.chomp
+ exit_status_sum += result.status
+
+ if result.status
+ messages = output.lstrip.split("\n")
+ end
+ end
+
+ unless messages.empty?
+ feedback = parse_messages(messages)
+ end
+
+ :pass if exit_status_sum == 0
+ :pass if feedback.empty?
+
+ feedback
+ end
+
+ def parse_messages(messages)
+ output = []
+
+ messages.map do |message|
+ message.scan(MESSAGE_REGEX).map do |file, violated_rules|
+ type = :error
+ unless violated_rules.nil?
+ type = :warning
+ end
+ text = if type == :error
+ "Cannot process #{file}: Syntax error"
+ else
+ "#{file} has been fixed"
+ end
+
+ output << Overcommit::Hook::Message.new(type, file, 0, text)
+ end
+ end
+
+ output
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/php_lint.rb b/lib/overcommit/hook/pre_commit/php_lint.rb
new file mode 100644
index 00000000..f2ba2f24
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/php_lint.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `php -l` against any modified PHP files.
+ class PhpLint < Base
+ # Sample String
+ # rubocop:disable Layout/LineLength
+ # PHP Parse error: syntax error, unexpected 'require_once' (T_REQUIRE_ONCE) in site/sumo.php on line 12
+ # rubocop:enable Layout/LineLength
+ MESSAGE_REGEX = /^(?.+)\:\s+(?.+) in (?.+) on line (?\d+)/.freeze
+
+ def run
+ # A list of error messages
+ messages = []
+
+ # Exit status for all of the runs. Should be zero!
+ exit_status_sum = 0
+
+ # Run for each of our applicable files
+ applicable_files.each do |file|
+ result = execute(command, args: [file])
+ output = result.stdout.chomp
+ exit_status_sum += result.status
+ if result.status
+ # `php -l` returns with a leading newline, and we only need the first
+ # line, there is usually some redundancy
+ messages << output.lstrip.split("\n").first
+ end
+ end
+
+ # If the sum of all lint status is zero, then none had exit status
+ return :pass if exit_status_sum == 0
+
+ # No messages is great news for us
+ return :pass if messages.empty?
+
+ # Return the list of message objects
+ extract_messages(
+ messages,
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/php_stan.rb b/lib/overcommit/hook/pre_commit/php_stan.rb
new file mode 100644
index 00000000..9b8550f7
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/php_stan.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `phpstan` against any modified PHP files.
+ # For running `phpstan` with Laravel, it requires setup with `ide_helper`.
+ #
+ # References:
+ # https://github.com/phpstan/phpstan/issues/239
+ # https://gist.github.com/edmondscommerce/89695c9cd2584fefdf540fb1c528d2c2
+ class PhpStan < Base
+ MESSAGE_REGEX = /^(?.+)\:(?\d+)\:(?.+)/.freeze
+
+ def run
+ messages = []
+
+ result = execute(command, args: applicable_files)
+
+ unless result.success?
+ messages += result.stdout.lstrip.split("\n")
+ end
+
+ return :pass if messages.empty?
+
+ extract_messages(
+ messages,
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/pronto.rb b/lib/overcommit/hook/pre_commit/pronto.rb
new file mode 100644
index 00000000..664b4ef8
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/pronto.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/pronto'
+
+module Overcommit::Hook::PreCommit
+ # Runs `pronto`
+ #
+ # @see https://github.com/mmozuras/pronto
+ class Pronto < Base
+ include Overcommit::Hook::Shared::Pronto
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/puppet_lint.rb b/lib/overcommit/hook/pre_commit/puppet_lint.rb
index 14d01d9a..2ddf3a10 100644
--- a/lib/overcommit/hook/pre_commit/puppet_lint.rb
+++ b/lib/overcommit/hook/pre_commit/puppet_lint.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs 'puppet-lint' against any modified Puppet files.
#
# @see http://puppet-lint.com/
class PuppetLint < Base
- MESSAGE_REGEX = /(?(?:\w:)?.+):(?\d+):\d+:(?\w+)/
+ MESSAGE_REGEX = /(?(?:\w:)?.+):(?\d+):\d+:(?\w+)/.freeze
MESSAGE_TYPE_CATEGORIZER = lambda do |type|
type == 'ERROR' ? :error : :warning
diff --git a/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb b/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb
new file mode 100644
index 00000000..311a09c3
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ #
+ # Run's the Puppet metadata linter. It has support for adding options
+ # in the .overcommit.yaml
+ #
+ # @see https://voxpupuli.org/blog/2014/11/06/linting-metadata-json/
+ #
+ class PuppetMetadataJsonLint < Base
+ MESSAGE_REGEX = /\((?.*)\).*/.freeze
+
+ MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ type == 'WARN' ? :warning : :error
+ end
+
+ def run
+ result = execute(command, args: applicable_files)
+ output = result.stdout.chomp.gsub(/^"|"$/, '')
+ return :pass if result.success? && output.empty?
+
+ extract_messages(
+ output.split("\n"),
+ MESSAGE_REGEX,
+ MESSAGE_TYPE_CATEGORIZER
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/pycodestyle.rb b/lib/overcommit/hook/pre_commit/pycodestyle.rb
new file mode 100644
index 00000000..a15e7fb7
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/pycodestyle.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `pycodestyle` against any modified Python files.
+ #
+ # @see https://pypi.python.org/pypi/pycodestyle
+ class Pycodestyle < Base
+ def run
+ result = execute(command, args: applicable_files)
+ output = result.stdout.chomp
+
+ return :pass if result.success? && output.empty?
+
+ # example message:
+ # path/to/file.py:88:5: E301 expected 1 blank line, found 0
+ extract_messages(
+ output.split("\n"),
+ /^(?(?:\w:)?[^:]+):(?\d+):\d+:\s(?E|W)/,
+ lambda { |type| type.include?('W') ? :warning : :error }
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/pydocstyle.rb b/lib/overcommit/hook/pre_commit/pydocstyle.rb
new file mode 100644
index 00000000..3556f2bf
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/pydocstyle.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `pydocstyle` against any modified Python files.
+ #
+ # @see https://pypi.python.org/pypi/pydocstyle
+ class Pydocstyle < Base
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ output = result.stderr.chomp
+
+ # example message:
+ # path/to/file.py:1 in public method `foo`:
+ # D102: Docstring missing
+ extract_messages(
+ output.gsub(/:\s+/, ': ').split("\n"),
+ /^(?(?:\w:)?[^:]+):(?\d+)/
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/pyflakes.rb b/lib/overcommit/hook/pre_commit/pyflakes.rb
index 8c56ef1d..ff7824c4 100644
--- a/lib/overcommit/hook/pre_commit/pyflakes.rb
+++ b/lib/overcommit/hook/pre_commit/pyflakes.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `pyflakes` against any modified Python files.
#
# @see https://pypi.python.org/pypi/pyflakes
class Pyflakes < Base
- MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+):/
+ MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+):/.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/pylint.rb b/lib/overcommit/hook/pre_commit/pylint.rb
index 7ef6d57c..526bee2d 100644
--- a/lib/overcommit/hook/pre_commit/pylint.rb
+++ b/lib/overcommit/hook/pre_commit/pylint.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `pylint` against any modified Python files.
#
# @see http://www.pylint.org/
class Pylint < Base
- MESSAGE_REGEX = /^(?(?:\w:)?.+):(?\d+):(?[CEFRW])/
+ MESSAGE_REGEX = /^(?(?:\w:)?.+):(?\d+):(?[CEFRW])/.freeze
# Classify 'E' and 'F' message codes as errors,
# everything else as warnings.
diff --git a/lib/overcommit/hook/pre_commit/python_flake8.rb b/lib/overcommit/hook/pre_commit/python_flake8.rb
index 8ae893af..d506cf62 100644
--- a/lib/overcommit/hook/pre_commit/python_flake8.rb
+++ b/lib/overcommit/hook/pre_commit/python_flake8.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `flake8` against any modified Python files.
#
# @see https://pypi.python.org/pypi/flake8
class PythonFlake8 < Base
- MESSAGE_REGEX = /^(?(?:\w:)?.+):(?\d+):\d+:\s(?\w\d+)/
+ MESSAGE_REGEX = /^(?(?:\w:)?.+):(?\d+):\d+:\s(?\w\d+)/.freeze
# Classify 'Exxx' and 'Fxxx' message codes as errors,
# everything else as warnings.
diff --git a/lib/overcommit/hook/pre_commit/r_spec.rb b/lib/overcommit/hook/pre_commit/r_spec.rb
new file mode 100644
index 00000000..26bbc0a8
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/r_spec.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/r_spec'
+
+module Overcommit::Hook::PreCommit
+ # Runs `rspec` test suite
+ #
+ # @see http://rspec.info/
+ class RSpec < Base
+ include Overcommit::Hook::Shared::RSpec
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/rails_best_practices.rb b/lib/overcommit/hook/pre_commit/rails_best_practices.rb
index ee04bc7e..7c3ba79a 100644
--- a/lib/overcommit/hook/pre_commit/rails_best_practices.rb
+++ b/lib/overcommit/hook/pre_commit/rails_best_practices.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit
module Hook
module PreCommit
@@ -5,10 +7,10 @@ module PreCommit
#
# @see https://github.com/railsbp/rails_best_practices
class RailsBestPractices < Base
- ERROR_REGEXP = /^(?(?:\w:)?[^:]+):(?\d+)\s-\s(?.+)/
+ ERROR_REGEXP = /^(?(?:\w:)?[^:]+):(?\d+)\s-\s(?.+)/.freeze
def run
- result = execute(command)
+ result = execute(command, args: applicable_files)
return :pass if result.success?
return [:fail, result.stderr] unless result.stderr.empty?
diff --git a/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb b/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb
index f6a83ec6..61725074 100644
--- a/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb
+++ b/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb
@@ -1,10 +1,15 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
- # Check to see whether the schema file is in line with the migrations
+ # Check to see whether the schema file is in line with the migrations. When a
+ # schema file is present but a migration file is not, this is usually a
+ # failure. The exception is if the schema is at version 0 (i.e before any
+ # migrations have been run). In this case it is OK if there are no migrations.
class RailsSchemaUpToDate < Base
- def run # rubocop:disable CyclomaticComplexity, PerceivedComplexity
+ def run # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
if migration_files.any? && schema_files.none?
return :fail, "It looks like you're adding a migration, but did not update the schema file"
- elsif migration_files.none? && schema_files.any?
+ elsif migration_files.none? && schema_files.any? && non_zero_schema_version?
return :fail, "You're trying to change the schema without adding a migration file"
elsif migration_files.any? && schema_files.any?
# Get the latest version from the migration filename. Use
@@ -13,9 +18,8 @@ def run # rubocop:disable CyclomaticComplexity, PerceivedComplexity
# their username.
latest_version = migration_files.map do |file|
File.basename(file)[/\d+/]
- end.sort.last
+ end.max
- schema = schema_files.map { |file| File.read(file) }.join
up_to_date = schema.include?(latest_version)
unless up_to_date
@@ -30,6 +34,12 @@ def run # rubocop:disable CyclomaticComplexity, PerceivedComplexity
private
+ def encoding
+ return unless @config.key?('encoding')
+
+ { encoding: @config['encoding'] }.compact
+ end
+
def migration_files
@migration_files ||= applicable_files.select do |file|
file.match %r{db/migrate/.*\.rb}
@@ -41,5 +51,14 @@ def schema_files
file.match %r{db/schema\.rb|db/structure.*\.sql}
end
end
+
+ def schema
+ @schema ||= schema_files.map { |file| File.read(file, **(encoding || {})) }.join
+ @schema.tr('_', '')
+ end
+
+ def non_zero_schema_version?
+ schema =~ /\d{14}/
+ end
end
end
diff --git a/lib/overcommit/hook/pre_commit/rake_target.rb b/lib/overcommit/hook/pre_commit/rake_target.rb
new file mode 100644
index 00000000..8ee36ba6
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/rake_target.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/rake_target'
+
+module Overcommit::Hook::PreCommit
+ # Runs rake targets
+ #
+ # @see Overcommit::Hook::Shared::RakeTarget
+ class RakeTarget < Base
+ include Overcommit::Hook::Shared::RakeTarget
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/reek.rb b/lib/overcommit/hook/pre_commit/reek.rb
index 4574a297..163d7206 100644
--- a/lib/overcommit/hook/pre_commit/reek.rb
+++ b/lib/overcommit/hook/pre_commit/reek.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `reek` against any modified Ruby files.
#
diff --git a/lib/overcommit/hook/pre_commit/rst_lint.rb b/lib/overcommit/hook/pre_commit/rst_lint.rb
new file mode 100644
index 00000000..5647508d
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/rst_lint.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `rst-lint` against any modified reStructuredText files
+ #
+ # @see https://github.com/twolfson/restructuredtext-lint
+ class RstLint < Base
+ MESSAGE_REGEX = /
+ ^(?INFO|WARNING|ERROR|SEVERE)(?(?:\w:)?[^:]+):(?\d+)\s(?.+)
+ /x.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ output = result.stdout.chomp
+
+ return :pass if result.success?
+ return [:fail, result.stderr] unless result.stderr.empty?
+
+ # example message:
+ # WARNING README.rst:7 Title underline too short.
+ extract_messages(
+ output.split("\n"),
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/rubo_cop.rb b/lib/overcommit/hook/pre_commit/rubo_cop.rb
index 2fe17207..4e3b3353 100644
--- a/lib/overcommit/hook/pre_commit/rubo_cop.rb
+++ b/lib/overcommit/hook/pre_commit/rubo_cop.rb
@@ -1,10 +1,12 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `rubocop` against any modified Ruby files.
#
# @see http://batsov.com/rubocop/
class RuboCop < Base
GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
- type =~ /^warn/ ? :warning : :error
+ type.match?(/^warn/) ? :warning : :error
end
COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type|
diff --git a/lib/overcommit/hook/pre_commit/ruby_lint.rb b/lib/overcommit/hook/pre_commit/ruby_lint.rb
index 6e0c23da..63403b22 100644
--- a/lib/overcommit/hook/pre_commit/ruby_lint.rb
+++ b/lib/overcommit/hook/pre_commit/ruby_lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `ruby-lint` against any modified Ruby files.
#
diff --git a/lib/overcommit/hook/pre_commit/ruby_syntax.rb b/lib/overcommit/hook/pre_commit/ruby_syntax.rb
new file mode 100644
index 00000000..ac31e5ed
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/ruby_syntax.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `ruby -c` against all Ruby files.
+ #
+ class RubySyntax < Base
+ MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ type.match?(/^(syntax)?\s*error/) ? :error : :warning
+ end
+
+ def run
+ result = execute(command, args: applicable_files)
+
+ result_lines = result.stderr.split("\n")
+
+ return :pass if result_lines.length.zero?
+
+ # Example message:
+ # path/to/file.rb:1: syntax error, unexpected '^'
+ extract_messages(
+ result_lines,
+ /^(?[^:]+):(?\d+):\s*(?[^,]+),\s*(?.+)/,
+ MESSAGE_TYPE_CATEGORIZER
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/scalariform.rb b/lib/overcommit/hook/pre_commit/scalariform.rb
index 3822a80e..63bf5e6c 100644
--- a/lib/overcommit/hook/pre_commit/scalariform.rb
+++ b/lib/overcommit/hook/pre_commit/scalariform.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `scalariform` against any modified Scala files.
#
# @see https://github.com/mdr/scalariform
class Scalariform < Base
- MESSAGE_REGEX = /^\[(?FAILED|ERROR)\]\s+(?(?:\w:)?.+)/
+ MESSAGE_REGEX = /^\[(?FAILED|ERROR)\]\s+(?(?:\w:)?.+)/.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/scalastyle.rb b/lib/overcommit/hook/pre_commit/scalastyle.rb
index 6aa4017c..6494521d 100644
--- a/lib/overcommit/hook/pre_commit/scalastyle.rb
+++ b/lib/overcommit/hook/pre_commit/scalastyle.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `scalastyle` against any modified Scala files.
#
@@ -6,9 +8,9 @@ class Scalastyle < Base
MESSAGE_REGEX = /
^(?error|warning)\s
file=(?(?:\w:)?.+)\s
- message=.+\s
- line=(?\d+)
- /x
+ message=.+\s*
+ (line=(?\d+))?
+ /x.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/scss_lint.rb b/lib/overcommit/hook/pre_commit/scss_lint.rb
index e6e45f07..1bbaba93 100644
--- a/lib/overcommit/hook/pre_commit/scss_lint.rb
+++ b/lib/overcommit/hook/pre_commit/scss_lint.rb
@@ -1,12 +1,10 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `scss-lint` against any modified SCSS files.
#
- # @see https://github.com/brigade/scss-lint
+ # @see https://github.com/sds/scss-lint
class ScssLint < Base
- MESSAGE_TYPE_CATEGORIZER = lambda do |type|
- type.include?('W') ? :warning : :error
- end
-
def run
result = execute(command, args: applicable_files)
@@ -16,13 +14,30 @@ def run
return :pass if [0, 81].include?(result.status)
# Any status that isn't indicating lint warnings or errors indicates failure
- return :fail, result.stdout unless [1, 2].include?(result.status)
+ return :fail, (result.stdout + result.stderr) unless [1, 2].include?(result.status)
+
+ begin
+ collect_lint_messages(JSON.parse(result.stdout))
+ rescue JSON::ParserError => e
+ [:fail, "Unable to parse JSON returned by SCSS-Lint: #{e.message}\n" \
+ "STDOUT: #{result.stdout}\nSTDERR: #{result.stderr}"]
+ end
+ end
+
+ private
+
+ def collect_lint_messages(files_to_lints)
+ files_to_lints.flat_map do |path, lints|
+ lints.map do |lint|
+ severity = lint['severity'] == 'warning' ? :warning : :error
+
+ message = lint['reason']
+ message = "#{lint['linter']}: #{message}" if lint['linter']
+ message = "#{path}:#{lint['line']} #{message}"
- extract_messages(
- result.stdout.split("\n"),
- /^(?(?:\w:)?[^:]+):(?\d+)[^ ]* (?[^ ]+)/,
- MESSAGE_TYPE_CATEGORIZER,
- )
+ Overcommit::Hook::Message.new(severity, path, lint['line'], message)
+ end
+ end
end
end
end
diff --git a/lib/overcommit/hook/pre_commit/semi_standard.rb b/lib/overcommit/hook/pre_commit/semi_standard.rb
index d88afb28..f6746d2f 100644
--- a/lib/overcommit/hook/pre_commit/semi_standard.rb
+++ b/lib/overcommit/hook/pre_commit/semi_standard.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `semistandard` against any modified JavaScript files.
#
# @see https://github.com/Flet/semistandard
class SemiStandard < Base
- MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+)/
+ MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+)/.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/shell_check.rb b/lib/overcommit/hook/pre_commit/shell_check.rb
index d918c95c..03c7a64f 100644
--- a/lib/overcommit/hook/pre_commit/shell_check.rb
+++ b/lib/overcommit/hook/pre_commit/shell_check.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `shellcheck` against any modified shell script files.
#
diff --git a/lib/overcommit/hook/pre_commit/slim_lint.rb b/lib/overcommit/hook/pre_commit/slim_lint.rb
index e373bcb5..8e1d1ce0 100644
--- a/lib/overcommit/hook/pre_commit/slim_lint.rb
+++ b/lib/overcommit/hook/pre_commit/slim_lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `slim-lint` against any modified Slim templates.
#
diff --git a/lib/overcommit/hook/pre_commit/sorbet.rb b/lib/overcommit/hook/pre_commit/sorbet.rb
new file mode 100644
index 00000000..57988a6e
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/sorbet.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs 'srb tc' against any modified files.
+ #
+ # @see https://github.com/sorbet/sorbet
+ class Sorbet < Base
+ # example of output:
+ # sorbet.rb:1: Method `foo` does not exist on `T.class_of(Bar)` https://srb.help/7003
+ MESSAGE_REGEX = /^(?[^:]+):(?\d+): (?.*)$/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ output = result.stderr.split("\n").grep(MESSAGE_REGEX)
+
+ extract_messages(
+ output,
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/sqlint.rb b/lib/overcommit/hook/pre_commit/sqlint.rb
index b5cae3c6..2e5c5a88 100644
--- a/lib/overcommit/hook/pre_commit/sqlint.rb
+++ b/lib/overcommit/hook/pre_commit/sqlint.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs 'sqlint' against any modified SQL files.
#
# @see https://github.com/purcell/sqlint
class Sqlint < Base
- MESSAGE_REGEX = /(?(?:\w:)?.+):(?\d+):\d+:(?\w+)/
+ MESSAGE_REGEX = /(?(?:\w:)?.+):(?\d+):\d+:(?\w+)/.freeze
MESSAGE_TYPE_CATEGORIZER = lambda do |type|
type == 'ERROR' ? :error : :warning
diff --git a/lib/overcommit/hook/pre_commit/standard.rb b/lib/overcommit/hook/pre_commit/standard.rb
index ae8dfa5d..7b70ab2c 100644
--- a/lib/overcommit/hook/pre_commit/standard.rb
+++ b/lib/overcommit/hook/pre_commit/standard.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `standard` against any modified JavaScript files.
#
# @see https://github.com/feross/standard
class Standard < Base
- MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+)/
+ MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+)/.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/stylelint.rb b/lib/overcommit/hook/pre_commit/stylelint.rb
new file mode 100644
index 00000000..aae26f08
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/stylelint.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `stylelint` against any modified CSS file.
+ #
+ # @see https://github.com/stylelint/stylelint
+ class Stylelint < Base
+ # example of output:
+ # index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation)
+
+ MESSAGE_REGEX = /^(?[^:]+):\D*(?\d+).*$/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ output = result.stdout + result.stderr.chomp
+ return :pass if result.success? && output.empty?
+
+ extract_messages(
+ output.split("\n"),
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/swift_lint.rb b/lib/overcommit/hook/pre_commit/swift_lint.rb
new file mode 100644
index 00000000..a1d8ef09
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/swift_lint.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `swiftlint lint` against modified Swift files.
+ # @see https://github.com/realm/SwiftLint
+ class SwiftLint < Base
+ MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+)[^ ]* (?[^ ]+):(?.*)/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ return :pass if result.success?
+
+ extract_messages(
+ result.stdout.split("\n").grep(MESSAGE_REGEX),
+ MESSAGE_REGEX
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/terraform_format.rb b/lib/overcommit/hook/pre_commit/terraform_format.rb
new file mode 100644
index 00000000..905a55eb
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/terraform_format.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs 'terraform fmt' against any modified *.tf files.
+ #
+ # @see https://www.terraform.io/docs/commands/fmt.html
+ class TerraformFormat < Base
+ def run
+ messages = []
+ applicable_files.each do |f|
+ result = execute(command, args: [f])
+ unless result.success?
+ messages << Overcommit::Hook::Message.new(:error, f, nil, "violation found in #{f}")
+ end
+ end
+ messages
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/trailing_whitespace.rb b/lib/overcommit/hook/pre_commit/trailing_whitespace.rb
index b17e2871..62431cad 100644
--- a/lib/overcommit/hook/pre_commit/trailing_whitespace.rb
+++ b/lib/overcommit/hook/pre_commit/trailing_whitespace.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks for trailing whitespace in files.
class TrailingWhitespace < Base
diff --git a/lib/overcommit/hook/pre_commit/travis_lint.rb b/lib/overcommit/hook/pre_commit/travis_lint.rb
index 9909a072..9fb4c681 100644
--- a/lib/overcommit/hook/pre_commit/travis_lint.rb
+++ b/lib/overcommit/hook/pre_commit/travis_lint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `travis-lint` against any modified Travis CI files.
#
diff --git a/lib/overcommit/hook/pre_commit/ts_lint.rb b/lib/overcommit/hook/pre_commit/ts_lint.rb
new file mode 100644
index 00000000..1340cbcf
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/ts_lint.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `tslint` against modified TypeScript files.
+ # @see http://palantir.github.io/tslint/
+ class TsLint < Base
+ # example message:
+ # "src/file/anotherfile.ts[298, 1]: exceeds maximum line length of 140"
+ # or
+ # "ERROR: src/AccountController.ts[4, 28]: expected call-signature to have a typedef"
+ MESSAGE_REGEX = /^(?.+: )?(?.+?(?=\[))[^\d]+(?\d+).*?/.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ output = result.stdout.chomp
+ return :pass if result.success? && output.empty?
+
+ output_lines = output.split("\n").map(&:strip).reject(&:empty?)
+ type_categorizer = ->(type) { type.nil? || type.include?('ERROR') ? :error : :warning }
+
+ extract_messages(
+ output_lines,
+ MESSAGE_REGEX,
+ type_categorizer
+ )
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/vint.rb b/lib/overcommit/hook/pre_commit/vint.rb
index 91169e82..17d8f9a2 100644
--- a/lib/overcommit/hook/pre_commit/vint.rb
+++ b/lib/overcommit/hook/pre_commit/vint.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `vint` against any modified Vim script files.
#
diff --git a/lib/overcommit/hook/pre_commit/w3c_css.rb b/lib/overcommit/hook/pre_commit/w3c_css.rb
index 0193ea3e..cb32c109 100644
--- a/lib/overcommit/hook/pre_commit/w3c_css.rb
+++ b/lib/overcommit/hook/pre_commit/w3c_css.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `w3c_validators` against any modified CSS files.
#
@@ -40,10 +42,10 @@ def validator
def opts
@opts ||= {
validator_uri: config['validator_uri'],
- proxy_server: config['proxy_server'],
- proxy_port: config['proxy_port'],
- proxy_user: config['proxy_user'],
- proxy_pass: config['proxy_pass']
+ proxy_server: config['proxy_server'],
+ proxy_port: config['proxy_port'],
+ proxy_user: config['proxy_user'],
+ proxy_pass: config['proxy_pass']
}
end
diff --git a/lib/overcommit/hook/pre_commit/w3c_html.rb b/lib/overcommit/hook/pre_commit/w3c_html.rb
index ae6a8a7c..945e7997 100644
--- a/lib/overcommit/hook/pre_commit/w3c_html.rb
+++ b/lib/overcommit/hook/pre_commit/w3c_html.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `w3c_validators` against any modified HTML files.
#
@@ -40,10 +42,10 @@ def validator
def opts
@opts ||= {
validator_uri: config['validator_uri'],
- proxy_server: config['proxy_server'],
- proxy_port: config['proxy_port'],
- proxy_user: config['proxy_user'],
- proxy_pass: config['proxy_pass']
+ proxy_server: config['proxy_server'],
+ proxy_port: config['proxy_port'],
+ proxy_user: config['proxy_user'],
+ proxy_pass: config['proxy_pass']
}
end
diff --git a/lib/overcommit/hook/pre_commit/xml_lint.rb b/lib/overcommit/hook/pre_commit/xml_lint.rb
index 0be13e82..71cb403f 100644
--- a/lib/overcommit/hook/pre_commit/xml_lint.rb
+++ b/lib/overcommit/hook/pre_commit/xml_lint.rb
@@ -1,9 +1,11 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Runs `xmllint` against any modified XML files.
#
# @see http://xmlsoft.org/xmllint.html
class XmlLint < Base
- MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+):/
+ MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+):/.freeze
def run
result = execute(command, args: applicable_files)
diff --git a/lib/overcommit/hook/pre_commit/xml_syntax.rb b/lib/overcommit/hook/pre_commit/xml_syntax.rb
index a84ac145..99465182 100644
--- a/lib/overcommit/hook/pre_commit/xml_syntax.rb
+++ b/lib/overcommit/hook/pre_commit/xml_syntax.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks the syntax of any modified XML files.
class XmlSyntax < Base
@@ -5,12 +7,10 @@ def run
messages = []
applicable_files.each do |file|
- begin
- REXML::Document.new(IO.read(file))
- rescue REXML::ParseException => e
- error = "Error parsing #{file}: #{e.message}"
- messages << Overcommit::Hook::Message.new(:error, file, nil, error)
- end
+ REXML::Document.new(IO.read(file))
+ rescue REXML::ParseException => e
+ error = "Error parsing #{file}: #{e.message}"
+ messages << Overcommit::Hook::Message.new(:error, file, nil, error)
end
messages
diff --git a/lib/overcommit/hook/pre_commit/yaml_lint.rb b/lib/overcommit/hook/pre_commit/yaml_lint.rb
new file mode 100644
index 00000000..f780725f
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/yaml_lint.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Runs `YAMLLint` against any modified YAML files.
+ #
+ # @see https://github.com/adrienverge/yamllint
+ class YamlLint < Base
+ MESSAGE_REGEX = /
+ ^(?.+)
+ :(?\d+)
+ :(?\d+)
+ :\s\[(?\w+)\]
+ \s(?.+)$
+ /x.freeze
+
+ def run
+ result = execute(command, args: applicable_files)
+ parse_messages(result.stdout)
+ end
+
+ private
+
+ def parse_messages(output)
+ repo_root = Overcommit::Utils.repo_root
+
+ output.scan(MESSAGE_REGEX).map do |file, line, col, type, msg|
+ line = line.to_i
+ type = type.to_sym
+ # Obtain the path relative to the root of the repository
+ # for nicer output:
+ relpath = file.dup
+ relpath.slice!("#{repo_root}/")
+
+ text = "#{relpath}:#{line}:#{col}:#{type} #{msg}"
+ Overcommit::Hook::Message.new(type, file, line, text)
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/yaml_syntax.rb b/lib/overcommit/hook/pre_commit/yaml_syntax.rb
index 9562087e..83ff6789 100644
--- a/lib/overcommit/hook/pre_commit/yaml_syntax.rb
+++ b/lib/overcommit/hook/pre_commit/yaml_syntax.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreCommit
# Checks the syntax of any modified YAML files.
class YamlSyntax < Base
@@ -5,14 +7,25 @@ def run
messages = []
applicable_files.each do |file|
+ YAML.load_file(file, aliases: true)
+ rescue ArgumentError
begin
YAML.load_file(file)
rescue ArgumentError, Psych::SyntaxError => e
messages << Overcommit::Hook::Message.new(:error, file, nil, e.message)
end
+ rescue Psych::DisallowedClass => e
+ messages << error_message(file, e)
end
messages
end
+
+ private
+
+ def error_message(file, error)
+ text = "#{file}: #{error.message}"
+ Overcommit::Hook::Message.new(:error, file, nil, text)
+ end
end
end
diff --git a/lib/overcommit/hook/pre_commit/yard_coverage.rb b/lib/overcommit/hook/pre_commit/yard_coverage.rb
new file mode 100644
index 00000000..c9188edb
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/yard_coverage.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Class to check yard documentation coverage.
+ #
+ # Use option "min_coverage_percentage" in your YardCoverage configuration
+ # to set your desired documentation coverage percentage.
+ #
+ class YardCoverage < Base
+ def run
+ # Run a no-stats yard command to get the coverage
+ args = flags + applicable_files
+ result = execute(command, args: args)
+
+ warnings_and_stats_text, undocumented_objects_text =
+ result.stdout.split('Undocumented Objects:')
+
+ warnings_and_stats = warnings_and_stats_text.strip.split("\n")
+
+ # Stats are the last 7 lines before the undocumented objects
+ stats = warnings_and_stats.slice(-7, 7)
+
+ # If no stats present (shouldn't happen), warn the user and end
+ if stats.class != Array || stats.length != 7
+ return [:warn, 'Impossible to read the yard stats. Please, check your yard installation.']
+ end
+
+ # Check the yard coverage
+ yard_coverage = check_yard_coverage(stats)
+ if yard_coverage == :warn
+ return [
+ :warn,
+ 'Impossible to read yard doc coverage. Please, check your yard installation.'
+ ]
+ end
+ return :pass if yard_coverage == :pass
+
+ error_messages(yard_coverage, undocumented_objects_text)
+ end
+
+ private
+
+ # Check the yard coverage
+ #
+ # Return a :pass if the coverage is enough, :warn if it couldn't be read,
+ # otherwise, it has been read successfully.
+ #
+ def check_yard_coverage(stat_lines)
+ if config['min_coverage_percentage']
+ match = stat_lines.last.match(/^\s*([\d.]+)%\s+documented\s*$/)
+ unless match
+ return :warn
+ end
+
+ yard_coverage = match.captures[0].to_f
+ if yard_coverage >= config['min_coverage_percentage'].to_f
+ return :pass
+ end
+
+ yard_coverage
+ end
+ end
+
+ # Create the error messages
+ def error_messages(yard_coverage, error_text)
+ first_message = "You have a #{yard_coverage}% yard documentation coverage. "\
+ "#{config['min_coverage_percentage']}% is the minimum required."
+
+ # Add the undocumented objects text as error messages
+ messages = [Overcommit::Hook::Message.new(:error, nil, nil, first_message)]
+
+ errors = error_text.strip.split("\n")
+ errors.each do |undocumented_object|
+ undocumented_object_message, file_info = undocumented_object.split(/:?\s+/)
+ file_info_match = file_info.match(/^\(([^:]+):(\d+)\)/)
+
+ # In case any compacted error does not follow the format, ignore it
+ if file_info_match
+ file = file_info_match.captures[0]
+ line = file_info_match.captures[1]
+ messages << Overcommit::Hook::Message.new(
+ :error, file, line, "#{file}:#{line}: #{undocumented_object_message}"
+ )
+ end
+ end
+ messages
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_commit/yarn_check.rb b/lib/overcommit/hook/pre_commit/yarn_check.rb
new file mode 100644
index 00000000..f51402d3
--- /dev/null
+++ b/lib/overcommit/hook/pre_commit/yarn_check.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PreCommit
+ # Check if local yarn.lock matches package.json when either changes, unless
+ # yarn.lock is ignored by git.
+ #
+ # @see https://yarnpkg.com/en/docs/cli/check
+ class YarnCheck < Base
+ LOCK_FILE = 'yarn.lock'
+
+ # A lot of the errors returned by `yarn check` are outside the developer's control
+ # (are caused by bad package specification, in the hands of the upstream maintainer)
+ # So limit reporting to errors the developer can do something about
+ ACTIONABLE_ERRORS = [
+ 'Lockfile does not contain pattern',
+ ].freeze
+
+ def run
+ # Ignore if yarn.lock is not tracked by git
+ ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n")
+ return :pass if ignored_files.include?(LOCK_FILE)
+
+ previous_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil
+ result = execute(command)
+ new_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil
+
+ # `yarn check` also throws many warnings, which should be ignored here
+ errors_regex = Regexp.new("^error (.*)(#{ACTIONABLE_ERRORS.join('|')})(.*)$")
+ errors = errors_regex.match(result.stderr)
+ unless errors.nil? && previous_lockfile == new_lockfile
+ return :fail, "#{LOCK_FILE} is not up-to-date -- run `yarn install`"
+ end
+
+ :pass
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/base.rb b/lib/overcommit/hook/pre_push/base.rb
index 7f07a9ac..35e2b3a8 100644
--- a/lib/overcommit/hook/pre_push/base.rb
+++ b/lib/overcommit/hook/pre_push/base.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require 'forwardable'
+require 'overcommit/utils/messages_utils'
module Overcommit::Hook::PrePush
# Functionality common to all pre-push hooks.
@@ -6,5 +9,25 @@ class Base < Overcommit::Hook::Base
extend Forwardable
def_delegators :@context, :remote_name, :remote_url, :pushed_refs
+
+ def run?
+ super &&
+ !exclude_remotes.include?(remote_name) &&
+ (include_remote_ref_deletions? || !@context.remote_ref_deletion?)
+ end
+
+ private
+
+ def extract_messages(*args)
+ Overcommit::Utils::MessagesUtils.extract_messages(*args)
+ end
+
+ def exclude_remotes
+ @config['exclude_remotes'] || []
+ end
+
+ def include_remote_ref_deletions?
+ @config['include_remote_ref_deletions']
+ end
end
end
diff --git a/lib/overcommit/hook/pre_commit/brakeman.rb b/lib/overcommit/hook/pre_push/brakeman.rb
similarity index 50%
rename from lib/overcommit/hook/pre_commit/brakeman.rb
rename to lib/overcommit/hook/pre_push/brakeman.rb
index d8b7e793..c4fbf482 100644
--- a/lib/overcommit/hook/pre_commit/brakeman.rb
+++ b/lib/overcommit/hook/pre_push/brakeman.rb
@@ -1,10 +1,12 @@
-module Overcommit::Hook::PreCommit
- # Runs `brakeman` against any modified Ruby/Rails files.
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `brakeman` whenever Ruby/Rails files change.
#
# @see http://brakemanscanner.org/
class Brakeman < Base
def run
- result = execute(command + [applicable_files.join(',')])
+ result = execute(command)
return :pass if result.success?
[:fail, result.stdout]
diff --git a/lib/overcommit/hook/pre_push/cargo_test.rb b/lib/overcommit/hook/pre_push/cargo_test.rb
new file mode 100644
index 00000000..ab782e0c
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/cargo_test.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `cargo test` before push if Rust files changed
+ class CargoTest < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ [:fail, result.stdout]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/flutter_test.rb b/lib/overcommit/hook/pre_push/flutter_test.rb
new file mode 100644
index 00000000..26c987cf
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/flutter_test.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs Flutter test suite (`flutter test`) before push
+ #
+ # @see https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html
+ class FlutterTest < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/go_test.rb b/lib/overcommit/hook/pre_push/go_test.rb
new file mode 100644
index 00000000..b6b732a6
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/go_test.rb
@@ -0,0 +1,14 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `go test ./...` command on prepush
+ class GoTest < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/golangci_lint.rb b/lib/overcommit/hook/pre_push/golangci_lint.rb
new file mode 100644
index 00000000..42df41b0
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/golangci_lint.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs golangci-lint
+ #
+ # @see https://github.com/golangci/golangci-lint
+ class GolangciLint < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/minitest.rb b/lib/overcommit/hook/pre_push/minitest.rb
index 660d122d..4743665e 100644
--- a/lib/overcommit/hook/pre_push/minitest.rb
+++ b/lib/overcommit/hook/pre_push/minitest.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PrePush
# Runs `minitest` test suite before push
#
@@ -10,5 +12,9 @@ def run
output = result.stdout + result.stderr
[:fail, output]
end
+
+ def command
+ super + included_files.map { |file| "-r#{file}" }
+ end
end
end
diff --git a/lib/overcommit/hook/pre_push/mix_test.rb b/lib/overcommit/hook/pre_push/mix_test.rb
new file mode 100644
index 00000000..3ce005e9
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/mix_test.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `mix test` test suite before push
+ #
+ # @see https://hexdocs.pm/mix/Mix.Tasks.Test.html
+ class MixTest < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/php_unit.rb b/lib/overcommit/hook/pre_push/php_unit.rb
new file mode 100644
index 00000000..ec500597
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/php_unit.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `phpunit` test suite before push
+ #
+ # @see https://phpunit.de/
+ class PhpUnit < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/pronto.rb b/lib/overcommit/hook/pre_push/pronto.rb
new file mode 100644
index 00000000..93a0724e
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/pronto.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/pronto'
+
+module Overcommit::Hook::PrePush
+ # Runs `pronto`
+ #
+ # @see https://github.com/mmozuras/pronto
+ class Pronto < Base
+ include Overcommit::Hook::Shared::Pronto
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/protected_branches.rb b/lib/overcommit/hook/pre_push/protected_branches.rb
index 2d1d05b2..aca3f2ec 100644
--- a/lib/overcommit/hook/pre_push/protected_branches.rb
+++ b/lib/overcommit/hook/pre_push/protected_branches.rb
@@ -1,5 +1,9 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PrePush
- # Prevents destructive updates to specified branches.
+ # Prevents updates to specified branches.
+ # Accepts a 'destructive_only' option globally or per branch
+ # to only prevent destructive updates.
class ProtectedBranches < Base
def run
return :pass unless illegal_pushes.any?
@@ -15,20 +19,56 @@ def run
def illegal_pushes
@illegal_pushes ||= pushed_refs.select do |pushed_ref|
- protected?(pushed_ref.remote_ref) && pushed_ref.destructive?
+ protected?(pushed_ref)
end
end
- def protected?(remote_ref)
+ def protected?(ref)
+ find_pattern(ref.remote_ref)&.destructive?(ref)
+ end
+
+ def find_pattern(remote_ref)
ref_name = remote_ref[%r{refs/heads/(.*)}, 1]
- protected_branch_patterns.any? do |pattern|
- File.fnmatch(pattern, ref_name)
+ return if ref_name.nil?
+
+ patterns.find do |pattern|
+ File.fnmatch(pattern.to_s, ref_name)
end
end
- def protected_branch_patterns
- @protected_branch_patterns ||= Array(config['branches']).
- concat(Array(config['branch_patterns']))
+ def patterns
+ @patterns ||= fetch_patterns
+ end
+
+ def fetch_patterns
+ branch_configurations.map do |pattern|
+ if pattern.is_a?(Hash)
+ Pattern.new(pattern.keys.first, pattern['destructive_only'])
+ else
+ Pattern.new(pattern, global_destructive_only?)
+ end
+ end
+ end
+
+ def branch_configurations
+ config['branches'].to_a + config['branch_patterns'].to_a
+ end
+
+ def global_destructive_only?
+ config['destructive_only'].nil? || config['destructive_only']
+ end
+
+ Pattern = Struct.new('Pattern', :name, :destructive_only) do
+ alias_method :to_s, :name
+ alias_method :destructive_only?, :destructive_only
+
+ def destructive?(ref)
+ if destructive_only?
+ ref.destructive?
+ else
+ true
+ end
+ end
end
end
end
diff --git a/lib/overcommit/hook/pre_push/pub_test.rb b/lib/overcommit/hook/pre_push/pub_test.rb
new file mode 100644
index 00000000..0dbfb0bf
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/pub_test.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs Dart test suite (`pub run test`) before push
+ #
+ # @see https://pub.dev/packages/test#running-tests
+ class PubTest < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/pytest.rb b/lib/overcommit/hook/pre_push/pytest.rb
new file mode 100644
index 00000000..35c11561
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/pytest.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `pytest` test suite before push
+ #
+ # @see https://github.com/pytest-dev/pytest
+ class Pytest < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/python_nose.rb b/lib/overcommit/hook/pre_push/python_nose.rb
new file mode 100644
index 00000000..94476e0e
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/python_nose.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `nose` test suite before push
+ #
+ # @see https://nose.readthedocs.io/en/latest/
+ class PythonNose < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/r_spec.rb b/lib/overcommit/hook/pre_push/r_spec.rb
index e9f4ebad..7ce3df29 100644
--- a/lib/overcommit/hook/pre_push/r_spec.rb
+++ b/lib/overcommit/hook/pre_push/r_spec.rb
@@ -1,14 +1,12 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/r_spec'
+
module Overcommit::Hook::PrePush
- # Runs `rspec` test suite before push
+ # Runs `rspec` test suite
#
# @see http://rspec.info/
class RSpec < Base
- def run
- result = execute(command)
- return :pass if result.success?
-
- output = result.stdout + result.stderr
- [:fail, output]
- end
+ include Overcommit::Hook::Shared::RSpec
end
end
diff --git a/lib/overcommit/hook/pre_push/rake_target.rb b/lib/overcommit/hook/pre_push/rake_target.rb
new file mode 100644
index 00000000..f44897de
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/rake_target.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+require 'overcommit/hook/shared/rake_target'
+
+module Overcommit::Hook::PrePush
+ # Runs rake targets
+ #
+ # @see Overcommit::Hook::Shared::RakeTarget
+ class RakeTarget < Base
+ include Overcommit::Hook::Shared::RakeTarget
+ end
+end
diff --git a/lib/overcommit/hook/pre_push/test_unit.rb b/lib/overcommit/hook/pre_push/test_unit.rb
new file mode 100644
index 00000000..44ae9d71
--- /dev/null
+++ b/lib/overcommit/hook/pre_push/test_unit.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrePush
+ # Runs `test-unit` test suite before push
+ #
+ # @see https://github.com/test-unit/test-unit
+ class TestUnit < Base
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/pre_rebase/base.rb b/lib/overcommit/hook/pre_rebase/base.rb
index 49ac6511..1dc72f1c 100644
--- a/lib/overcommit/hook/pre_rebase/base.rb
+++ b/lib/overcommit/hook/pre_rebase/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'forwardable'
module Overcommit::Hook::PreRebase
diff --git a/lib/overcommit/hook/pre_rebase/merged_commits.rb b/lib/overcommit/hook/pre_rebase/merged_commits.rb
index fd003942..7c522e8c 100644
--- a/lib/overcommit/hook/pre_rebase/merged_commits.rb
+++ b/lib/overcommit/hook/pre_rebase/merged_commits.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::PreRebase
# Prevents rebasing commits that have already been merged into one of
# a specified set of branches.
diff --git a/lib/overcommit/hook/prepare_commit_msg/base.rb b/lib/overcommit/hook/prepare_commit_msg/base.rb
new file mode 100644
index 00000000..a4fbf31e
--- /dev/null
+++ b/lib/overcommit/hook/prepare_commit_msg/base.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'forwardable'
+
+module Overcommit::Hook::PrepareCommitMsg
+ # Functionality common to all prepare-commit-msg hooks.
+ class Base < Overcommit::Hook::Base
+ extend Forwardable
+
+ def_delegators :@context,
+ :commit_message_filename, :commit_message_source, :commit, :lock
+
+ def modify_commit_message
+ raise 'This expects a block!' unless block_given?
+
+ # NOTE: this assumes all the hooks of the same type share the context's
+ # memory. If that's not the case, this won't work.
+ lock.synchronize do
+ contents = File.read(commit_message_filename)
+ File.open(commit_message_filename, 'w') do |f|
+ f << (yield contents)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb b/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb
new file mode 100644
index 00000000..8852b0b5
--- /dev/null
+++ b/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb
@@ -0,0 +1,91 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::PrepareCommitMsg
+ # Prepends the commit message with a message based on the branch name.
+ #
+ # === What to prepend
+ #
+ # It's possible to reference parts of the branch name through the captures in
+ # the `branch_pattern` regex.
+ #
+ # For instance, if your current branch is `123-topic` then this config
+ #
+ # branch_pattern: '(\d+)-(\w+)'
+ # replacement_text: '[#\1] '
+ #
+ # would make this hook prepend commit messages with `[#123] `.
+ #
+ # Similarly, a replacement text of `[\1][\2]` would result in `[123][topic]`.
+ #
+ # == When to run this hook
+ #
+ # You can configure this to run only for specific types of commits by setting
+ # the `skipped_commit_types`. The allowed types are
+ #
+ # - 'message' - if message is given via `-m`, `-F`
+ # - 'template' - if `-t` is given or `commit.template` is set
+ # - 'commit' - if `-c`, `-C`, or `--amend` is given
+ # - 'merge' - if merging
+ # - 'squash' - if squashing
+ #
+ class ReplaceBranch < Base
+ DEFAULT_BRANCH_PATTERN = /\A(\d+)-(\w+).*\z/.freeze
+
+ def run
+ return :pass if skip?
+
+ Overcommit::Utils.log.debug(
+ "Checking if '#{Overcommit::GitRepo.current_branch}' matches #{branch_pattern}"
+ )
+
+ return :warn unless branch_pattern.match?(Overcommit::GitRepo.current_branch)
+
+ Overcommit::Utils.log.debug("Writing #{commit_message_filename} with #{new_template}")
+
+ modify_commit_message do |old_contents|
+ "#{new_template}#{old_contents}"
+ end
+
+ :pass
+ end
+
+ def new_template
+ @new_template ||=
+ begin
+ curr_branch = Overcommit::GitRepo.current_branch
+ curr_branch.gsub(branch_pattern, replacement_text)
+ end
+ end
+
+ def branch_pattern
+ @branch_pattern ||=
+ begin
+ pattern = config['branch_pattern']
+ Regexp.new((pattern || '').empty? ? DEFAULT_BRANCH_PATTERN : pattern)
+ end
+ end
+
+ def replacement_text
+ @replacement_text ||=
+ begin
+ if File.exist?(replacement_text_config)
+ File.read(replacement_text_config).chomp
+ else
+ replacement_text_config
+ end
+ end
+ end
+
+ def replacement_text_config
+ @replacement_text_config ||= config['replacement_text']
+ end
+
+ def skipped_commit_types
+ @skipped_commit_types ||= config['skipped_commit_types'].map(&:to_sym)
+ end
+
+ def skip?
+ super || skipped_commit_types.include?(commit_message_source)
+ end
+ end
+end
diff --git a/lib/overcommit/hook/shared/bower_install.rb b/lib/overcommit/hook/shared/bower_install.rb
index a0ecab63..cfcde92f 100644
--- a/lib/overcommit/hook/shared/bower_install.rb
+++ b/lib/overcommit/hook/shared/bower_install.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::Shared
# Shared code used by all BowerInstall hooks. Runs `bower install` when a
# change is detected in the repository's dependencies.
@@ -7,6 +9,7 @@ module BowerInstall
def run
result = execute(command)
return :fail, result.stderr unless result.success?
+
:pass
end
end
diff --git a/lib/overcommit/hook/shared/bundle_install.rb b/lib/overcommit/hook/shared/bundle_install.rb
index bfb2d560..01acaa32 100644
--- a/lib/overcommit/hook/shared/bundle_install.rb
+++ b/lib/overcommit/hook/shared/bundle_install.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::Shared
# Shared code used by all BundleInstall hooks. Runs `bundle install` when a
# change is detected in the repository's dependencies.
@@ -7,6 +9,7 @@ module BundleInstall
def run
result = execute(command)
return :fail, result.stdout unless result.success?
+
:pass
end
end
diff --git a/lib/overcommit/hook/shared/composer_install.rb b/lib/overcommit/hook/shared/composer_install.rb
new file mode 100644
index 00000000..2d2ff01d
--- /dev/null
+++ b/lib/overcommit/hook/shared/composer_install.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::Shared
+ # Shared code used by all ComposerInstall hooks. Runs `composer install` when
+ # a change is detected in the repository's dependencies.
+ #
+ # @see https://getcomposer.org/
+ module ComposerInstall
+ def run
+ result = execute(command)
+ return :fail, result.stdout unless result.success?
+
+ :pass
+ end
+ end
+end
diff --git a/lib/overcommit/hook/shared/index_tags.rb b/lib/overcommit/hook/shared/index_tags.rb
index ffc4a67e..99f7efde 100644
--- a/lib/overcommit/hook/shared/index_tags.rb
+++ b/lib/overcommit/hook/shared/index_tags.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::Shared
# Shared code used by all IndexTags hooks. It runs ctags in the background so
# your tag definitions are up-to-date.
diff --git a/lib/overcommit/hook/shared/npm_install.rb b/lib/overcommit/hook/shared/npm_install.rb
index 93dd0696..f487c8e7 100644
--- a/lib/overcommit/hook/shared/npm_install.rb
+++ b/lib/overcommit/hook/shared/npm_install.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::Shared
# Shared code used by all NpmInstall hooks. Runs `npm install` when a change
# is detected in the repository's dependencies.
@@ -7,6 +9,7 @@ module NpmInstall
def run
result = execute(command)
return :fail, result.stderr unless result.success?
+
:pass
end
end
diff --git a/lib/overcommit/hook/shared/pronto.rb b/lib/overcommit/hook/shared/pronto.rb
new file mode 100644
index 00000000..cf8a3139
--- /dev/null
+++ b/lib/overcommit/hook/shared/pronto.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::Shared
+ # Shared code used by all Pronto hooks. Runs pronto linters.
+
+ # @see https://github.com/prontolabs/pronto
+ module Pronto
+ MESSAGE_TYPE_CATEGORIZER = lambda do |type|
+ type.include?('E') ? :error : :warning
+ end
+
+ MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+) (?[^ ]+)/.freeze
+
+ def run
+ result = execute(command)
+ return :pass if result.success?
+
+ # e.g. runtime errors
+ generic_errors = extract_messages(
+ result.stderr.split("\n"),
+ /^(?[a-z]+)/i
+ )
+
+ pronto_infractions = extract_messages(
+ result.stdout.split("\n").select { |line| line.match?(MESSAGE_REGEX) },
+ MESSAGE_REGEX,
+ MESSAGE_TYPE_CATEGORIZER,
+ )
+
+ generic_errors + pronto_infractions
+ end
+ end
+end
diff --git a/lib/overcommit/hook/shared/r_spec.rb b/lib/overcommit/hook/shared/r_spec.rb
new file mode 100644
index 00000000..9b41e8e1
--- /dev/null
+++ b/lib/overcommit/hook/shared/r_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::Shared
+ # Runs `rspec` test suite before push
+ #
+ # @see http://rspec.info/
+ module RSpec
+ def run
+ result = if @config['include']
+ execute(command, args: applicable_files)
+ else
+ execute(command)
+ end
+
+ return :pass if result.success?
+
+ output = result.stdout + result.stderr
+ [:fail, output]
+ end
+ end
+end
diff --git a/lib/overcommit/hook/shared/rake_target.rb b/lib/overcommit/hook/shared/rake_target.rb
new file mode 100644
index 00000000..6ef3873a
--- /dev/null
+++ b/lib/overcommit/hook/shared/rake_target.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::Shared
+ # runs specified rake targets. It fails on the first non-
+ # successful exit.
+ #
+ module RakeTarget
+ def run
+ targets = config['targets']
+
+ if Array(targets).empty?
+ raise 'RakeTarget: targets parameter is empty. Add at least one task to ' \
+ 'the targets parameter. Valid: Array of target names or String of ' \
+ 'target names'
+ end
+
+ targets.each do |task|
+ result = execute(command + [task])
+ unless result.success?
+ return :fail, "Rake target #{task}:\n#{result.stdout}"
+ end
+ end
+ :pass
+ end
+ end
+end
diff --git a/lib/overcommit/hook/shared/submodule_status.rb b/lib/overcommit/hook/shared/submodule_status.rb
index fc35c32d..41e24aed 100644
--- a/lib/overcommit/hook/shared/submodule_status.rb
+++ b/lib/overcommit/hook/shared/submodule_status.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::Hook::Shared
# Shared code used by all `SubmoduleStatus` hooks to notify the user if any
# submodules are uninitialized, out of date with the current index, or contain
diff --git a/lib/overcommit/hook/shared/yarn_install.rb b/lib/overcommit/hook/shared/yarn_install.rb
new file mode 100644
index 00000000..fd548f07
--- /dev/null
+++ b/lib/overcommit/hook/shared/yarn_install.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Overcommit::Hook::Shared
+ # Shared code used by all YarnInstall hooks. Runs `yarn install` when a change
+ # is detected in the repository's dependencies.
+ #
+ # @see https://yarnpkg.com/
+ module YarnInstall
+ def run
+ result = execute(command)
+ return :fail, result.stderr unless result.success?
+
+ :pass
+ end
+ end
+end
diff --git a/lib/overcommit/hook_context.rb b/lib/overcommit/hook_context.rb
index 567ac153..5863ed94 100644
--- a/lib/overcommit/hook_context.rb
+++ b/lib/overcommit/hook_context.rb
@@ -1,17 +1,19 @@
+# frozen_string_literal: true
+
# Utility module which manages the creation of {HookContext}s.
module Overcommit::HookContext
- def self.create(hook_type, config, args, input)
+ def self.create(hook_type, config, args, input, **cli_options)
hook_type_class = Overcommit::Utils.camel_case(hook_type)
underscored_hook_type = Overcommit::Utils.snake_case(hook_type)
require "overcommit/hook_context/#{underscored_hook_type}"
- Overcommit::HookContext.const_get(hook_type_class).new(config, args, input)
- rescue LoadError, NameError => error
+ Overcommit::HookContext.const_get(hook_type_class).new(config, args, input, **cli_options)
+ rescue LoadError, NameError => e
# Could happen when a symlink was created for a hook type Overcommit does
# not yet support.
raise Overcommit::Exceptions::HookContextLoadError,
- "Unable to load '#{hook_type}' hook context: '#{error}'",
- error.backtrace
+ "Unable to load '#{hook_type}' hook context: '#{e}'",
+ e.backtrace
end
end
diff --git a/lib/overcommit/hook_context/base.rb b/lib/overcommit/hook_context/base.rb
index 60ea47e1..b50698c9 100644
--- a/lib/overcommit/hook_context/base.rb
+++ b/lib/overcommit/hook_context/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers related to the context with which a hook is being run.
#
@@ -16,10 +18,12 @@ class Base
# @param config [Overcommit::Configuration]
# @param args [Array]
# @param input [IO] standard input stream
- def initialize(config, args, input)
+ # @param options [Hash] cli options
+ def initialize(config, args, input, **options)
@config = config
@args = args
@input = input
+ @options = options
end
# Executes a command as if it were a regular git hook, passing all
@@ -79,6 +83,13 @@ def modified_files
[]
end
+ # Returns the full list of files tracked by git
+ #
+ # @return [Array]
+ def all_files
+ Overcommit::GitRepo.all_files
+ end
+
# Returns the contents of the entire standard input stream that were passed
# to the hook.
#
@@ -95,6 +106,13 @@ def input_lines
@input_lines ||= input_string.split("\n")
end
+ # Returns a message to display on failure.
+ #
+ # @return [String]
+ def post_fail_message
+ nil
+ end
+
private
def filter_modified_files(modified_files)
diff --git a/lib/overcommit/hook_context/commit_msg.rb b/lib/overcommit/hook_context/commit_msg.rb
index c161131d..ae32e55d 100644
--- a/lib/overcommit/hook_context/commit_msg.rb
+++ b/lib/overcommit/hook_context/commit_msg.rb
@@ -1,6 +1,15 @@
+# frozen_string_literal: true
+
+require_relative 'pre_commit'
+require_relative 'helpers/stash_unstaged_changes'
+require_relative 'helpers/file_modifications'
+
module Overcommit::HookContext
# Contains helpers related to contextual information used by commit-msg hooks.
class CommitMsg < Base
+ include Overcommit::HookContext::Helpers::StashUnstagedChanges
+ include Overcommit::HookContext::Helpers::FileModifications
+
def empty_message?
commit_message.strip.empty?
end
@@ -31,6 +40,12 @@ def commit_message_file
@args[0]
end
+ def post_fail_message
+ "Failed commit message:\n#{commit_message_lines.join.chomp}\n\n" \
+ "Try again with your existing commit message by running:\n" \
+ "git commit --edit --file=#{commit_message_file}"
+ end
+
private
def raw_commit_message_lines
diff --git a/lib/overcommit/hook_context/diff.rb b/lib/overcommit/hook_context/diff.rb
new file mode 100644
index 00000000..3e9aa568
--- /dev/null
+++ b/lib/overcommit/hook_context/diff.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'overcommit/git_repo'
+
+require 'set'
+
+module Overcommit::HookContext
+ # Simulates a pre-commit context based on the diff with another git ref.
+ #
+ # This results in pre-commit hooks running against the changes between the current
+ # and another ref, which is useful for automated CI scripts.
+ class Diff < Base
+ def modified_files
+ @modified_files ||= Overcommit::GitRepo.modified_files(refs: @options[:diff])
+ end
+
+ def modified_lines_in_file(file)
+ @modified_lines ||= {}
+ @modified_lines[file] ||= Overcommit::GitRepo.extract_modified_lines(file,
+ refs: @options[:diff])
+ end
+
+ def hook_class_name
+ 'PreCommit'
+ end
+
+ def hook_type_name
+ 'pre_commit'
+ end
+
+ def hook_script_name
+ 'pre-commit'
+ end
+
+ def initial_commit?
+ @initial_commit ||= Overcommit::GitRepo.initial_commit?
+ end
+ end
+end
diff --git a/lib/overcommit/hook_context/helpers/file_modifications.rb b/lib/overcommit/hook_context/helpers/file_modifications.rb
new file mode 100644
index 00000000..00aaccc8
--- /dev/null
+++ b/lib/overcommit/hook_context/helpers/file_modifications.rb
@@ -0,0 +1,79 @@
+# frozen_string_literal: true
+
+module Overcommit::HookContext
+ module Helpers
+ # This module contains methods for determining what files were changed and on what unique line
+ # numbers did the change occur.
+ module FileModifications
+ # Returns whether this hook run was triggered by `git commit --amend`
+ def amendment?
+ return @amendment unless @amendment.nil?
+
+ cmd = Overcommit::Utils.parent_command
+ return unless cmd
+
+ amend_pattern = 'commit(\s.*)?\s--amend(\s|$)'
+
+ # Since the ps command can return invalid byte sequences for commands
+ # containing unicode characters, we replace the offending characters,
+ # since the pattern we're looking for will consist of ASCII characters
+ unless cmd.valid_encoding?
+ cmd = Overcommit::Utils.
+ parent_command.
+ encode('UTF-16be', invalid: :replace, replace: '?').
+ encode('UTF-8')
+ end
+
+ return @amendment if
+ # True if the command is a commit with the --amend flag
+ @amendment = !(/\s#{amend_pattern}/ =~ cmd).nil?
+
+ # Check for git aliases that call `commit --amend`
+ `git config --get-regexp "^alias\\." "#{amend_pattern}"`.
+ scan(/alias\.([-\w]+)/). # Extract the alias
+ each do |match|
+ return @amendment if
+ # True if the command uses a git alias for `commit --amend`
+ @amendment = !(/git(\.exe)?\s+#{match[0]}/ =~ cmd).nil?
+ end
+
+ @amendment
+ end
+
+ # Get a list of added, copied, or modified files that have been staged.
+ # Renames and deletions are ignored, since there should be nothing to check.
+ def modified_files
+ unless @modified_files
+ currently_staged = Overcommit::GitRepo.modified_files(staged: true)
+ @modified_files = currently_staged
+
+ # Include files modified in last commit if amending
+ if amendment?
+ subcmd = 'show --format=%n'
+ previously_modified = Overcommit::GitRepo.modified_files(subcmd: subcmd)
+ @modified_files |= filter_modified_files(previously_modified)
+ end
+ end
+ @modified_files
+ end
+
+ # Returns the set of line numbers corresponding to the lines that were
+ # changed in a specified file.
+ def modified_lines_in_file(file)
+ @modified_lines ||= {}
+ unless @modified_lines[file]
+ @modified_lines[file] =
+ Overcommit::GitRepo.extract_modified_lines(file, staged: true)
+
+ # Include lines modified in last commit if amending
+ if amendment?
+ subcmd = 'show --format=%n'
+ @modified_lines[file] +=
+ Overcommit::GitRepo.extract_modified_lines(file, subcmd: subcmd)
+ end
+ end
+ @modified_lines[file]
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb b/lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb
new file mode 100644
index 00000000..17400539
--- /dev/null
+++ b/lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb
@@ -0,0 +1,144 @@
+# frozen_string_literal: true
+
+module Overcommit::HookContext
+ module Helpers
+ # This module contains behavior for stashing unstaged changes before hooks are ran and restoring
+ # them afterwards
+ module StashUnstagedChanges
+ # Stash unstaged contents of files so hooks don't see changes that aren't
+ # about to be committed.
+ def setup_environment
+ store_modified_times
+ Overcommit::GitRepo.store_merge_state
+ Overcommit::GitRepo.store_cherry_pick_state
+
+ # Don't attempt to stash changes if all changes are staged, as this
+ # prevents us from modifying files at all, which plays better with
+ # editors/tools which watch for file changes.
+ if !initial_commit? && unstaged_changes?
+ stash_changes
+
+ # While running hooks make it appear as if nothing changed
+ restore_modified_times
+ end
+ end
+
+ # Returns whether the current git branch is empty (has no commits).
+ def initial_commit?
+ return @initial_commit unless @initial_commit.nil?
+
+ @initial_commit = Overcommit::GitRepo.initial_commit?
+ end
+
+ # Restore unstaged changes and reset file modification times so it appears
+ # as if nothing ever changed.
+ #
+ # We want to restore the modification times for each of the files after
+ # every step to ensure as little time as possible has passed while the
+ # modification time on the file was newer. This helps us play more nicely
+ # with file watchers.
+ def cleanup_environment
+ if @changes_stashed
+ clear_working_tree
+ restore_working_tree
+ restore_modified_times
+ end
+
+ Overcommit::GitRepo.restore_merge_state
+ Overcommit::GitRepo.restore_cherry_pick_state
+ end
+
+ private
+
+ # Stores the modification times for all modified files to make it appear like
+ # they never changed.
+ #
+ # This prevents (some) editors from complaining about files changing when we
+ # stash changes before running the hooks.
+ def store_modified_times
+ @modified_times = {}
+
+ staged_files = modified_files
+ unstaged_files = Overcommit::GitRepo.modified_files(staged: false)
+
+ (staged_files + unstaged_files).each do |file|
+ next if Overcommit::Utils.broken_symlink?(file)
+ next unless File.exist?(file) # Ignore renamed files (old file no longer exists)
+
+ @modified_times[file] = File.mtime(file)
+ end
+ end
+
+ # Returns whether there are any changes to tracked files which have not yet
+ # been staged.
+ def unstaged_changes?
+ result = Overcommit::Utils.execute(%w[git --no-pager diff --quiet])
+ !result.success?
+ end
+
+ def stash_changes
+ @stash_attempted = true
+
+ stash_message = "Overcommit: Stash of repo state before hook run at #{Time.now}"
+ result = Overcommit::Utils.with_environment('GIT_LITERAL_PATHSPECS' => '0') do
+ Overcommit::Utils.execute(
+ %w[git -c commit.gpgsign=false stash save --keep-index --quiet] + [stash_message]
+ )
+ end
+
+ unless result.success?
+ # Failure to stash in this case is likely due to a configuration
+ # issue (e.g. author/email not set or GPG signing key incorrect)
+ raise Overcommit::Exceptions::HookSetupFailed,
+ "Unable to setup environment for #{hook_script_name} hook run:" \
+ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
+ end
+
+ @changes_stashed = `git stash list -1`.include?(stash_message)
+ end
+
+ # Restores the file modification times for all modified files to make it
+ # appear like they never changed.
+ def restore_modified_times
+ @modified_times.each do |file, time|
+ next if Overcommit::Utils.broken_symlink?(file)
+ next unless File.exist?(file)
+
+ File.utime(time, time, file)
+ end
+ end
+
+ # Clears the working tree so that the stash can be applied.
+ def clear_working_tree
+ removed_submodules = Overcommit::GitRepo.staged_submodule_removals
+
+ result = Overcommit::Utils.execute(%w[git reset --hard])
+ unless result.success?
+ raise Overcommit::Exceptions::HookCleanupFailed,
+ "Unable to cleanup working tree after #{hook_script_name} hooks run:" \
+ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
+ end
+
+ # Hard-resetting a staged submodule removal results in the index being
+ # reset but the submodule being restored as an empty directory. This empty
+ # directory prevents us from stashing on a subsequent run if a hook fails.
+ #
+ # Work around this by removing these empty submodule directories as there
+ # doesn't appear any reason to keep them around.
+ removed_submodules.each do |submodule|
+ FileUtils.rmdir(submodule.path)
+ end
+ end
+
+ # Applies the stash to the working tree to restore the user's state.
+ def restore_working_tree
+ result = Overcommit::Utils.execute(%w[git stash pop --index])
+ unless result.success?
+ raise Overcommit::Exceptions::HookCleanupFailed,
+ "Unable to restore working tree after #{hook_script_name} hooks run:" \
+ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/hook_context/post_checkout.rb b/lib/overcommit/hook_context/post_checkout.rb
index a9310974..a8d38649 100644
--- a/lib/overcommit/hook_context/post_checkout.rb
+++ b/lib/overcommit/hook_context/post_checkout.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers related to contextual information used by post-checkout
# hooks.
diff --git a/lib/overcommit/hook_context/post_commit.rb b/lib/overcommit/hook_context/post_commit.rb
index e5ec6da6..8327b0d9 100644
--- a/lib/overcommit/hook_context/post_commit.rb
+++ b/lib/overcommit/hook_context/post_commit.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers related to contextual information used by post-commit
# hooks.
@@ -25,6 +27,7 @@ def modified_lines_in_file(file)
# @return [true,false]
def initial_commit?
return @initial_commit unless @initial_commit.nil?
+
@initial_commit = !Overcommit::Utils.execute(%w[git rev-parse HEAD~]).success?
end
end
diff --git a/lib/overcommit/hook_context/post_merge.rb b/lib/overcommit/hook_context/post_merge.rb
index d8547998..14f91826 100644
--- a/lib/overcommit/hook_context/post_merge.rb
+++ b/lib/overcommit/hook_context/post_merge.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers related to contextual information used by post-merge
# hooks.
diff --git a/lib/overcommit/hook_context/post_rewrite.rb b/lib/overcommit/hook_context/post_rewrite.rb
index 249071dc..5310ef8e 100644
--- a/lib/overcommit/hook_context/post_rewrite.rb
+++ b/lib/overcommit/hook_context/post_rewrite.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers for contextual information used by post-rewrite hooks.
class PostRewrite < Base
diff --git a/lib/overcommit/hook_context/pre_commit.rb b/lib/overcommit/hook_context/pre_commit.rb
index d51c497f..cd510605 100644
--- a/lib/overcommit/hook_context/pre_commit.rb
+++ b/lib/overcommit/hook_context/pre_commit.rb
@@ -1,5 +1,9 @@
+# frozen_string_literal: true
+
require 'fileutils'
require 'set'
+require_relative 'helpers/stash_unstaged_changes'
+require_relative 'helpers/file_modifications'
module Overcommit::HookContext
# Contains helpers related to contextual information used by pre-commit hooks.
@@ -7,201 +11,8 @@ module Overcommit::HookContext
# This includes staged files, which lines of those files have been modified,
# etc. It is also responsible for saving/restoring the state of the repo so
# hooks only inspect staged changes.
- class PreCommit < Base # rubocop:disable ClassLength
- # Returns whether this hook run was triggered by `git commit --amend`
- def amendment?
- return @amendment unless @amendment.nil?
-
- cmd = Overcommit::Utils.parent_command
- amend_pattern = 'commit(\s.*)?\s--amend(\s|$)'
-
- # Since the ps command can return invalid byte sequences for commands
- # containing unicode characters, we replace the offending characters,
- # since the pattern we're looking for will consist of ASCII characters
- unless cmd.valid_encoding?
- cmd.encode!('UTF-16be', invalid: :replace, replace: '?').encode!('UTF-8')
- end
-
- return @amendment if
- # True if the command is a commit with the --amend flag
- @amendment = !(/\s#{amend_pattern}/ =~ cmd).nil?
-
- # Check for git aliases that call `commit --amend`
- `git config --get-regexp "^alias\\." "#{amend_pattern}"`.
- scan(/alias\.([-\w]+)/). # Extract the alias
- each do |match|
- return @amendment if
- # True if the command uses a git alias for `commit --amend`
- @amendment = !(/git(\.exe)?\s+#{match[0]}/ =~ cmd).nil?
- end
-
- @amendment
- end
-
- # Stash unstaged contents of files so hooks don't see changes that aren't
- # about to be committed.
- def setup_environment
- store_modified_times
- Overcommit::GitRepo.store_merge_state
- Overcommit::GitRepo.store_cherry_pick_state
-
- if !initial_commit? && any_changes?
- @stash_attempted = true
-
- stash_message = "Overcommit: Stash of repo state before hook run at #{Time.now}"
- result = Overcommit::Utils.execute(
- %w[git stash save --keep-index --quiet] + [stash_message]
- )
-
- unless result.success?
- # Failure to stash in this case is likely due to a configuration
- # issue (e.g. author/email not set or GPG signing key incorrect)
- raise Overcommit::Exceptions::HookSetupFailed,
- "Unable to setup environment for #{hook_script_name} hook run:" \
- "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
- end
-
- @changes_stashed = `git stash list -1`.include?(stash_message)
- end
-
- # While running the hooks make it appear as if nothing changed
- restore_modified_times
- end
-
- # Restore unstaged changes and reset file modification times so it appears
- # as if nothing ever changed.
- #
- # We want to restore the modification times for each of the files after
- # every step to ensure as little time as possible has passed while the
- # modification time on the file was newer. This helps us play more nicely
- # with file watchers.
- def cleanup_environment
- unless initial_commit? || (@stash_attempted && !@changes_stashed)
- clear_working_tree # Ensure working tree is clean before restoring it
- restore_modified_times
- end
-
- if @changes_stashed
- restore_working_tree
- restore_modified_times
- end
-
- Overcommit::GitRepo.restore_merge_state
- Overcommit::GitRepo.restore_cherry_pick_state
- restore_modified_times
- end
-
- # Get a list of added, copied, or modified files that have been staged.
- # Renames and deletions are ignored, since there should be nothing to check.
- def modified_files
- unless @modified_files
- currently_staged = Overcommit::GitRepo.modified_files(staged: true)
- @modified_files = currently_staged
-
- # Include files modified in last commit if amending
- if amendment?
- subcmd = 'show --format=%n'
- previously_modified = Overcommit::GitRepo.modified_files(subcmd: subcmd)
- @modified_files |= filter_modified_files(previously_modified)
- end
- end
- @modified_files
- end
-
- # Returns the set of line numbers corresponding to the lines that were
- # changed in a specified file.
- def modified_lines_in_file(file)
- @modified_lines ||= {}
- unless @modified_lines[file]
- @modified_lines[file] =
- Overcommit::GitRepo.extract_modified_lines(file, staged: true)
-
- # Include lines modified in last commit if amending
- if amendment?
- subcmd = 'show --format=%n'
- @modified_lines[file] +=
- Overcommit::GitRepo.extract_modified_lines(file, subcmd: subcmd)
- end
- end
- @modified_lines[file]
- end
-
- private
-
- # Clears the working tree so that the stash can be applied.
- def clear_working_tree
- removed_submodules = Overcommit::GitRepo.staged_submodule_removals
-
- result = Overcommit::Utils.execute(%w[git reset --hard])
- unless result.success?
- raise Overcommit::Exceptions::HookCleanupFailed,
- "Unable to cleanup working tree after #{hook_script_name} hooks run:" \
- "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
- end
-
- # Hard-resetting a staged submodule removal results in the index being
- # reset but the submodule being restored as an empty directory. This empty
- # directory prevents us from stashing on a subsequent run if a hook fails.
- #
- # Work around this by removing these empty submodule directories as there
- # doesn't appear any reason to keep them around.
- removed_submodules.each do |submodule|
- FileUtils.rmdir(submodule.path)
- end
- end
-
- # Applies the stash to the working tree to restore the user's state.
- def restore_working_tree
- result = Overcommit::Utils.execute(%w[git stash pop --index --quiet])
- unless result.success?
- raise Overcommit::Exceptions::HookCleanupFailed,
- "Unable to restore working tree after #{hook_script_name} hooks run:" \
- "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}"
- end
- end
-
- # Returns whether there are any changes to the working tree, staged or
- # otherwise.
- def any_changes?
- modified_files = `git status -z --untracked-files=no`.
- split("\0").
- map { |line| line.gsub(/[^\s]+\s+(.+)/, '\\1') }
-
- modified_files.any?
- end
-
- # Returns whether the current git branch is empty (has no commits).
- def initial_commit?
- return @initial_commit unless @initial_commit.nil?
- @initial_commit = Overcommit::GitRepo.initial_commit?
- end
-
- # Stores the modification times for all modified files to make it appear like
- # they never changed.
- #
- # This prevents (some) editors from complaining about files changing when we
- # stash changes before running the hooks.
- def store_modified_times
- @modified_times = {}
-
- staged_files = modified_files
- unstaged_files = Overcommit::GitRepo.modified_files(staged: false)
-
- (staged_files + unstaged_files).each do |file|
- next if Overcommit::Utils.broken_symlink?(file)
- next unless File.exist?(file) # Ignore renamed files (old file no longer exists)
- @modified_times[file] = File.mtime(file)
- end
- end
-
- # Restores the file modification times for all modified files to make it
- # appear like they never changed.
- def restore_modified_times
- @modified_times.each do |file, time|
- next if Overcommit::Utils.broken_symlink?(file)
- next unless File.exist?(file)
- File.utime(time, time, file)
- end
- end
+ class PreCommit < Base
+ include Overcommit::HookContext::Helpers::StashUnstagedChanges
+ include Overcommit::HookContext::Helpers::FileModifications
end
end
diff --git a/lib/overcommit/hook_context/pre_push.rb b/lib/overcommit/hook_context/pre_push.rb
index b47e2987..39ec19ba 100644
--- a/lib/overcommit/hook_context/pre_push.rb
+++ b/lib/overcommit/hook_context/pre_push.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers related to contextual information used by pre-push hooks.
class PrePush < Base
@@ -11,12 +13,32 @@ def remote_url
@args[1]
end
+ def remote_ref_deletion?
+ return @remote_ref_deletion if defined?(@remote_ref_deletion)
+
+ @remote_ref_deletion ||= input_lines.
+ first&.
+ split(' ')&.
+ first == '(deleted)'
+ end
+
def pushed_refs
input_lines.map do |line|
PushedRef.new(*line.split(' '))
end
end
+ def modified_files
+ @modified_files ||= pushed_refs.map(&:modified_files).flatten.uniq
+ end
+
+ def modified_lines_in_file(file)
+ @modified_lines ||= {}
+ @modified_lines[file] = pushed_refs.each_with_object(Set.new) do |pushed_ref, set|
+ set.merge(pushed_ref.modified_lines_in_file(file))
+ end
+ end
+
PushedRef = Struct.new(:local_ref, :local_sha1, :remote_ref, :remote_sha1) do
def forced?
!(created? || deleted? || overwritten_commits.empty?)
@@ -34,14 +56,27 @@ def destructive?
deleted? || forced?
end
+ def modified_files
+ Overcommit::GitRepo.modified_files(refs: ref_range)
+ end
+
+ def modified_lines_in_file(file)
+ Overcommit::GitRepo.extract_modified_lines(file, refs: ref_range)
+ end
+
def to_s
"#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}"
end
private
+ def ref_range
+ "#{remote_sha1}..#{local_sha1}"
+ end
+
def overwritten_commits
return @overwritten_commits if defined? @overwritten_commits
+
result = Overcommit::Subprocess.spawn(%W[git rev-list #{remote_sha1} ^#{local_sha1}])
if result.success?
result.stdout.split("\n")
diff --git a/lib/overcommit/hook_context/pre_rebase.rb b/lib/overcommit/hook_context/pre_rebase.rb
index 5a2ca149..f681e067 100644
--- a/lib/overcommit/hook_context/pre_rebase.rb
+++ b/lib/overcommit/hook_context/pre_rebase.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookContext
# Contains helpers related to contextual information used by pre-rebase
# hooks.
diff --git a/lib/overcommit/hook_context/prepare_commit_msg.rb b/lib/overcommit/hook_context/prepare_commit_msg.rb
new file mode 100644
index 00000000..781fab73
--- /dev/null
+++ b/lib/overcommit/hook_context/prepare_commit_msg.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Overcommit::HookContext
+ # Contains helpers related to contextual information used by prepare-commit-msg
+ # hooks.
+ class PrepareCommitMsg < Base
+ # Returns the name of the file that contains the commit log message
+ def commit_message_filename
+ @args[0]
+ end
+
+ # Returns the source of the commit message, and can be: message (if a -m or
+ # -F option was given); template (if a -t option was given or the
+ # configuration option commit.template is set); merge (if the commit is a
+ # merge or a .git/MERGE_MSG file exists); squash (if a .git/SQUASH_MSG file
+ # exists); or commit, followed by a commit SHA-1 (if a -c, -C or --amend
+ # option was given)
+ def commit_message_source
+ @args[1]&.to_sym
+ end
+
+ # Returns the commit's SHA-1.
+ # If commit_message_source is :commit, it's passed through the command-line.
+ def commit_message_source_ref
+ @args[2] || `git rev-parse HEAD`
+ end
+
+ # Lock for the pre_commit_message file. Should be shared by all
+ # prepare-commit-message hooks
+ def lock
+ @lock ||= Monitor.new
+ end
+ end
+end
diff --git a/lib/overcommit/hook_context/run_all.rb b/lib/overcommit/hook_context/run_all.rb
index 7005c26c..5ed7efa0 100644
--- a/lib/overcommit/hook_context/run_all.rb
+++ b/lib/overcommit/hook_context/run_all.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'set'
module Overcommit::HookContext
@@ -7,7 +9,7 @@ module Overcommit::HookContext
# which is useful for automated CI scripts.
class RunAll < Base
def modified_files
- @modified_files ||= Overcommit::GitRepo.all_files
+ @modified_files ||= all_files
end
# Returns all lines in the file since in this context the entire repo is
@@ -34,6 +36,7 @@ def hook_script_name
def initial_commit?
return @initial_commit unless @initial_commit.nil?
+
@initial_commit = Overcommit::GitRepo.initial_commit?
end
diff --git a/lib/overcommit/hook_loader/base.rb b/lib/overcommit/hook_loader/base.rb
index 020bcf5b..9173e9f1 100644
--- a/lib/overcommit/hook_loader/base.rb
+++ b/lib/overcommit/hook_loader/base.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookLoader
# Responsible for loading hooks from a file.
class Base
@@ -24,13 +26,23 @@ def load_hooks
# Load and return a {Hook} from a CamelCase hook name.
def create_hook(hook_name)
- Overcommit::Hook.const_get(@context.hook_class_name).
- const_get(hook_name).
- new(@config, @context)
- rescue LoadError, NameError => error
- raise Overcommit::Exceptions::HookLoadError,
- "Unable to load hook '#{hook_name}': #{error}",
- error.backtrace
+ hook_type_class = Overcommit::Hook.const_get(@context.hook_class_name)
+ hook_base_class = hook_type_class.const_get(:Base)
+ hook_class = hook_type_class.const_get(hook_name)
+ unless hook_class < hook_base_class
+ raise Overcommit::Exceptions::HookLoadError,
+ "Class #{hook_name} is not a subclass of #{hook_base_class}."
+ end
+
+ begin
+ Overcommit::Hook.const_get(@context.hook_class_name).
+ const_get(hook_name).
+ new(@config, @context)
+ rescue LoadError, NameError => e
+ raise Overcommit::Exceptions::HookLoadError,
+ "Unable to load hook '#{hook_name}': #{e}",
+ e.backtrace
+ end
end
end
end
diff --git a/lib/overcommit/hook_loader/built_in_hook_loader.rb b/lib/overcommit/hook_loader/built_in_hook_loader.rb
index 5d3be7e5..1b7d3f87 100644
--- a/lib/overcommit/hook_loader/built_in_hook_loader.rb
+++ b/lib/overcommit/hook_loader/built_in_hook_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit::HookLoader
# Responsible for loading hooks that ship with Overcommit.
class BuiltInHookLoader < Base
diff --git a/lib/overcommit/hook_loader/plugin_hook_loader.rb b/lib/overcommit/hook_loader/plugin_hook_loader.rb
index dbf53a9c..d168a0de 100644
--- a/lib/overcommit/hook_loader/plugin_hook_loader.rb
+++ b/lib/overcommit/hook_loader/plugin_hook_loader.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'digest'
module Overcommit::HookLoader
@@ -79,7 +81,7 @@ def create_ad_hoc_hook(hook_name)
# Implement a simple class that executes the command and returns pass/fail
# based on the exit status
hook_class = Class.new(hook_base) do
- def run # rubocop:disable Lint/NestedMethodDefinition
+ def run
result = @context.execute_hook(command)
if result.success?
@@ -91,10 +93,10 @@ def run # rubocop:disable Lint/NestedMethodDefinition
end
hook_module.const_set(hook_name, hook_class).new(@config, @context)
- rescue LoadError, NameError => error
+ rescue LoadError, NameError => e
raise Overcommit::Exceptions::HookLoadError,
- "Unable to load hook '#{hook_name}': #{error}",
- error.backtrace
+ "Unable to load hook '#{hook_name}': #{e}",
+ e.backtrace
end
end
end
diff --git a/lib/overcommit/hook_runner.rb b/lib/overcommit/hook_runner.rb
index d55a9f3a..c7c313ab 100644
--- a/lib/overcommit/hook_runner.rb
+++ b/lib/overcommit/hook_runner.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit
# Responsible for loading the hooks the repository has configured and running
# them, collecting and displaying the results.
@@ -51,7 +53,7 @@ def run
attr_reader :log
- def run_hooks
+ def run_hooks # rubocop:disable Metrics/MethodLength
if @hooks.any?(&:enabled?)
@printer.start_run
@@ -74,7 +76,14 @@ def run_hooks
print_results
- !(@failed || @interrupted)
+ hook_failed = @failed || @interrupted
+
+ if hook_failed
+ message = @context.post_fail_message
+ @printer.hook_run_failed(message) unless message.nil?
+ end
+
+ !hook_failed
else
@printer.nothing_to_run
true # Run was successful
@@ -85,6 +94,7 @@ def consume
loop do
hook = @lock.synchronize { @hooks_left.pop }
break unless hook
+
run_hook(hook)
end
end
@@ -107,6 +117,9 @@ def wait_for_slot(hook)
# Wait for a signal from another thread to try again
@resource.wait(@lock)
+ else
+ # Otherwise there are not slots left, so just wait for signal
+ @resource.wait(@lock)
end
end
end
@@ -117,12 +130,9 @@ def release_slot(hook)
slots_released = processors_for_hook(hook)
@slots_available += slots_released
- if @hooks_left.any?
- # Signal once. `wait_for_slot` will perform additional signals if
- # there are still slots available. This prevents us from sending out
- # useless signals
- @resource.signal
- end
+ # Signal every time in case there are threads that are already waiting for
+ # these slots to be released
+ @resource.signal
end
end
@@ -150,9 +160,12 @@ def run_hook(hook) # rubocop:disable Metrics/CyclomaticComplexity
return if should_skip?(hook)
status, output = hook.run_and_transform
- rescue => ex
+ rescue Overcommit::Exceptions::MessageProcessingError => e
+ status = :fail
+ output = e.message
+ rescue StandardError => e
status = :fail
- output = "Hook raised unexpected error\n#{ex.message}\n#{ex.backtrace.join("\n")}"
+ output = "Hook raised unexpected error\n#{e.message}\n#{e.backtrace.join("\n")}"
end
@failed = true if status == :fail
@@ -190,7 +203,7 @@ def load_hooks
# Load plugin hooks after so they can subclass existing hooks
@hooks += HookLoader::PluginHookLoader.new(@config, @context, @log).load_hooks
- rescue LoadError => ex
+ rescue LoadError => e
# Include a more helpful message that will probably save some confusion
message = 'A load error occurred. ' +
if @config['gemfile']
@@ -200,8 +213,8 @@ def load_hooks
end
raise Overcommit::Exceptions::HookLoadError,
- "#{message}\n#{ex.message}",
- ex.backtrace
+ "#{message}\n#{e.message}",
+ e.backtrace
end
end
end
diff --git a/lib/overcommit/hook_signer.rb b/lib/overcommit/hook_signer.rb
index 5a51a123..fab99c37 100644
--- a/lib/overcommit/hook_signer.rb
+++ b/lib/overcommit/hook_signer.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit
# Calculates, stores, and retrieves stored signatures of hook plugins.
class HookSigner
@@ -32,25 +34,28 @@ def hook_path
# Otherwise this is an ad hoc hook using an existing hook script
hook_config = @config.for_hook(@hook_name, @context.hook_class_name)
- command = Array(hook_config['command'] ||
- hook_config['required_executable'])
+ command = Array(hook_config['command'] || hook_config['required_executable'])
- unless !@config.verify_signatures? || signable_file?(command.first)
+ if @config.verify_signatures? &&
+ signable_file?(command.first) &&
+ !Overcommit::GitRepo.tracked?(command.first)
raise Overcommit::Exceptions::InvalidHookDefinition,
- 'Hook must specify a `required_executable` or `command` that ' \
- 'is tracked by git (i.e. is a path relative to the root ' \
- 'of the repository) so that it can be signed'
+ 'Hook specified a `required_executable` or `command` that ' \
+ 'is a path relative to the root of the repository, and so ' \
+ 'must be tracked by Git in order to be signed'
end
- File.join(Overcommit::Utils.repo_root, command.first)
+ File.join(Overcommit::Utils.repo_root, command.first.to_s)
end
end
end
def signable_file?(file)
+ return unless file
+
sep = Overcommit::OS.windows? ? '\\' : File::SEPARATOR
- file.start_with?(".#{sep}") &&
- Overcommit::GitRepo.tracked?(file)
+ file.start_with?(".#{sep}") ||
+ file.start_with?(Overcommit::Utils.repo_root)
end
# Return whether the signature for this hook has changed since it was last
@@ -85,7 +90,12 @@ def signature
dup.
tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } }
- Digest::SHA256.hexdigest(hook_contents + hook_config.to_s)
+ content_to_sign =
+ if signable_file?(hook_path) && Overcommit::GitRepo.tracked?(hook_path)
+ hook_contents
+ end
+
+ Digest::SHA256.hexdigest(content_to_sign.to_s + hook_config.to_s)
end
def hook_contents
diff --git a/lib/overcommit/installer.rb b/lib/overcommit/installer.rb
index ffdd9767..79287b30 100644
--- a/lib/overcommit/installer.rb
+++ b/lib/overcommit/installer.rb
@@ -1,8 +1,10 @@
+# frozen_string_literal: true
+
require 'fileutils'
module Overcommit
# Manages the installation of Overcommit hooks in a git repository.
- class Installer # rubocop:disable ClassLength
+ class Installer # rubocop:disable Metrics/ClassLength
TEMPLATE_DIRECTORY = File.join(Overcommit::HOME, 'template-dir')
MASTER_HOOK = File.join(TEMPLATE_DIRECTORY, 'hooks', 'overcommit-hook')
@@ -33,16 +35,19 @@ def install
ensure_directory(hooks_path)
preserve_old_hooks
install_master_hook
- install_hook_symlinks
+ install_hook_files
install_starter_config
+ # Auto-sign configuration file on install
+ config(verify: false).update_signature!
+
log.success "Successfully installed hooks into #{@target}"
end
def uninstall
log.log "Removing hooks from #{@target}"
- uninstall_hook_symlinks
+ uninstall_hook_files
uninstall_master_hook
restore_old_hooks
@@ -54,7 +59,7 @@ def update
unless FileUtils.compare_file(MASTER_HOOK, master_hook_install_path)
preserve_old_hooks
install_master_hook
- install_hook_symlinks
+ install_hook_files
log.success "Hooks updated to Overcommit version #{Overcommit::VERSION}"
true
@@ -62,8 +67,7 @@ def update
end
def hooks_path
- absolute_target = File.expand_path(@target)
- File.join(Overcommit::Utils.git_dir(absolute_target), 'hooks')
+ @hooks_path ||= Dir.chdir(@target) { GitConfig.hooks_path }
end
def old_hooks_path
@@ -103,10 +107,8 @@ def uninstall_master_hook
FileUtils.rm_rf(master_hook_install_path, secure: true)
end
- def install_hook_symlinks
- # Link each hook type (pre-commit, commit-msg, etc.) to the master hook.
- # We change directories so that the relative symlink paths work regardless
- # of where the repository is located.
+ def install_hook_files
+ # Copy each hook type (pre-commit, commit-msg, etc.) from the master hook.
Dir.chdir(hooks_path) do
Overcommit::Utils.supported_hook_types.each do |hook_type|
unless can_replace_file?(hook_type)
@@ -115,7 +117,7 @@ def install_hook_symlinks
'was not installed by Overcommit'
end
FileUtils.rm_f(hook_type)
- Overcommit::Utils::FileUtils.symlink('overcommit-hook', hook_type)
+ FileUtils.cp('overcommit-hook', hook_type)
end
end
end
@@ -138,8 +140,8 @@ def preserve_old_hooks
FileUtils.mv(hook_file, old_hooks_path)
end
end
- # Remove old-hooks directory if empty
- FileUtils.rmdir(old_hooks_path)
+ # Remove old-hooks directory if empty (i.e. no old hooks were preserved)
+ FileUtils.rmdir(old_hooks_path) if Dir.entries(old_hooks_path).size <= 2
end
def restore_old_hooks
@@ -158,7 +160,7 @@ def restore_old_hooks
log.success "Successfully restored old hooks from #{old_hooks_path}"
end
- def uninstall_hook_symlinks
+ def uninstall_hook_files
return unless File.directory?(hooks_path)
Dir.chdir(hooks_path) do
@@ -172,18 +174,21 @@ def install_starter_config
repo_config_file = File.join(@target, Overcommit::CONFIG_FILE_NAME)
return if File.exist?(repo_config_file)
+
FileUtils.cp(File.join(Overcommit::HOME, 'config', 'starter.yml'), repo_config_file)
end
def overcommit_hook?(file)
- return true if File.read(file) =~ /OVERCOMMIT_DISABLE/
- # TODO: Remove these checks once we hit version 1.0
- Overcommit::Utils::FileUtils.symlink?(file) &&
- Overcommit::Utils::FileUtils.readlink(file) == 'overcommit-hook'
+ File.read(file) =~ /OVERCOMMIT_DISABLE/
rescue Errno::ENOENT
# Some Ruby implementations (e.g. JRuby) raise an error when the file
# doesn't exist. Standardize the behavior to return false.
false
end
+
+ # Returns the configuration for this repository.
+ def config(options = {})
+ Overcommit::ConfigurationLoader.new(log, options).load_repo_config
+ end
end
end
diff --git a/lib/overcommit/interrupt_handler.rb b/lib/overcommit/interrupt_handler.rb
index 96ee2400..4dc085ab 100644
--- a/lib/overcommit/interrupt_handler.rb
+++ b/lib/overcommit/interrupt_handler.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'singleton'
# Provides a handler for interrupt signals (SIGINT), allowing the application to
diff --git a/lib/overcommit/logger.rb b/lib/overcommit/logger.rb
index f726e617..9f5d4248 100644
--- a/lib/overcommit/logger.rb
+++ b/lib/overcommit/logger.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
module Overcommit
# Encapsulates all communication to an output source.
class Logger
@@ -11,6 +13,12 @@ def self.silent
# @param out [IO]
def initialize(out)
@out = out
+ @colorize =
+ if ENV.key?('OVERCOMMIT_COLOR')
+ !%w[0 false no].include?(ENV['OVERCOMMIT_COLOR'])
+ else
+ @out.tty?
+ end
end
# Write output without a trailing newline.
@@ -23,6 +31,11 @@ def newline
log
end
+ # Flushes the [IO] object for partial lines
+ def flush
+ @out.flush if @out.respond_to? :flush
+ end
+
# Write a line of output.
#
# A newline character will always be appended.
@@ -32,7 +45,7 @@ def log(*args)
# Write a line of output if debug mode is enabled.
def debug(*args)
- color('35', *args) unless ENV.fetch('OVERCOMMIT_DEBUG', '').empty?
+ color('35', *args) unless ENV.fetch('OVERCOMMIT_DEBUG') { '' }.empty?
end
# Write a line of output that is intended to be emphasized.
@@ -78,7 +91,7 @@ def bold_warning(*args)
# @param partial [true,false] whether to omit a newline
def color(code, str, partial = false)
send(partial ? :partial : :log,
- @out.tty? ? "\033[#{code}m#{str}\033[0m" : str)
+ @colorize ? "\033[#{code}m#{str}\033[0m" : str)
end
end
end
diff --git a/lib/overcommit/message_processor.rb b/lib/overcommit/message_processor.rb
index eac9fb00..c1bedf96 100644
--- a/lib/overcommit/message_processor.rb
+++ b/lib/overcommit/message_processor.rb
@@ -8,12 +8,12 @@ module Overcommit
# output tuple from an array of {Overcommit::Hook::Message}s, respecting the
# configuration settings for the given hook.
class MessageProcessor
- ERRORS_MODIFIED_HEADER = 'Errors on modified lines:'.freeze
- WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:'.freeze
- ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:".freeze
- WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:".freeze
- ERRORS_GENERIC_HEADER = 'Errors:'.freeze
- WARNINGS_GENERIC_HEADER = 'Warnings:'.freeze
+ ERRORS_MODIFIED_HEADER = 'Errors on modified lines:'
+ WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:'
+ ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:"
+ WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:"
+ ERRORS_GENERIC_HEADER = 'Errors:'
+ WARNINGS_GENERIC_HEADER = 'Warnings:'
# @param hook [Overcommit::Hook::Base]
# @param unmodified_lines_setting [String] how to treat messages on
diff --git a/lib/overcommit/os.rb b/lib/overcommit/os.rb
index d61864cf..7b525a1a 100644
--- a/lib/overcommit/os.rb
+++ b/lib/overcommit/os.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'rbconfig'
module Overcommit
@@ -27,7 +29,7 @@ def linux?
private
def host_os
- @os ||= ::RbConfig::CONFIG['host_os'].freeze
+ @host_os ||= ::RbConfig::CONFIG['host_os'].freeze
end
end
diff --git a/lib/overcommit/printer.rb b/lib/overcommit/printer.rb
index ef669d07..fcb69653 100644
--- a/lib/overcommit/printer.rb
+++ b/lib/overcommit/printer.rb
@@ -1,4 +1,4 @@
-# encoding: utf-8
+# frozen_string_literal: true
require 'monitor'
@@ -8,7 +8,8 @@ module Overcommit
class Printer
attr_reader :log
- def initialize(logger, context)
+ def initialize(config, logger, context)
+ @config = config
@log = logger
@context = context
@lock = Monitor.new # Need to use monitor so we can have re-entrant locks
@@ -17,7 +18,7 @@ def initialize(logger, context)
# Executed at the very beginning of running the collection of hooks.
def start_run
- log.bold "Running #{hook_script_name} hooks"
+ log.bold "Running #{hook_script_name} hooks" unless @config['quiet']
end
def nothing_to_run
@@ -36,21 +37,20 @@ def required_hook_not_skipped(hook)
def end_hook(hook, status, output)
# Want to print the header for quiet hooks only if the result wasn't good
# so that the user knows what failed
- print_header(hook) if !hook.quiet? || status != :pass
+ print_header(hook) if (!hook.quiet? && !@config['quiet']) || status != :pass
print_result(hook, status, output)
end
def interrupt_triggered
- log.newline
- log.error 'Interrupt signal received. Stopping hooks...'
+ log.error "\nInterrupt signal received. Stopping hooks..."
end
# Executed when a hook run was interrupted/cancelled by user.
def run_interrupted
log.newline
log.warning '⚠ Hook run interrupted by user'
- log.newline
+ log.warning "⚠ If files appear modified/missing, check your stash to recover them\n"
end
# Executed when one or more hooks by the end of the run.
@@ -69,8 +69,16 @@ def run_warned
# Executed when no hooks failed by the end of the run.
def run_succeeded
+ unless @config['quiet']
+ log.newline
+ log.success "✓ All #{hook_script_name} hooks passed"
+ log.newline
+ end
+ end
+
+ def hook_run_failed(message)
log.newline
- log.success "✓ All #{hook_script_name} hooks passed"
+ log.log message
log.newline
end
@@ -81,12 +89,13 @@ def print_header(hook)
log.partial hook.description
log.partial '.' * [70 - hook.description.length - hook_name.length, 0].max
log.partial hook_name
+ log.flush
end
- def print_result(hook, status, output)
+ def print_result(hook, status, output) # rubocop:disable Metrics/CyclomaticComplexity
case status
when :pass
- log.success 'OK' unless hook.quiet?
+ log.success 'OK' unless @config['quiet'] || hook.quiet?
when :warn
log.warning 'WARNING'
print_report(output, :bold_warning)
@@ -124,9 +133,7 @@ def synchronize_all_methods
self.class.__send__(:alias_method, old_method, method_name)
self.class.send(:define_method, new_method) do |*args|
- @lock.synchronize do
- __send__(old_method, *args)
- end
+ @lock.synchronize { __send__(old_method, *args) }
end
self.class.__send__(:alias_method, method_name, new_method)
diff --git a/lib/overcommit/subprocess.rb b/lib/overcommit/subprocess.rb
index df872995..41175fb9 100644
--- a/lib/overcommit/subprocess.rb
+++ b/lib/overcommit/subprocess.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
require 'childprocess'
require 'tempfile'
+require 'overcommit/os'
module Overcommit
# Manages execution of a child process, collecting the exit status and
@@ -36,7 +39,7 @@ def spawn(args, options = {})
if options[:input]
begin
process.io.stdin.puts(options[:input])
- rescue # rubocop:disable Lint/HandleExceptions
+ rescue StandardError
# Silently ignore if the standard input stream of the spawned
# process is closed before we get a chance to write to it. This
# happens on JRuby a lot.
diff --git a/lib/overcommit/utils.rb b/lib/overcommit/utils.rb
index dc2fba61..130046c3 100644
--- a/lib/overcommit/utils.rb
+++ b/lib/overcommit/utils.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'pathname'
require 'overcommit/os'
require 'overcommit/subprocess'
@@ -43,38 +45,31 @@ def script_path(script)
def repo_root
@repo_root ||=
begin
- git_dir = Pathname.new(File.expand_path('.')).enum_for(:ascend).find do |path|
- File.exist?(File.join(path, '.git'))
- end
-
- unless git_dir
- raise Overcommit::Exceptions::InvalidGitRepo, 'no .git directory found'
+ result = execute(%w[git rev-parse --show-toplevel])
+ unless result.success?
+ raise Overcommit::Exceptions::InvalidGitRepo,
+ 'Unable to determine location of GIT_DIR. ' \
+ 'Not a recognizable Git repository!'
end
-
- git_dir.to_s
+ result.stdout.chomp("\n")
end
end
# Returns an absolute path to the .git directory for a repo.
#
- # @param repo_dir [String] root directory of git repo
# @return [String]
- def git_dir(repo_dir = repo_root)
+ def git_dir
@git_dir ||=
begin
- git_dir = File.expand_path('.git', repo_dir)
-
- # .git could also be a file that contains the location of the git directory
- unless File.directory?(git_dir)
- git_dir = File.read(git_dir)[/^gitdir: (.*)$/, 1]
-
- # Resolve relative paths
- unless git_dir.start_with?('/')
- git_dir = File.expand_path(git_dir, repo_dir)
- end
+ cmd = %w[git rev-parse]
+ cmd << (GIT_VERSION < '2.5' ? '--git-dir' : '--git-common-dir')
+ result = execute(cmd)
+ unless result.success?
+ raise Overcommit::Exceptions::InvalidGitRepo,
+ 'Unable to determine location of GIT_DIR. ' \
+ 'Not a recognizable Git repository!'
end
-
- git_dir
+ File.expand_path(result.stdout.chomp("\n"), Dir.pwd)
end
end
@@ -138,7 +133,12 @@ def in_path?(cmd)
end
# Return the parent command that triggered this hook run
+ #
+ # @return [String,nil] the command as a string, if a parent exists.
def parent_command
+ # When run in Docker containers, there may be no parent process.
+ return if Process.ppid.zero?
+
if OS.windows?
`wmic process where ProcessId=#{Process.ppid} get CommandLine /FORMAT:VALUE`.
strip.
@@ -179,7 +179,7 @@ def execute(initial_args, options = {})
end
result =
- if (splittable_args = options.fetch(:args, [])).any?
+ if (splittable_args = options.fetch(:args) { [] }).any?
debug(initial_args.join(' ') + " ... (#{splittable_args.length} splittable args)")
Overcommit::CommandSplitter.execute(initial_args, options)
else
@@ -220,7 +220,8 @@ def processor_count # rubocop:disable all
if Overcommit::OS.windows?
require 'win32ole'
result = WIN32OLE.connect('winmgmts://').ExecQuery(
- 'select NumberOfLogicalProcessors from Win32_Processor')
+ 'select NumberOfLogicalProcessors from Win32_Processor'
+ )
result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+)
elsif File.readable?('/proc/cpuinfo')
IO.read('/proc/cpuinfo').scan(/^processor/).size
@@ -284,9 +285,10 @@ def convert_glob_to_absolute(glob)
# @param pattern [String]
# @param path [String]
def matches_path?(pattern, path)
- File.fnmatch?(pattern, path,
- File::FNM_PATHNAME | # Wildcard doesn't match separator
- File::FNM_DOTMATCH # Wildcards match dotfiles
+ File.fnmatch?(
+ pattern, path,
+ File::FNM_PATHNAME | # Wildcard doesn't match separator
+ File::FNM_DOTMATCH # Wildcards match dotfiles
)
end
@@ -300,7 +302,7 @@ def matches_path?(pattern, path)
#
# @param args [Array]
def debug(*args)
- log.debug(*args) if log
+ log&.debug(*args)
end
end
end
diff --git a/lib/overcommit/utils/file_utils.rb b/lib/overcommit/utils/file_utils.rb
index f96cc8ad..cff2ce0b 100644
--- a/lib/overcommit/utils/file_utils.rb
+++ b/lib/overcommit/utils/file_utils.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'overcommit/os'
require 'overcommit/subprocess'
diff --git a/lib/overcommit/utils/messages_utils.rb b/lib/overcommit/utils/messages_utils.rb
new file mode 100644
index 00000000..31c0f8db
--- /dev/null
+++ b/lib/overcommit/utils/messages_utils.rb
@@ -0,0 +1,78 @@
+# frozen_string_literal: true
+
+module Overcommit::Utils
+ # Utility to process messages
+ module MessagesUtils
+ class << self
+ # Extract file, line number, and type of message from an error/warning
+ # messages in output.
+ #
+ # Assumes each element of `output` is a separate error/warning with all
+ # information necessary to identify it.
+ #
+ # @param output_messages [Array] unprocessed error/warning messages
+ # @param regex [Regexp] regular expression defining `file`, `line` and
+ # `type` capture groups used to extract file locations and error/warning
+ # type from each line of output
+ # @param type_categorizer [Proc] function executed against the `type`
+ # capture group to convert it to a `:warning` or `:error` symbol. Assumes
+ # `:error` if `nil`.
+ # @raise [Overcommit::Exceptions::MessageProcessingError] line of output did
+ # not match regex
+ # @return [Array]
+ def extract_messages(output_messages, regex, type_categorizer = nil)
+ output_messages.map.with_index do |message, index|
+ unless match = message.match(regex)
+ raise Overcommit::Exceptions::MessageProcessingError,
+ 'Unexpected output: unable to determine line number or type ' \
+ "of error/warning for output:\n" \
+ "#{output_messages[index..].join("\n")}"
+ end
+
+ file = extract_file(match, message)
+ line = extract_line(match, message) if match.names.include?('line') && match[:line]
+ type = extract_type(match, message, type_categorizer)
+
+ Overcommit::Hook::Message.new(type, file, line, message)
+ end
+ end
+
+ private
+
+ def extract_file(match, message)
+ return unless match.names.include?('file')
+
+ if match[:file].to_s.empty?
+ raise Overcommit::Exceptions::MessageProcessingError,
+ "Unexpected output: no file found in '#{message}'"
+ end
+
+ match[:file]
+ end
+
+ def extract_line(match, message)
+ return unless match.names.include?('line')
+
+ Integer(match[:line])
+ rescue ArgumentError, TypeError
+ raise Overcommit::Exceptions::MessageProcessingError,
+ "Unexpected output: invalid line number found in '#{message}'"
+ end
+
+ def extract_type(match, message, type_categorizer)
+ if type_categorizer
+ type_match = match.names.include?('type') ? match[:type] : nil
+ type = type_categorizer.call(type_match)
+ unless Overcommit::Hook::MESSAGE_TYPES.include?(type)
+ raise Overcommit::Exceptions::MessageProcessingError,
+ "Invalid message type '#{type}' for '#{message}': must " \
+ "be one of #{Overcommit::Hook::MESSAGE_TYPES.inspect}"
+ end
+ type
+ else
+ :error # Assume error since no categorizer was defined
+ end
+ end
+ end
+ end
+end
diff --git a/lib/overcommit/version.rb b/lib/overcommit/version.rb
index 3eafe551..872c5327 100644
--- a/lib/overcommit/version.rb
+++ b/lib/overcommit/version.rb
@@ -2,5 +2,5 @@
# Defines the gem version.
module Overcommit
- VERSION = '0.32.0.rc1'.freeze
+ VERSION = '0.67.1'
end
diff --git a/libexec/index-tags b/libexec/index-tags
index 3c4478e2..51a6d516 100755
--- a/libexec/index-tags
+++ b/libexec/index-tags
@@ -6,10 +6,12 @@
set -e
-trap "rm -f $GIT_DIR/tags.$$" EXIT
-err_file=$GIT_DIR/ctags.err
-if ctags --tag-relative -Rf$GIT_DIR/tags.$$ --exclude=.git "$@" 2>${err_file}; then
- mv $GIT_DIR/tags.$$ $GIT_DIR/tags
+dir="`git rev-parse --git-dir`"
+
+trap "rm -f $dir/tags.$$" EXIT
+err_file=$dir/ctags.err
+if ctags --tag-relative -Rf$dir/tags.$$ --exclude=.git "$@" 2>${err_file}; then
+ mv $dir/tags.$$ $dir/tags
[ -e ${err_file} ] && rm -f ${err_file}
else
# Ignore STDERR unless `ctags` returned a non-zero exit code
diff --git a/logo/horizontal.png b/logo/horizontal.png
new file mode 100644
index 00000000..afad79be
Binary files /dev/null and b/logo/horizontal.png differ
diff --git a/logo/square.png b/logo/square.png
new file mode 100644
index 00000000..f5ccdc3d
Binary files /dev/null and b/logo/square.png differ
diff --git a/overcommit.gemspec b/overcommit.gemspec
index 0ad9f3c9..caaa8499 100644
--- a/overcommit.gemspec
+++ b/overcommit.gemspec
@@ -1,6 +1,7 @@
-$LOAD_PATH << File.expand_path('../lib', __FILE__)
-require 'overcommit/constants'
-require 'overcommit/version'
+# frozen_string_literal: true
+
+require_relative './lib/overcommit/constants'
+require_relative './lib/overcommit/version'
Gem::Specification.new do |s|
s.name = 'overcommit'
@@ -8,12 +9,16 @@ Gem::Specification.new do |s|
s.license = 'MIT'
s.summary = 'Git hook manager'
s.description = 'Utility to install, configure, and extend Git hooks'
- s.authors = ['Brigade Engineering', 'Shane da Silva']
- s.email = ['eng@brigade.com', 'shane.dasilva@brigade.com']
+ s.authors = ['Shane da Silva']
+ s.email = ['shane@dasilva.io']
s.homepage = Overcommit::REPO_URL
s.post_install_message =
'Install hooks by running `overcommit --install` in your Git repository'
+ s.metadata = {
+ 'changelog_uri' => 'https://github.com/sds/overcommit/blob/main/CHANGELOG.md'
+ }
+
s.require_paths = %w[lib]
s.executables = ['overcommit']
@@ -24,12 +29,9 @@ Gem::Specification.new do |s|
Dir['libexec/**/*'] +
Dir['template-dir/**/*']
- s.required_ruby_version = '>= 1.9.3'
-
- s.add_dependency 'childprocess', '~> 0.5.8'
- s.add_dependency 'iniparse', '~> 1.4'
+ s.required_ruby_version = '>= 2.6'
- s.add_development_dependency 'rake', '~> 10.4'
- s.add_development_dependency 'rspec', '~> 3.0'
- s.add_development_dependency 'travis', '~> 1.7'
+ s.add_dependency 'childprocess', '>= 0.6.3', '< 6'
+ s.add_dependency 'iniparse', '~> 1.4'
+ s.add_dependency 'rexml', '>= 3.3.9'
end
diff --git a/spec/integration/committing_spec.rb b/spec/integration/committing_spec.rb
index 31da17de..1e1f65a6 100644
--- a/spec/integration/committing_spec.rb
+++ b/spec/integration/committing_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'commiting' do
@@ -32,6 +34,16 @@
end
end
+ context 'when no hooks fail on single author name' do
+ before do
+ `git config --local user.name "John"`
+ end
+
+ it 'exits successfully' do
+ subject.status.should == 0
+ end
+ end
+
context 'when no hooks fail' do
before do
`git config --local user.name "John Doe"`
diff --git a/spec/integration/configuration_signing_spec.rb b/spec/integration/configuration_signing_spec.rb
index 2b097ff0..0b1e0b38 100644
--- a/spec/integration/configuration_signing_spec.rb
+++ b/spec/integration/configuration_signing_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'yaml'
@@ -27,8 +29,8 @@
around do |example|
repo do
- echo(config.to_yaml, '.overcommit.yml')
`overcommit --install > #{File::NULL}`
+ echo(config.to_yaml, '.overcommit.yml')
`overcommit --sign` if configuration_signed
echo(new_config.to_yaml, '.overcommit.yml')
diff --git a/spec/integration/diff_flag_spec.rb b/spec/integration/diff_flag_spec.rb
new file mode 100644
index 00000000..dcaa1bd3
--- /dev/null
+++ b/spec/integration/diff_flag_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'overcommit --diff' do
+ subject { shell(%w[overcommit --diff main]) }
+
+ context 'when using an existing pre-commit hook script' do
+ let(:script_name) { 'test-script' }
+ let(:script_contents) { "#!/bin/bash\nexit 0" }
+ let(:script_path) { ".#{Overcommit::OS::SEPARATOR}#{script_name}" }
+
+ let(:config) do
+ {
+ 'PreCommit' => {
+ 'MyHook' => {
+ 'enabled' => true,
+ 'required_executable' => script_path,
+ }
+ }
+ }
+ end
+
+ around do |example|
+ repo do
+ File.open('.overcommit.yml', 'w') { |f| f.puts(config.to_yaml) }
+ echo(script_contents, script_path)
+ `git add #{script_path}`
+ FileUtils.chmod(0o755, script_path)
+ example.run
+ end
+ end
+
+ it 'completes successfully without blocking' do
+ wait_until(timeout: 10) { subject } # Need to wait long time for JRuby startup
+ subject.status.should == 0
+ end
+ end
+end
diff --git a/spec/integration/disable_overcommit_spec.rb b/spec/integration/disable_overcommit_spec.rb
index 2bcdaebf..04243cd5 100644
--- a/spec/integration/disable_overcommit_spec.rb
+++ b/spec/integration/disable_overcommit_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'disabling Overcommit' do
diff --git a/spec/integration/gemfile_option_spec.rb b/spec/integration/gemfile_option_spec.rb
index 5d29c7c8..a6a6a4ba 100644
--- a/spec/integration/gemfile_option_spec.rb
+++ b/spec/integration/gemfile_option_spec.rb
@@ -1,96 +1,158 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'specifying `gemfile` option in Overcommit configuration' do
- let(:repo_root) { File.expand_path(File.join('..', '..'), File.dirname(__FILE__)) }
- let(:fake_gem_path) { File.join('lib', 'my_fake_gem') }
-
- # We point the overcommit gem back to this repo since we can't assume the gem
- # has already been installed in a test environment
- let(:gemfile) { normalize_indent(<<-RUBY) }
- source 'https://rubygems.org'
-
- gem 'overcommit', path: '#{repo_root}'
- gem 'my_fake_gem', path: '#{fake_gem_path}'
- RUBY
-
- let(:gemspec) { normalize_indent(<<-RUBY) }
- Gem::Specification.new do |s|
- s.name = 'my_fake_gem'
- s.version = '1.0.0'
- s.author = 'John Doe'
- s.email = 'john.doe@example.com'
- s.summary = 'A fake gem'
- s.files = [File.join('lib', 'my_fake_gem.rb')]
- end
- RUBY
-
- # Specify a hook that depends on an external gem to test Gemfile loading
- let(:hook) { normalize_indent(<<-RUBY) }
- module Overcommit::Hook::PreCommit
- class FakeHook < Base
- def run
- require 'my_fake_gem'
- :pass
+ context 'given a project that uses a Gemfile' do
+ let(:repo_root) { File.expand_path(File.join('..', '..'), File.dirname(__FILE__)) }
+ let(:fake_gem_path) { File.join('lib', 'my_fake_gem') }
+
+ # We point the overcommit gem back to this repo since we can't assume the gem
+ # has already been installed in a test environment
+ let(:gemfile) { normalize_indent(<<-RUBY) }
+ source 'https://rubygems.org'
+
+ gem 'overcommit', path: '#{repo_root}'
+ gem 'my_fake_gem', path: '#{fake_gem_path}'
+ gem 'ffi' if Gem.win_platform? # Necessary for test to pass on Windows
+ RUBY
+
+ let(:gemspec) { normalize_indent(<<-RUBY) }
+ Gem::Specification.new do |s|
+ s.name = 'my_fake_gem'
+ s.version = '1.0.0'
+ s.author = 'John Doe'
+ s.license = 'MIT'
+ s.homepage = 'https://example.com'
+ s.email = 'john.doe@example.com'
+ s.summary = 'A fake gem'
+ s.files = [File.join('lib', 'my_fake_gem.rb')]
+ end
+ RUBY
+
+ # Specify a hook that depends on an external gem to test Gemfile loading
+ let(:hook) { normalize_indent(<<-RUBY) }
+ module Overcommit::Hook::PreCommit
+ class FakeHook < Base
+ def run
+ require 'my_fake_gem'
+ :pass
+ end
+ end
+ end
+ RUBY
+
+ let(:config) { normalize_indent(<<-YAML) }
+ verify_signatures: false
+
+ CommitMsg:
+ ALL:
+ enabled: false
+
+ PreCommit:
+ ALL:
+ enabled: false
+ FakeHook:
+ enabled: true
+ requires_files: false
+ YAML
+
+ around do |example|
+ repo do
+ # Since RSpec is being run within a Bundler context we need to clear it
+ # in order to not taint the test
+ Bundler.with_unbundled_env do
+ FileUtils.mkdir_p(File.join(fake_gem_path, 'lib'))
+ echo(gemspec, File.join(fake_gem_path, 'my_fake_gem.gemspec'))
+ touch(File.join(fake_gem_path, 'lib', 'my_fake_gem.rb'))
+
+ echo(gemfile, '.overcommit_gems.rb')
+ `bundle install --gemfile=.overcommit_gems.rb`
+
+ echo(config, '.overcommit.yml')
+
+ # Set BUNDLE_GEMFILE so we load Overcommit from the current repo
+ ENV['BUNDLE_GEMFILE'] = '.overcommit_gems.rb'
+ `bundle exec overcommit --install > #{File::NULL}`
+ FileUtils.mkdir_p(File.join('.git-hooks', 'pre_commit'))
+ echo(hook, File.join('.git-hooks', 'pre_commit', 'fake_hook.rb'))
+
+ Overcommit::Utils.with_environment 'OVERCOMMIT_NO_VERIFY' => '1' do
+ example.run
+ end
end
end
end
- RUBY
-
- let(:config) { normalize_indent(<<-YAML) }
- verify_signatures: false
-
- CommitMsg:
- ALL:
- enabled: false
-
- PreCommit:
- ALL:
- enabled: false
- FakeHook:
- enabled: true
- requires_files: false
- YAML
-
- around do |example|
- repo do
- # Since RSpec is being run within a Bundler context we need to clear it
- # in order to not taint the test
- Bundler.with_clean_env do
- FileUtils.mkdir_p(File.join(fake_gem_path, 'lib'))
- echo(gemspec, File.join(fake_gem_path, 'my_fake_gem.gemspec'))
- touch(File.join(fake_gem_path, 'lib', 'my_fake_gem.rb'))
-
- echo(gemfile, '.overcommit_gems.rb')
- `bundle install --gemfile=.overcommit_gems.rb`
+ subject { shell(%w[git commit --allow-empty -m Test]) }
+
+ context 'when configuration specifies the gemfile' do
+ let(:config) { "gemfile: .overcommit_gems.rb\n" + super() }
+
+ it 'runs the hook successfully' do
+ subject.status.should == 0
+ end
+ end
+
+ context 'when configuration does not specify the gemfile' do
+ it 'fails to run the hook' do
+ subject.status.should_not == 0
+ end
+ end
+ end
+
+ context 'given a project that does not use a Gemfile' do
+ let(:hook) { normalize_indent(<<-RUBY) }
+ module Overcommit::Hook::PreCommit
+ class NoInvalidGemfileHook < Base
+ def run
+ if (gemfile = ENV["BUNDLE_GEMFILE"])
+ raise unless File.exist?(gemfile)
+ end
+
+ :pass
+ end
+ end
+ end
+ RUBY
+
+ let(:config) { normalize_indent(<<-YAML) }
+ verify_signatures: false
+
+ CommitMsg:
+ ALL:
+ enabled: false
+
+ PreCommit:
+ ALL:
+ enabled: false
+ NoInvalidGemfileHook:
+ enabled: true
+ requires_files: false
+ YAML
+
+ around do |example|
+ repo do
echo(config, '.overcommit.yml')
- # Set BUNDLE_GEMFILE so we load Overcommit from the current repo
- ENV['BUNDLE_GEMFILE'] = '.overcommit_gems.rb'
- `bundle exec overcommit --install > #{File::NULL}`
+ `overcommit --install > #{File::NULL}`
FileUtils.mkdir_p(File.join('.git-hooks', 'pre_commit'))
- echo(hook, File.join('.git-hooks', 'pre_commit', 'fake_hook.rb'))
+ echo(hook, File.join('.git-hooks', 'pre_commit', 'no_invalid_gemfile_hook.rb'))
Overcommit::Utils.with_environment 'OVERCOMMIT_NO_VERIFY' => '1' do
example.run
end
end
end
- end
-
- subject { shell(%w[git commit --allow-empty -m Test]) }
- context 'when configuration specifies the gemfile' do
- let(:config) { "gemfile: .overcommit_gems.rb\n" + super() }
+ subject { shell(%w[git commit --allow-empty -m Test]) }
- it 'runs the hook successfully' do
- subject.status.should == 0
- end
- end
+ context 'when configuration explicitly sets the gemfile to false' do
+ let(:config) { "gemfile: false\n" + super() }
- context 'when configuration does not specify the gemfile' do
- it 'fails to run the hook' do
- subject.status.should_not == 0
+ it 'runs the hook successfully' do
+ subject.status.should == 0
+ end
end
end
end
diff --git a/spec/integration/hook_signing_spec.rb b/spec/integration/hook_signing_spec.rb
index c5a71b4b..6392a64e 100644
--- a/spec/integration/hook_signing_spec.rb
+++ b/spec/integration/hook_signing_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'yaml'
@@ -49,7 +51,7 @@
`overcommit --install > #{File::NULL}`
echo(hook_script, script_path)
- FileUtils.chmod(0755, script_path)
+ FileUtils.chmod(0o755, script_path)
`git add #{script_path}`
`overcommit --sign`
@@ -88,7 +90,7 @@
`overcommit --install > #{File::NULL}`
echo(hook_script, script_path)
- FileUtils.chmod(0755, script_path)
+ FileUtils.chmod(0o755, script_path)
`git add #{script_path}`
`overcommit --sign`
diff --git a/spec/integration/installing_overcommit_spec.rb b/spec/integration/installing_overcommit_spec.rb
index dbd4cc81..5c8ebe35 100644
--- a/spec/integration/installing_overcommit_spec.rb
+++ b/spec/integration/installing_overcommit_spec.rb
@@ -1,6 +1,20 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'installing Overcommit' do
+ let(:enable_verification) { true }
+
+ it 'signs the configuration file' do
+ repo do
+ `overcommit --install`
+ touch('some-file')
+ `git add some-file`
+ result = shell(%w[git commit --allow-empty -m Test])
+ result.status.should == 0
+ end
+ end
+
context 'when template directory points to the Overcommit template directory' do
around do |example|
repo(template_dir: Overcommit::Installer::TEMPLATE_DIRECTORY) do
@@ -9,12 +23,6 @@
end
it 'automatically installs Overcommit hooks for new repositories' do
- if Overcommit::OS.windows?
- # Symlinks in template-dir are not compatible with Windows.
- # Windows users will need to manually install Overcommit for now.
- skip 'Unix symlinks not compatible with Windows'
- end
-
Overcommit::Utils.supported_hook_types.each do |hook_type|
hook_file = File.join('.git', 'hooks', hook_type)
File.read(hook_file).should include 'OVERCOMMIT'
@@ -26,10 +34,10 @@
`overcommit --install`
end
- it 'replaces the hooks with symlinks' do
+ it 'leaves the hooks intact' do
Overcommit::Utils.supported_hook_types.each do |hook_type|
hook_file = File.join('.git', 'hooks', hook_type)
- Overcommit::Utils::FileUtils.symlink?(hook_file).should == true
+ File.read(hook_file).should include 'OVERCOMMIT'
end
end
end
diff --git a/spec/integration/parallelize_spec.rb b/spec/integration/parallelize_spec.rb
new file mode 100644
index 00000000..471aa7b3
--- /dev/null
+++ b/spec/integration/parallelize_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'timeout'
+
+describe 'running a hook with parallelism disabled' do
+ subject { shell(%w[git commit --allow-empty -m Test]) }
+
+ let(:config) { <<-YML }
+ concurrency: 20
+ CommitMsg:
+ TrailingPeriod:
+ enabled: true
+ parallelize: false
+ command: ['ruby', '-e', 'sleep 1']
+ TextWidth:
+ enabled: true
+ parallelize: true
+ processors: 1
+ YML
+
+ around do |example|
+ repo do
+ File.open('.overcommit.yml', 'w') { |f| f.write(config) }
+ `overcommit --install > #{File::NULL}`
+ example.run
+ end
+ end
+
+ # Test fails on Ruby 3.0 on Windows but nothing else. Would glady accept a pull
+ # request that resolves.
+ unless Overcommit::OS.windows? &&
+ Overcommit::Utils::Version.new(RUBY_VERSION) >= '3' &&
+ Overcommit::Utils::Version.new(RUBY_VERSION) < '3.1'
+ it 'does not hang' do
+ result = Timeout.timeout(5) { subject }
+ result.stderr.should_not include 'No live threads left. Deadlock?'
+ end
+ end
+end
diff --git a/spec/integration/protected_branches_spec.rb b/spec/integration/protected_branches_spec.rb
new file mode 100644
index 00000000..115be29d
--- /dev/null
+++ b/spec/integration/protected_branches_spec.rb
@@ -0,0 +1,277 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PrePush::ProtectedBranches,
+ if: Overcommit::GIT_VERSION >= '2.0' do
+ let(:flags) { '' }
+ let(:pushed_ref) { remote_ref }
+ subject do
+ shell("git push #{flags} origin #{pushed_ref}:#{remote_ref}".split)
+ end
+
+ let(:config) { <<-YML }
+ CommitMsg:
+ ALL:
+ enabled: false
+ PreCommit:
+ ALL:
+ enabled: false
+ PrePush:
+ ALL:
+ enabled: false
+ ProtectedBranches:
+ enabled: true
+ branches:
+ - protected
+ - protected_for_destructive_only:
+ destructive_only: true
+ YML
+
+ around do |example|
+ remote_repo = repo do
+ `git checkout -b protected > #{File::NULL} 2>&1`
+ `git commit --allow-empty -m "Remote commit"`
+ `git checkout -b unprotected > #{File::NULL} 2>&1`
+ `git checkout -b dummy > #{File::NULL} 2>&1`
+ end
+ repo do
+ File.open('.overcommit.yml', 'w') { |f| f.write(config) }
+ `git remote add origin file://#{remote_repo}`
+ `git checkout -b #{remote_ref} > #{File::NULL} 2>&1`
+ `git commit --allow-empty -m "Local commit"`
+ example.run
+ end
+ end
+
+ shared_context 'deleting' do
+ let(:pushed_ref) { '' }
+ end
+
+ shared_context 'force-pushing' do
+ let(:flags) { '--force' }
+ end
+
+ shared_context 'remote exists locally' do
+ before { `git fetch origin #{remote_ref} > #{File::NULL} 2>&1` }
+ end
+
+ shared_context 'local branch up-to-date' do
+ before { `git rebase --keep-empty origin/#{remote_ref} > #{File::NULL} 2>&1` }
+ end
+
+ shared_context 'ProtectedBranches enabled' do
+ before { `overcommit --install > #{File::NULL}` }
+ end
+
+ shared_examples 'push succeeds' do
+ it 'exits successfully' do
+ subject.status.should == 0
+ end
+ end
+
+ shared_examples 'push fails' do
+ it 'exits with a non-zero status' do
+ subject.status.should_not == 0
+ end
+ end
+
+ shared_examples 'push succeeds when remote exists locally' do
+ context 'when remote exists locally' do
+ include_context 'remote exists locally'
+
+ context 'when up-to-date with remote' do
+ include_context 'local branch up-to-date'
+ include_examples 'push succeeds'
+ end
+
+ context 'when not up-to-date with remote' do
+ include_examples 'push succeeds'
+ end
+ end
+
+ context 'when remote does not exist locally' do
+ include_examples 'push fails'
+ end
+ end
+
+ shared_examples 'push succeeds when up-to-date with remote' do
+ context 'when remote exists locally' do
+ include_context 'remote exists locally'
+
+ context 'when up-to-date with remote' do
+ include_context 'local branch up-to-date'
+ include_examples 'push succeeds'
+ end
+
+ context 'when not up-to-date with remote' do
+ include_examples 'push fails'
+ end
+ end
+
+ context 'when remote does not exist locally' do
+ include_examples 'push fails'
+ end
+ end
+
+ shared_examples 'push always fails' do
+ context 'when remote exists locally' do
+ include_context 'remote exists locally'
+
+ context 'when up-to-date with remote' do
+ include_context 'local branch up-to-date'
+ include_examples 'push fails'
+ end
+
+ context 'when not up-to-date with remote' do
+ include_examples 'push fails'
+ end
+ end
+
+ context 'when remote does not exist locally' do
+ include_examples 'push fails'
+ end
+ end
+
+ shared_examples 'push always succeeds' do
+ context 'when remote exists locally' do
+ include_context 'remote exists locally'
+
+ context 'when up-to-date with remote' do
+ include_context 'local branch up-to-date'
+ include_examples 'push succeeds'
+ end
+
+ context 'when not up-to-date with remote' do
+ include_examples 'push succeeds'
+ end
+ end
+
+ context 'when remote does not exist locally' do
+ include_examples 'push succeeds'
+ end
+ end
+
+ context 'when pushing to a protected branch' do
+ let(:remote_ref) { 'protected' }
+
+ context 'when force-pushing' do
+ include_context 'force-pushing'
+
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push succeeds when up-to-date with remote'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push always succeeds'
+ end
+ end
+
+ context 'when deleting' do
+ include_context 'deleting'
+
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push always fails'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push always succeeds'
+ end
+ end
+
+ context 'when not deleting or force-pushing' do
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push succeeds when up-to-date with remote'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push succeeds when up-to-date with remote'
+ end
+ end
+ end
+
+ context 'when pushing to an unprotected branch' do
+ let(:remote_ref) { 'unprotected' }
+
+ context 'when force-pushing' do
+ include_context 'force-pushing'
+
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push always succeeds'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push always succeeds'
+ end
+ end
+
+ context 'when deleting' do
+ include_context 'deleting'
+
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push always succeeds'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push always succeeds'
+ end
+ end
+
+ context 'when not deleting or force-pushing' do
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push succeeds when up-to-date with remote'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push succeeds when up-to-date with remote'
+ end
+ end
+ end
+
+ context 'when pushing to a nonexistent branch' do
+ let(:remote_ref) { 'new-branch' }
+
+ context 'when force-pushing' do
+ include_context 'force-pushing'
+
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push succeeds'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push succeeds'
+ end
+ end
+
+ context 'when deleting' do
+ include_context 'deleting'
+
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push fails'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push fails'
+ end
+ end
+
+ context 'when not deleting or force-pushing' do
+ context 'with ProtectedBranches enabled' do
+ include_context 'ProtectedBranches enabled'
+ include_examples 'push succeeds'
+ end
+
+ context 'with ProtectedBranches disabled' do
+ include_examples 'push succeeds'
+ end
+ end
+ end
+end
diff --git a/spec/integration/resolving_cherry_pick_conflict_spec.rb b/spec/integration/resolving_cherry_pick_conflict_spec.rb
index cb9f91f5..2a7f053c 100644
--- a/spec/integration/resolving_cherry_pick_conflict_spec.rb
+++ b/spec/integration/resolving_cherry_pick_conflict_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'resolving cherry-pick conflicts' do
- subject { shell(%w[git commit -m "Resolve conflicts" -i some-file]) }
+ subject { shell(%w[git commit -m Test -i some-file]) }
let(:config) { <<-YML }
PreCommit:
@@ -37,15 +39,18 @@
end
it 'exits with a non-zero status' do
+ skip 'Skipping flakey test on AppVeyor Windows builds' if ENV['APPVEYOR']
subject.status.should_not == 0
end
it 'does not remove the CHERRY_PICK_HEAD file' do
+ skip 'Skipping flakey test on AppVeyor Windows builds' if ENV['APPVEYOR']
subject
Dir['.git/*'].should include '.git/CHERRY_PICK_HEAD'
end
it 'keeps the commit message from the cherry-picked commit' do
+ skip 'Skipping flakey test on AppVeyor Windows builds' if ENV['APPVEYOR']
subject
File.read(File.join('.git', 'MERGE_MSG')).should include 'Add Branch 2 addition'
end
diff --git a/spec/integration/resolving_merge_conflict_spec.rb b/spec/integration/resolving_merge_conflict_spec.rb
index de1e5301..997dcb09 100644
--- a/spec/integration/resolving_merge_conflict_spec.rb
+++ b/spec/integration/resolving_merge_conflict_spec.rb
@@ -1,7 +1,9 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'resolving merge conflicts' do
- subject { shell(%w[git commit -m "Resolve conflicts" -i some-file]) }
+ subject { shell(%w[git commit -m Test -i some-file]) }
around do |example|
repo do
diff --git a/spec/integration/run_flag_spec.rb b/spec/integration/run_flag_spec.rb
index d3472b22..65284f29 100644
--- a/spec/integration/run_flag_spec.rb
+++ b/spec/integration/run_flag_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'overcommit --run' do
@@ -29,7 +31,7 @@
File.open('.overcommit.yml', 'w') { |f| f.puts(config.to_yaml) }
echo(script_contents, script_path)
`git add #{script_path}`
- FileUtils.chmod(0755, script_path)
+ FileUtils.chmod(0o755, script_path)
example.run
end
end
diff --git a/spec/integration/template_dir_spec.rb b/spec/integration/template_dir_spec.rb
index 99ef12b7..ebcff83c 100644
--- a/spec/integration/template_dir_spec.rb
+++ b/spec/integration/template_dir_spec.rb
@@ -1,4 +1,7 @@
+# frozen_string_literal: true
+
require 'spec_helper'
+require 'fileutils'
describe 'template directory' do
let(:template_dir) { File.join(Overcommit::HOME, 'template-dir') }
@@ -16,15 +19,16 @@
Overcommit::Utils::FileUtils.symlink?(master_hook).should == false
end
- it 'contains all other hooks as symlinks to the master hook' do
- if Overcommit::OS.windows?
- # Symlinks in template-dir are not compatible with Windows.
- # Windows users will need to manually install Overcommit for now.
- skip 'Unix symlinks not compatible with Windows'
+ it 'contains all other hooks as copies of the master hook' do
+ Overcommit::Utils.supported_hook_types.each do |hook_type|
+ FileUtils.compare_file(File.join(hooks_dir, hook_type),
+ File.join(hooks_dir, 'overcommit-hook')).should == true
end
+ end
+ it 'contains no symlinks' do
Overcommit::Utils.supported_hook_types.each do |hook_type|
- Overcommit::Utils::FileUtils.symlink?(File.join(hooks_dir, hook_type)).should == true
+ Overcommit::Utils::FileUtils.symlink?(File.join(hooks_dir, hook_type)).should == false
end
end
end
diff --git a/spec/overcommit/cli_spec.rb b/spec/overcommit/cli_spec.rb
index fa12d359..3d95b09d 100644
--- a/spec/overcommit/cli_spec.rb
+++ b/spec/overcommit/cli_spec.rb
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'overcommit/cli'
+require 'overcommit/hook_context/diff'
require 'overcommit/hook_context/run_all'
describe Overcommit::CLI do
@@ -123,5 +126,30 @@
subject
end
end
+
+ context 'with the diff switch specified' do
+ let(:arguments) { ['--diff=some-ref'] }
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+
+ before do
+ cli.stub(:halt)
+ Overcommit::HookRunner.any_instance.stub(:run)
+ end
+
+ it 'creates a HookRunner with the diff context' do
+ Overcommit::HookRunner.should_receive(:new).
+ with(config,
+ logger,
+ instance_of(Overcommit::HookContext::Diff),
+ instance_of(Overcommit::Printer)).
+ and_call_original
+ subject
+ end
+
+ it 'runs the HookRunner' do
+ Overcommit::HookRunner.any_instance.should_receive(:run)
+ subject
+ end
+ end
end
end
diff --git a/spec/overcommit/command_splitter_spec.rb b/spec/overcommit/command_splitter_spec.rb
index 2da34ba0..5db2e82f 100644
--- a/spec/overcommit/command_splitter_spec.rb
+++ b/spec/overcommit/command_splitter_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::CommandSplitter do
diff --git a/spec/overcommit/configuration_loader_spec.rb b/spec/overcommit/configuration_loader_spec.rb
index a454f10e..90497201 100644
--- a/spec/overcommit/configuration_loader_spec.rb
+++ b/spec/overcommit/configuration_loader_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::ConfigurationLoader do
@@ -55,5 +57,71 @@
end
end
end
+
+ context 'when repo only contains a repo level configuration file' do
+ let(:config_contents) { <<-CFG }
+ PreCommit:
+ Rubocop:
+ enabled: true
+ CFG
+
+ around do |example|
+ repo do
+ File.open('.overcommit.yml', 'w') { |f| f.write(config_contents) }
+ example.run
+ end
+ end
+
+ it 'includes default settings' do
+ subject
+ subject.for_hook('CapitalizedSubject', 'CommitMsg').should include('enabled' => true)
+ end
+
+ it 'includes .overwrite.yml configs' do
+ subject
+ subject.for_hook('Rubocop', 'PreCommit').should include('enabled' => true)
+ end
+ end
+
+ context 'when repo also contains a local configuration file' do
+ let(:local_config_contents) { <<-CFG }
+ plugin_directory: 'some-different-directory'
+ CFG
+
+ around do |example|
+ repo do
+ File.open('.overcommit.yml', 'w') { |f| f.write(config_contents) }
+ File.open('.local-overcommit.yml', 'w') { |f| f.write(local_config_contents) }
+ example.run
+ end
+ end
+
+ let(:config_contents) { <<-CFG }
+ PreCommit:
+ ScssLint:
+ enabled: true
+ CFG
+
+ let(:local_config_contents) { <<-CFG }
+ PreCommit:
+ Rubocop:
+ enabled: true
+ CFG
+
+ it 'includes default settings' do
+ subject
+ subject.for_hook('CapitalizedSubject', 'CommitMsg').should include('enabled' => true)
+ end
+
+ it 'includes .overwrite.yml configs' do
+ subject
+ subject.for_hook('ScssLint', 'PreCommit').should include('enabled' => true)
+ end
+
+ it 'includes .local-overwrite.yml configs' do
+ subject
+ subject.for_hook('Rubocop', 'PreCommit').should include('enabled' => true)
+ end
+ end
end
end
diff --git a/spec/overcommit/configuration_spec.rb b/spec/overcommit/configuration_spec.rb
index 4d43188c..d9860400 100644
--- a/spec/overcommit/configuration_spec.rb
+++ b/spec/overcommit/configuration_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Configuration do
diff --git a/spec/overcommit/configuration_validator_spec.rb b/spec/overcommit/configuration_validator_spec.rb
index 351eed7f..52320633 100644
--- a/spec/overcommit/configuration_validator_spec.rb
+++ b/spec/overcommit/configuration_validator_spec.rb
@@ -1,7 +1,11 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::ConfigurationValidator do
- let(:options) { {} }
+ let(:output) { StringIO.new }
+ let(:logger) { Overcommit::Logger.new(output) }
+ let(:options) { { logger: logger } }
let(:config) { Overcommit::Configuration.new(config_hash, validate: false) }
subject { described_class.new.validate(config, config_hash, options) }
@@ -22,6 +26,56 @@
end
end
+ context 'when hook has `env` set' do
+ let(:config_hash) do
+ {
+ 'PreCommit' => {
+ 'MyHook' => {
+ 'enabled' => true,
+ 'env' => env,
+ },
+ },
+ }
+ end
+
+ context 'and it is a single string' do
+ let(:env) { 'OVERCOMMIT_ENV_VAR=1' }
+
+ it 'raises an error and mentions `env` must be a hash' do
+ expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError
+ output.string.should =~ /must be a hash/i
+ end
+ end
+
+ context 'and it is a hash with string values' do
+ let(:env) { { 'OVERCOMMIT_ENV_VAR' => '1', 'OVERCOMMIT_ENV_VAR_2' => '2' } }
+
+ it 'is valid' do
+ expect { subject }.not_to raise_error
+ end
+ end
+
+ context 'and it is a hash with integer values' do
+ let(:env) { { 'OVERCOMMIT_ENV_VAR' => 1, 'OVERCOMMIT_ENV_VAR_2' => 2 } }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError
+ output.string.should =~ /`OVERCOMMIT_ENV_VAR`.*must be a string/i
+ output.string.should =~ /`OVERCOMMIT_ENV_VAR_2`.*must be a string/i
+ end
+ end
+
+ context 'and it is a hash with boolean values' do
+ let(:env) { { 'OVERCOMMIT_ENV_VAR' => true, 'OVERCOMMIT_ENV_VAR_2' => false } }
+
+ it 'raises an error' do
+ expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError
+ output.string.should =~ /`OVERCOMMIT_ENV_VAR`.*must be a string/i
+ output.string.should =~ /`OVERCOMMIT_ENV_VAR_2`.*must be a string/i
+ end
+ end
+ end
+
context 'when hook has `processors` set' do
let(:concurrency) { 4 }
diff --git a/spec/overcommit/default_configuration_spec.rb b/spec/overcommit/default_configuration_spec.rb
index 271d37d6..706af872 100644
--- a/spec/overcommit/default_configuration_spec.rb
+++ b/spec/overcommit/default_configuration_spec.rb
@@ -1,8 +1,14 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe 'default configuration' do
default_config =
- YAML.load_file(Overcommit::ConfigurationLoader::DEFAULT_CONFIG_PATH).to_hash
+ begin
+ YAML.load_file(Overcommit::ConfigurationLoader::DEFAULT_CONFIG_PATH, aliases: true).to_hash
+ rescue ArgumentError
+ YAML.load_file(Overcommit::ConfigurationLoader::DEFAULT_CONFIG_PATH).to_hash
+ end
Overcommit::Utils.supported_hook_types.each do |hook_type|
hook_class = Overcommit::Utils.camel_case(hook_type)
diff --git a/spec/overcommit/git_config_spec.rb b/spec/overcommit/git_config_spec.rb
index 0f8a33e2..9cc51862 100644
--- a/spec/overcommit/git_config_spec.rb
+++ b/spec/overcommit/git_config_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::GitConfig do
@@ -22,4 +24,73 @@
end
end
end
+
+ describe '.hooks_path' do
+ subject { described_class.hooks_path }
+
+ context 'when not explicitly set' do
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ it 'returns the default hook path' do
+ expect(subject).to eq File.expand_path(File.join('.git', 'hooks'))
+ end
+ end
+
+ context 'when explicitly set to an empty string' do
+ around do |example|
+ repo do
+ `git config --local core.hooksPath ""`
+ example.run
+ end
+ end
+
+ it 'returns the default hook path' do
+ expect(subject).to eq File.expand_path(File.join('.git', 'hooks'))
+ end
+ end
+
+ context 'when explicitly set to an absolute path' do
+ around do |example|
+ repo do
+ `git config --local core.hooksPath /etc/hooks`
+ example.run
+ end
+ end
+
+ it 'returns the absolute path' do
+ expect(subject).to eq File.absolute_path('/etc/hooks')
+ end
+ end
+
+ context 'when explicitly set to a relative path' do
+ around do |example|
+ repo do
+ `git config --local core.hooksPath my-hooks`
+ example.run
+ end
+ end
+
+ it 'returns the absolute path to the directory relative to the repo root' do
+ expect(subject).to eq File.expand_path('my-hooks')
+ end
+ end
+
+ context 'when explicitly set to a path starting with a tilde' do
+ around do |example|
+ repo do
+ `git config --local core.hooksPath ~/my-hooks`
+ example.run
+ end
+ end
+
+ it 'returns the absolute path to the folder in the users home path' do
+ expect(subject).to eq File.expand_path('~/my-hooks')
+ expect(subject).not_to include('~')
+ end
+ end
+ end
end
diff --git a/spec/overcommit/git_repo_spec.rb b/spec/overcommit/git_repo_spec.rb
index 10bfbf88..b887415b 100644
--- a/spec/overcommit/git_repo_spec.rb
+++ b/spec/overcommit/git_repo_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::GitRepo do
@@ -22,12 +24,13 @@
end
submodule = repo do
- `git submodule add #{nested_submodule} nested-sub 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add \
+ #{nested_submodule} nested-sub 2>&1 > #{File::NULL}`
`git commit -m "Add nested submodule"`
end
repo do
- `git submodule add #{submodule} sub 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add #{submodule} sub 2>&1 > #{File::NULL}`
example.run
end
end
@@ -148,7 +151,7 @@
end
before do
- `git submodule add #{submodule} sub 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add #{submodule} sub 2>&1 > #{File::NULL}`
end
it { should_not include File.expand_path('sub') }
@@ -176,7 +179,8 @@
`git commit --allow-empty -m "Submodule commit"`
end
- `git submodule add #{submodule} #{submodule_dir} 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add \
+ #{submodule} #{submodule_dir} 2>&1 > #{File::NULL}`
`git commit -m "Add submodule"`
end
@@ -215,6 +219,20 @@
end
end
end
+
+ context 'when the git ls-tree command fails for whatever reason' do
+ before do
+ result = double('result', success?: false, statuses: [1], stdouts: '', stderrs: '')
+ allow(Overcommit::Utils).
+ to receive(:execute).
+ with(%w[git ls-tree --name-only HEAD], args: []).
+ and_return(result)
+ end
+
+ it 'raises' do
+ expect { subject }.to raise_error Overcommit::Exceptions::Error
+ end
+ end
end
describe '.tracked?' do
@@ -266,7 +284,7 @@
touch 'tracked'
`git add tracked`
`git commit -m "Initial commit"`
- `git submodule add #{submodule} sub 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add #{submodule} sub 2>&1 > #{File::NULL}`
touch 'staged'
`git add staged`
example.run
@@ -311,7 +329,7 @@
end
repo do
- `git submodule add #{submodule} sub-repo 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add #{submodule} sub-repo 2>&1 > #{File::NULL}`
`git commit -m "Initial commit"`
example.run
end
@@ -327,7 +345,8 @@
`git commit --allow-empty -m "Another submodule"`
end
- `git submodule add #{another_submodule} another-sub-repo 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add \
+ #{another_submodule} another-sub-repo 2>&1 > #{File::NULL}`
end
it { should be_empty }
@@ -353,7 +372,8 @@
`git commit --allow-empty -m "Another submodule"`
end
- `git submodule add #{another_submodule} yet-another-sub-repo 2>&1 > #{File::NULL}`
+ `git -c protocol.file.allow=always submodule add \
+ #{another_submodule} yet-another-sub-repo 2>&1 > #{File::NULL}`
`git commit -m "Add yet another submodule"`
`git rm sub-repo`
`git rm yet-another-sub-repo`
@@ -361,7 +381,7 @@
it 'returns all submodules that were removed' do
subject.size.should == 2
- subject.map(&:path).sort.should == ['sub-repo', 'yet-another-sub-repo']
+ subject.map(&:path).sort.should == %w[sub-repo yet-another-sub-repo]
end
end
end
diff --git a/spec/overcommit/hook/base_spec.rb b/spec/overcommit/hook/base_spec.rb
index c3caf2a2..0c8c7af7 100644
--- a/spec/overcommit/hook/base_spec.rb
+++ b/spec/overcommit/hook/base_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::Base do
@@ -32,4 +34,157 @@
end
end
end
+
+ describe '#run?' do
+ let(:modified_files) { [] }
+ let(:hook_config) do
+ {
+ 'enabled' => enabled,
+ 'requires_files' => requires_files,
+ }
+ end
+
+ before do
+ config.stub(:for_hook).and_return(hook_config)
+ context.stub(:modified_files).and_return(modified_files)
+ end
+
+ subject { hook.run? }
+
+ context 'enabled is true, requires_files is false, modified_files empty' do
+ let(:enabled) { true }
+ let(:requires_files) { false }
+
+ it { subject.should == true }
+ end
+
+ context 'enabled is false, requires_files is false, modified_files empty' do
+ let(:enabled) { false }
+ let(:requires_files) { false }
+
+ it { subject.should == false }
+ end
+
+ context 'enabled is true, requires_files is true, modified_files is not empty' do
+ let(:enabled) { true }
+ let(:requires_files) { true }
+ let(:modified_files) { ['file1'] }
+
+ it { subject.should == true }
+ end
+
+ context 'enabled is true, requires_files is false, modified_files is not empty' do
+ let(:enabled) { true }
+ let(:requires_files) { false }
+ let(:modified_files) { ['file1'] }
+
+ it { subject.should == true }
+ end
+
+ context 'with exclude_branches specified' do
+ let(:current_branch) { 'test-branch' }
+ let(:hook_config) do
+ {
+ 'enabled' => true,
+ 'requires_files' => false,
+ 'exclude_branches' => exclude_branches
+ }
+ end
+
+ before do
+ allow(Overcommit::GitRepo).
+ to receive(:current_branch).
+ and_return(current_branch)
+ end
+
+ context 'exclude_branches is nil' do
+ let(:exclude_branches) { nil }
+
+ it { subject.should == true }
+ end
+
+ context 'exact match between exclude_branches and current_branch' do
+ let(:exclude_branches) { ['test-branch'] }
+
+ it { subject.should == false }
+ end
+
+ context 'partial match between exclude_branches and current_branch' do
+ let(:exclude_branches) { ['test-*'] }
+
+ it { subject.should == false }
+ end
+
+ context 'non-match between exclude_branches and current_branch' do
+ let(:exclude_branches) { ['no-test-*'] }
+
+ it { subject.should == true }
+ end
+ end
+ end
+
+ context '#skip?' do
+ before do
+ config.stub(:for_hook).and_return(hook_config)
+ end
+
+ subject { hook.skip? }
+
+ context 'with skip_if not specified' do
+ let(:hook_config) do
+ { 'skip' => skip }
+ end
+
+ context 'with skip true' do
+ let(:skip) { true }
+
+ it { subject.should == true }
+ end
+
+ context 'with skip false' do
+ let(:skip) { false }
+
+ it { subject.should == false }
+ end
+ end
+
+ context 'with skip_if specified' do
+ before do
+ result = Overcommit::Subprocess::Result.new(success ? 0 : 1, '', '')
+ allow(Overcommit::Utils).to receive(:execute).and_return(result)
+ end
+
+ let(:hook_config) do
+ { 'skip' => skip, 'skip_if' => ['bash', '-c', '! which my-executable'] }
+ end
+
+ context 'with skip true and skip_if returning true' do
+ let(:skip) { true }
+ let(:success) { true }
+
+ it { subject.should == true }
+ end
+
+ context 'with skip true and skip_if returning false' do
+ let(:skip) { true }
+ let(:success) { false }
+
+ it { subject.should == true }
+ end
+
+ context 'with skip false and skip_if returning true' do
+ let(:skip) { false }
+ let(:success) { true }
+
+ it { subject.should == true }
+ end
+
+ context 'with skip false and skip_if returning false' do
+ let(:skip) { false }
+ let(:success) { false }
+
+ it { subject.should == false }
+ end
+ end
+ end
end
diff --git a/spec/overcommit/hook/commit_msg/capitalized_subject_spec.rb b/spec/overcommit/hook/commit_msg/capitalized_subject_spec.rb
index 09910ed7..77adaf69 100644
--- a/spec/overcommit/hook/commit_msg/capitalized_subject_spec.rb
+++ b/spec/overcommit/hook/commit_msg/capitalized_subject_spec.rb
@@ -1,4 +1,5 @@
-# encoding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::CapitalizedSubject do
@@ -79,4 +80,35 @@
it { should warn }
end
+
+ context 'when subject starts with special "fixup!" prefix' do
+ let(:commit_msg) { <<-MSG }
+fixup! commit
+
+This was created by running git commit --fixup=...
+ MSG
+
+ it { should pass }
+ end
+
+ context 'when subject starts with special "squash!" prefix' do
+ let(:commit_msg) { <<-MSG }
+squash! commit
+
+This was created by running git commit --squash=...
+ MSG
+
+ it { should pass }
+ end
+
+ context 'when first line of commit message is an empty line' do
+ let(:commit_msg) { <<-MSG }
+
+There was no first line
+
+This is a mistake.
+ MSG
+
+ it { should pass }
+ end
end
diff --git a/spec/overcommit/hook/commit_msg/empty_message_spec.rb b/spec/overcommit/hook/commit_msg/empty_message_spec.rb
index 5ccfcb23..94eeb468 100644
--- a/spec/overcommit/hook/commit_msg/empty_message_spec.rb
+++ b/spec/overcommit/hook/commit_msg/empty_message_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::EmptyMessage do
diff --git a/spec/overcommit/hook/commit_msg/gerrit_change_id_spec.rb b/spec/overcommit/hook/commit_msg/gerrit_change_id_spec.rb
index 49d13c07..e9b0577e 100644
--- a/spec/overcommit/hook/commit_msg/gerrit_change_id_spec.rb
+++ b/spec/overcommit/hook/commit_msg/gerrit_change_id_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::GerritChangeId do
diff --git a/spec/overcommit/hook/commit_msg/hard_tabs_spec.rb b/spec/overcommit/hook/commit_msg/hard_tabs_spec.rb
index 68900dd1..2898ff0a 100644
--- a/spec/overcommit/hook/commit_msg/hard_tabs_spec.rb
+++ b/spec/overcommit/hook/commit_msg/hard_tabs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::HardTabs do
diff --git a/spec/overcommit/hook/commit_msg/message_format_spec.rb b/spec/overcommit/hook/commit_msg/message_format_spec.rb
new file mode 100644
index 00000000..c9dde29b
--- /dev/null
+++ b/spec/overcommit/hook/commit_msg/message_format_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::CommitMsg::MessageFormat do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ context.stub(:commit_message_lines).and_return(commit_msg.lines.to_a)
+ context.stub(:empty_message?).and_return(commit_msg.empty?)
+ end
+
+ context 'when pattern is empty' do
+ let(:config) do
+ super().merge(Overcommit::Configuration.new(
+ 'CommitMsg' => {
+ 'MessageFormat' => {
+ 'pattern' => nil
+ }
+ }
+ ))
+ end
+
+ let(:commit_msg) { 'Some Message' }
+
+ it { should pass }
+ end
+
+ context 'when message does not match the pattern' do
+ let(:commit_msg) { 'Some Message' }
+
+ expected_message = [
+ 'Commit message pattern mismatch.',
+ 'Expected : | | ',
+ 'Sample : DEFECT-1234 | Refactored Onboarding flow | John Doe'
+ ].join("\n")
+
+ it { should fail_hook expected_message }
+ end
+
+ context 'when multiline message matches the pattern' do
+ let(:config) do
+ super().merge(Overcommit::Configuration.new(
+ 'CommitMsg' => {
+ 'MessageFormat' => {
+ 'pattern' => '^Some .* Message$'
+ }
+ }
+ ))
+ end
+
+ let(:commit_msg) { "Some \n multiline \n Message" }
+
+ it { should pass }
+ end
+end
diff --git a/spec/overcommit/hook/commit_msg/russian_novel_spec.rb b/spec/overcommit/hook/commit_msg/russian_novel_spec.rb
index 599a34a3..6f531a04 100644
--- a/spec/overcommit/hook/commit_msg/russian_novel_spec.rb
+++ b/spec/overcommit/hook/commit_msg/russian_novel_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::RussianNovel do
diff --git a/spec/overcommit/hook/commit_msg/single_line_subject_spec.rb b/spec/overcommit/hook/commit_msg/single_line_subject_spec.rb
index 2d4c5ef5..96ed6e32 100644
--- a/spec/overcommit/hook/commit_msg/single_line_subject_spec.rb
+++ b/spec/overcommit/hook/commit_msg/single_line_subject_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::SingleLineSubject do
diff --git a/spec/overcommit/hook/commit_msg/spell_check_spec.rb b/spec/overcommit/hook/commit_msg/spell_check_spec.rb
index 409680ea..6eae073f 100644
--- a/spec/overcommit/hook/commit_msg/spell_check_spec.rb
+++ b/spec/overcommit/hook/commit_msg/spell_check_spec.rb
@@ -1,4 +1,5 @@
-# encoding: utf-8
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::SpellCheck do
@@ -20,12 +21,12 @@
context 'with no misspellings' do
before do
- result.stub(:stdout).and_return(<<-EOS)
+ result.stub(:stdout).and_return(<<-MSG)
@(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3)
*
*
*
- EOS
+ MSG
end
it { should pass }
@@ -34,12 +35,12 @@
context 'with misspellings' do
context 'with suggestions' do
before do
- result.stub(:stdout).and_return(<<-EOS)
+ result.stub(:stdout).and_return(<<-MSG)
@(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3)
*
& msg 10 4: MSG, mag, ms, mg, meg, mtg, mug, mpg, mfg, ms g
*
- EOS
+ MSG
end
it { should warn(/^Potential misspelling: \w+. Suggestions: .+$/) }
@@ -47,12 +48,12 @@
context 'with no suggestions' do
before do
- result.stub(:stdout).and_return(<<-EOS)
+ result.stub(:stdout).and_return(<<-MSG)
@(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3)
*
# supercalifragilisticexpialidocious 4
*
- EOS
+ MSG
end
it { should warn(/^Potential misspelling: \w+.$/) }
@@ -64,9 +65,9 @@
let(:result) { double('result') }
before do
- result.stub(success?: false, stderr: <<-EOS)
+ result.stub(success?: false, stderr: <<-MSG)
Can't open affix or dictionary files for dictionary named "foo".
- EOS
+ MSG
end
it { should fail_hook }
diff --git a/spec/overcommit/hook/commit_msg/text_width_spec.rb b/spec/overcommit/hook/commit_msg/text_width_spec.rb
index 476f9920..8829668e 100644
--- a/spec/overcommit/hook/commit_msg/text_width_spec.rb
+++ b/spec/overcommit/hook/commit_msg/text_width_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::TextWidth do
@@ -28,6 +30,18 @@
it { should pass }
end
+ context 'when subject starts with special "fixup!" and is longer than 60 characters' do
+ let(:commit_msg) { 'fixup! ' + 'A' * 60 }
+
+ it { should pass }
+ end
+
+ context 'when subject starts with special "squash!" and is longer than 60 characters' do
+ let(:commit_msg) { 'squash! ' + 'A' * 60 }
+
+ it { should pass }
+ end
+
context 'when the subject is 60 characters followed by a newline' do
let(:commit_msg) { <<-MSG }
This is 60 characters, or 61 if the newline is counted
@@ -78,25 +92,32 @@
A message line that is way too long. A message line that is way too long.
MSG
- it { should warn /keep.*subject <= 60.*\n.*line 3.*> 72.*/im }
+ it { should warn /subject.*<= 60.*\n.*line 3.*> 72.*/im }
end
context 'when custom lengths are specified' do
let(:config) do
super().merge(Overcommit::Configuration.new(
- 'CommitMsg' => {
- 'TextWidth' => {
- 'max_subject_width' => 70,
- 'max_body_width' => 80
- }
- }
+ 'CommitMsg' => {
+ 'TextWidth' => {
+ 'max_subject_width' => 70,
+ 'min_subject_width' => 4,
+ 'max_body_width' => 80
+ }
+ }
))
end
context 'when subject is longer than 70 characters' do
let(:commit_msg) { 'A' * 71 }
- it { should warn /subject/ }
+ it { should warn /subject must be <= 70/ }
+ end
+
+ context 'when subject is less than 4 characters' do
+ let(:commit_msg) { 'A' * 3 }
+
+ it { should warn /subject must be >= 4/ }
end
context 'when subject is 70 characters or fewer' do
@@ -134,7 +155,7 @@
This line is longer than #{'A' * 80} characters.
MSG
- it { should warn /keep.*subject <= 70.*\n.*line 3.*> 80.*/im }
+ it { should warn /subject.*<= 70.*\n.*line 3.*> 80/im }
end
end
end
diff --git a/spec/overcommit/hook/commit_msg/trailing_period_spec.rb b/spec/overcommit/hook/commit_msg/trailing_period_spec.rb
index ea5f91c6..cee57981 100644
--- a/spec/overcommit/hook/commit_msg/trailing_period_spec.rb
+++ b/spec/overcommit/hook/commit_msg/trailing_period_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::CommitMsg::TrailingPeriod do
diff --git a/spec/overcommit/hook/post_checkout/base_spec.rb b/spec/overcommit/hook/post_checkout/base_spec.rb
new file mode 100644
index 00000000..ee3a9b2d
--- /dev/null
+++ b/spec/overcommit/hook/post_checkout/base_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostCheckout::Base do
+ let(:config) { double('config') }
+ let(:context) { double('context') }
+ let(:hook) { described_class.new(config, context) }
+
+ let(:hook_config) { {} }
+
+ before do
+ config.stub(:for_hook).and_return(hook_config)
+ end
+
+ describe '#skip_file_checkout?' do
+ subject { hook.skip_file_checkout? }
+
+ context 'when skip_file_checkout is not set' do
+ it { should == true }
+ end
+
+ context 'when skip_file_checkout is set to false' do
+ let(:hook_config) { { 'skip_file_checkout' => false } }
+
+ it { should == false }
+ end
+
+ context 'when skip_file_checkout is set to true' do
+ let(:hook_config) { { 'skip_file_checkout' => true } }
+
+ it { should == true }
+ end
+ end
+
+ describe '#enabled?' do
+ subject { hook.enabled? }
+
+ shared_examples 'hook enabled' do |enabled, skip_file_checkout, file_checkout, expected|
+ context "when enabled is set to #{enabled}" do
+ context "when skip_file_checkout is set to #{skip_file_checkout}" do
+ context "when file_checkout? is #{file_checkout}" do
+ let(:hook_config) do
+ { 'enabled' => enabled, 'skip_file_checkout' => skip_file_checkout }
+ end
+
+ before do
+ context.stub(:file_checkout?).and_return(file_checkout)
+ end
+
+ it { should == expected }
+ end
+ end
+ end
+ end
+
+ include_examples 'hook enabled', true, true, true, false
+ include_examples 'hook enabled', true, true, false, true
+ include_examples 'hook enabled', true, false, true, true
+ include_examples 'hook enabled', true, false, false, true
+ include_examples 'hook enabled', false, true, true, false
+ include_examples 'hook enabled', false, true, false, false
+ include_examples 'hook enabled', false, false, true, false
+ include_examples 'hook enabled', false, false, false, false
+ end
+end
diff --git a/spec/overcommit/hook/post_checkout/bower_install_spec.rb b/spec/overcommit/hook/post_checkout/bower_install_spec.rb
index 6720a244..1dd0bc2e 100644
--- a/spec/overcommit/hook/post_checkout/bower_install_spec.rb
+++ b/spec/overcommit/hook/post_checkout/bower_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCheckout::BowerInstall do
diff --git a/spec/overcommit/hook/post_checkout/bundle_install_spec.rb b/spec/overcommit/hook/post_checkout/bundle_install_spec.rb
index 3c9f46b5..0a375d54 100644
--- a/spec/overcommit/hook/post_checkout/bundle_install_spec.rb
+++ b/spec/overcommit/hook/post_checkout/bundle_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCheckout::BundleInstall do
diff --git a/spec/overcommit/hook/post_checkout/composer_install_spec.rb b/spec/overcommit/hook/post_checkout/composer_install_spec.rb
new file mode 100644
index 00000000..1b43b54b
--- /dev/null
+++ b/spec/overcommit/hook/post_checkout/composer_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostCheckout::ComposerInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when composer install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when composer install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stdout: 'Composer could not find a composer.json file')
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_checkout/index_tags_spec.rb b/spec/overcommit/hook/post_checkout/index_tags_spec.rb
index 1476eb6c..93d79db0 100644
--- a/spec/overcommit/hook/post_checkout/index_tags_spec.rb
+++ b/spec/overcommit/hook/post_checkout/index_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCheckout::IndexTags do
diff --git a/spec/overcommit/hook/post_checkout/npm_install_spec.rb b/spec/overcommit/hook/post_checkout/npm_install_spec.rb
index 41842735..00d324ce 100644
--- a/spec/overcommit/hook/post_checkout/npm_install_spec.rb
+++ b/spec/overcommit/hook/post_checkout/npm_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCheckout::NpmInstall do
diff --git a/spec/overcommit/hook/post_checkout/submodule_status_spec.rb b/spec/overcommit/hook/post_checkout/submodule_status_spec.rb
index 45078f46..799256b3 100644
--- a/spec/overcommit/hook/post_checkout/submodule_status_spec.rb
+++ b/spec/overcommit/hook/post_checkout/submodule_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCheckout::SubmoduleStatus do
diff --git a/spec/overcommit/hook/post_checkout/yarn_install_spec.rb b/spec/overcommit/hook/post_checkout/yarn_install_spec.rb
new file mode 100644
index 00000000..95028e77
--- /dev/null
+++ b/spec/overcommit/hook/post_checkout/yarn_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostCheckout::YarnInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when yarn install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when yarn install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stderr: %{error An unexpected error occurred: ...})
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_commit/bower_install_spec.rb b/spec/overcommit/hook/post_commit/bower_install_spec.rb
index 7d5417f8..e42b4038 100644
--- a/spec/overcommit/hook/post_commit/bower_install_spec.rb
+++ b/spec/overcommit/hook/post_commit/bower_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCommit::BowerInstall do
diff --git a/spec/overcommit/hook/post_commit/bundle_install_spec.rb b/spec/overcommit/hook/post_commit/bundle_install_spec.rb
index 1445436e..7c4f85db 100644
--- a/spec/overcommit/hook/post_commit/bundle_install_spec.rb
+++ b/spec/overcommit/hook/post_commit/bundle_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCommit::BundleInstall do
diff --git a/spec/overcommit/hook/post_commit/commitplease_spec.rb b/spec/overcommit/hook/post_commit/commitplease_spec.rb
new file mode 100644
index 00000000..e6b38bc8
--- /dev/null
+++ b/spec/overcommit/hook/post_commit/commitplease_spec.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostCommit::Commitplease do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when commitplease exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ result.stub(:stderr).and_return('')
+ end
+
+ it { should pass }
+ end
+
+ context 'when commitplease exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stderr: normalize_indent(<<-OUT))
+ - First line must be ():
+ Need an opening parenthesis: (
+ OUT
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_commit/composer_install_spec.rb b/spec/overcommit/hook/post_commit/composer_install_spec.rb
new file mode 100644
index 00000000..13abe5e0
--- /dev/null
+++ b/spec/overcommit/hook/post_commit/composer_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostCommit::ComposerInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when composer install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when composer install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stdout: 'Composer could not find a composer.json file')
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_commit/git_guilt_spec.rb b/spec/overcommit/hook/post_commit/git_guilt_spec.rb
index 28c8c9f6..d099b507 100644
--- a/spec/overcommit/hook/post_commit/git_guilt_spec.rb
+++ b/spec/overcommit/hook/post_commit/git_guilt_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCommit::GitGuilt do
diff --git a/spec/overcommit/hook/post_commit/index_tags_spec.rb b/spec/overcommit/hook/post_commit/index_tags_spec.rb
index d5ebcb65..eb3a5e10 100644
--- a/spec/overcommit/hook/post_commit/index_tags_spec.rb
+++ b/spec/overcommit/hook/post_commit/index_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCommit::IndexTags do
diff --git a/spec/overcommit/hook/post_commit/npm_install_spec.rb b/spec/overcommit/hook/post_commit/npm_install_spec.rb
index 81bbd325..d3405cd4 100644
--- a/spec/overcommit/hook/post_commit/npm_install_spec.rb
+++ b/spec/overcommit/hook/post_commit/npm_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCommit::NpmInstall do
diff --git a/spec/overcommit/hook/post_commit/submodule_status_spec.rb b/spec/overcommit/hook/post_commit/submodule_status_spec.rb
index 2c12d5fe..6d46a4e1 100644
--- a/spec/overcommit/hook/post_commit/submodule_status_spec.rb
+++ b/spec/overcommit/hook/post_commit/submodule_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostCommit::SubmoduleStatus do
diff --git a/spec/overcommit/hook/post_commit/yarn_install_spec.rb b/spec/overcommit/hook/post_commit/yarn_install_spec.rb
new file mode 100644
index 00000000..dfc77908
--- /dev/null
+++ b/spec/overcommit/hook/post_commit/yarn_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostCommit::YarnInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when yarn install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when yarn install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stderr: %{error An unexpected error occurred: ...})
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_merge/bower_install_spec.rb b/spec/overcommit/hook/post_merge/bower_install_spec.rb
index 63f5cbaf..163b5d12 100644
--- a/spec/overcommit/hook/post_merge/bower_install_spec.rb
+++ b/spec/overcommit/hook/post_merge/bower_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostMerge::BowerInstall do
diff --git a/spec/overcommit/hook/post_merge/bundle_install_spec.rb b/spec/overcommit/hook/post_merge/bundle_install_spec.rb
index ed463dac..1c2ad3b2 100644
--- a/spec/overcommit/hook/post_merge/bundle_install_spec.rb
+++ b/spec/overcommit/hook/post_merge/bundle_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostMerge::BundleInstall do
diff --git a/spec/overcommit/hook/post_merge/composer_install_spec.rb b/spec/overcommit/hook/post_merge/composer_install_spec.rb
new file mode 100644
index 00000000..94d00083
--- /dev/null
+++ b/spec/overcommit/hook/post_merge/composer_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostMerge::ComposerInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when composer install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when composer install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stdout: 'Composer could not find a composer.json file')
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_merge/index_tags_spec.rb b/spec/overcommit/hook/post_merge/index_tags_spec.rb
index e897ddf4..2ac6fad1 100644
--- a/spec/overcommit/hook/post_merge/index_tags_spec.rb
+++ b/spec/overcommit/hook/post_merge/index_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostMerge::IndexTags do
diff --git a/spec/overcommit/hook/post_merge/npm_install_spec.rb b/spec/overcommit/hook/post_merge/npm_install_spec.rb
index 9f63d717..017d7a36 100644
--- a/spec/overcommit/hook/post_merge/npm_install_spec.rb
+++ b/spec/overcommit/hook/post_merge/npm_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostMerge::NpmInstall do
diff --git a/spec/overcommit/hook/post_merge/submodule_status_spec.rb b/spec/overcommit/hook/post_merge/submodule_status_spec.rb
index 245fe6c1..c0dd2efc 100644
--- a/spec/overcommit/hook/post_merge/submodule_status_spec.rb
+++ b/spec/overcommit/hook/post_merge/submodule_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostMerge::SubmoduleStatus do
diff --git a/spec/overcommit/hook/post_merge/yarn_install_spec.rb b/spec/overcommit/hook/post_merge/yarn_install_spec.rb
new file mode 100644
index 00000000..b9648712
--- /dev/null
+++ b/spec/overcommit/hook/post_merge/yarn_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostMerge::YarnInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when yarn install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when yarn install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stderr: %{error An unexpected error occurred: ...})
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_rewrite/bower_install_spec.rb b/spec/overcommit/hook/post_rewrite/bower_install_spec.rb
index a3a1a1b1..fe2ebd35 100644
--- a/spec/overcommit/hook/post_rewrite/bower_install_spec.rb
+++ b/spec/overcommit/hook/post_rewrite/bower_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostRewrite::BowerInstall do
diff --git a/spec/overcommit/hook/post_rewrite/bundle_install_spec.rb b/spec/overcommit/hook/post_rewrite/bundle_install_spec.rb
index b3703ccb..93d05773 100644
--- a/spec/overcommit/hook/post_rewrite/bundle_install_spec.rb
+++ b/spec/overcommit/hook/post_rewrite/bundle_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostRewrite::BundleInstall do
diff --git a/spec/overcommit/hook/post_rewrite/composer_install_spec.rb b/spec/overcommit/hook/post_rewrite/composer_install_spec.rb
new file mode 100644
index 00000000..30c2091f
--- /dev/null
+++ b/spec/overcommit/hook/post_rewrite/composer_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostRewrite::ComposerInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when composer install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when composer install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stdout: 'Composer could not find a composer.json file')
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/post_rewrite/index_tags_spec.rb b/spec/overcommit/hook/post_rewrite/index_tags_spec.rb
index e897ddf4..2ac6fad1 100644
--- a/spec/overcommit/hook/post_rewrite/index_tags_spec.rb
+++ b/spec/overcommit/hook/post_rewrite/index_tags_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostMerge::IndexTags do
diff --git a/spec/overcommit/hook/post_rewrite/npm_install_spec.rb b/spec/overcommit/hook/post_rewrite/npm_install_spec.rb
index 2af2291f..24b72a5a 100644
--- a/spec/overcommit/hook/post_rewrite/npm_install_spec.rb
+++ b/spec/overcommit/hook/post_rewrite/npm_install_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostRewrite::NpmInstall do
diff --git a/spec/overcommit/hook/post_rewrite/submodule_status_spec.rb b/spec/overcommit/hook/post_rewrite/submodule_status_spec.rb
index c783c029..a5770745 100644
--- a/spec/overcommit/hook/post_rewrite/submodule_status_spec.rb
+++ b/spec/overcommit/hook/post_rewrite/submodule_status_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PostRewrite::SubmoduleStatus do
diff --git a/spec/overcommit/hook/post_rewrite/yarn_install_spec.rb b/spec/overcommit/hook/post_rewrite/yarn_install_spec.rb
new file mode 100644
index 00000000..9774d1ab
--- /dev/null
+++ b/spec/overcommit/hook/post_rewrite/yarn_install_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PostRewrite::YarnInstall do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when yarn install exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ end
+
+ it { should pass }
+ end
+
+ context 'when yarn install exits unsuccessfully' do
+ before do
+ result.stub(success?: false, stderr: %{error An unexpected error occurred: ...})
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/author_email_spec.rb b/spec/overcommit/hook/pre_commit/author_email_spec.rb
index 0ef79a72..8e0dd768 100644
--- a/spec/overcommit/hook/pre_commit/author_email_spec.rb
+++ b/spec/overcommit/hook/pre_commit/author_email_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::AuthorEmail do
@@ -6,50 +8,66 @@
subject { described_class.new(config, context) }
let(:result) { double('result') }
- before do
- result.stub(:stdout).and_return(email)
- subject.stub(:execute).and_return(result)
- end
+ shared_examples_for 'author email check' do
+ context 'when user has no email' do
+ let(:email) { '' }
- context 'when user has no email' do
- let(:email) { '' }
+ it { should fail_hook }
+ end
- it { should fail_hook }
- end
+ context 'when user has an invalid email' do
+ let(:email) { 'Invalid Email' }
- context 'when user has an invalid email' do
- let(:email) { 'Invalid Email' }
+ it { should fail_hook }
+ end
- it { should fail_hook }
- end
+ context 'when user has a valid email' do
+ let(:email) { 'email@example.com' }
+
+ it { should pass }
+ end
- context 'when user has a valid email' do
- let(:email) { 'email@example.com' }
+ context 'when a custom pattern is specified' do
+ let(:config) do
+ super().merge(Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'AuthorEmail' => {
+ 'pattern' => '^[^@]+@brigade\.com$'
+ }
+ }
+ ))
+ end
- it { should pass }
- end
+ context 'and the email does not match the pattern' do
+ let(:email) { 'email@example.com' }
- context 'when a custom pattern is specified' do
- let(:config) do
- super().merge(Overcommit::Configuration.new(
- 'PreCommit' => {
- 'AuthorEmail' => {
- 'pattern' => '^[^@]+@brigade\.com$'
- }
- }
- ))
- end
+ it { should fail_hook }
+ end
- context 'and the email does not match the pattern' do
- let(:email) { 'email@example.com' }
+ context 'and the email matches the pattern' do
+ let(:email) { 'email@brigade.com' }
- it { should fail_hook }
+ it { should pass }
+ end
end
+ end
- context 'and the email matches the pattern' do
- let(:email) { 'email@brigade.com' }
+ context 'when email is set via config' do
+ before do
+ result.stub(:stdout).and_return(email)
+ subject.stub(:execute).and_return(result)
+ end
- it { should pass }
+ it_should_behave_like 'author email check'
+ end
+
+ context 'when email is set via environment variable' do
+ around do |example|
+ Overcommit::Utils.with_environment 'GIT_AUTHOR_EMAIL' => email do
+ example.run
+ end
end
+
+ it_should_behave_like 'author email check'
end
end
diff --git a/spec/overcommit/hook/pre_commit/author_name_spec.rb b/spec/overcommit/hook/pre_commit/author_name_spec.rb
index 78dfd72d..1ed0502f 100644
--- a/spec/overcommit/hook/pre_commit/author_name_spec.rb
+++ b/spec/overcommit/hook/pre_commit/author_name_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::AuthorName do
@@ -6,26 +8,42 @@
subject { described_class.new(config, context) }
let(:result) { double('result') }
- before do
- result.stub(:stdout).and_return(name)
- subject.stub(:execute).and_return(result)
- end
+ shared_examples_for 'author name check' do
+ context 'when user has no name' do
+ let(:name) { '' }
+
+ it { should fail_hook }
+ end
+
+ context 'when user has only a first name' do
+ let(:name) { 'John' }
+
+ it { should pass }
+ end
- context 'when user has no name' do
- let(:name) { '' }
+ context 'when user has first and last name' do
+ let(:name) { 'John Doe' }
- it { should fail_hook }
+ it { should pass }
+ end
end
- context 'when user has only a first name' do
- let(:name) { 'John' }
+ context 'when name is set via config' do
+ before do
+ result.stub(:stdout).and_return(name)
+ subject.stub(:execute).and_return(result)
+ end
- it { should fail_hook }
+ it_should_behave_like 'author name check'
end
- context 'when user has first and last name' do
- let(:name) { 'John Doe' }
+ context 'when name is set via environment variable' do
+ around do |example|
+ Overcommit::Utils.with_environment 'GIT_AUTHOR_NAME' => name do
+ example.run
+ end
+ end
- it { should pass }
+ it_should_behave_like 'author name check'
end
end
diff --git a/spec/overcommit/hook/pre_commit/berksfile_check_spec.rb b/spec/overcommit/hook/pre_commit/berksfile_check_spec.rb
index a6c24355..58d82517 100644
--- a/spec/overcommit/hook/pre_commit/berksfile_check_spec.rb
+++ b/spec/overcommit/hook/pre_commit/berksfile_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::BerksfileCheck do
diff --git a/spec/overcommit/hook/pre_commit/broken_symlinks_spec.rb b/spec/overcommit/hook/pre_commit/broken_symlinks_spec.rb
index b3058aae..84ce890e 100644
--- a/spec/overcommit/hook/pre_commit/broken_symlinks_spec.rb
+++ b/spec/overcommit/hook/pre_commit/broken_symlinks_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::BrokenSymlinks do
diff --git a/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb b/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb
new file mode 100644
index 00000000..b8360e2e
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::BundleAudit do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when Gemfile.lock is ignored' do
+ around do |example|
+ repo do
+ touch 'Gemfile.lock'
+ echo('Gemfile.lock', '.gitignore')
+ `git add .gitignore`
+ `git commit -m "Ignore Gemfile.lock"`
+ example.run
+ end
+ end
+
+ it { should pass }
+ end
+
+ context 'when Gemfile.lock is not ignored' do
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard -- Gemfile.lock]).
+ and_return(double(stdout: ''))
+ subject.stub(:execute).with(%w[bundle-audit]).and_return(result)
+ end
+
+ context 'and it reports some outdated gems' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Name: rest-client
+Version: 1.6.9
+Advisory: CVE-2015-1820
+Criticality: Unknown
+URL: https://github.com/rest-client/rest-client/issues/369
+Title: rubygem-rest-client: session fixation vulnerability via Set-Cookie headers in 30x redirection responses
+Solution: upgrade to >= 1.8.0
+Name: rest-client
+Version: 1.6.9
+Advisory: CVE-2015-3448
+Criticality: Unknown
+URL: http://www.osvdb.org/show/osvdb/117461
+Title: Rest-Client Gem for Ruby logs password information in plaintext
+Solution: upgrade to >= 1.7.3
+Vulnerabilities found!
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Insecure Source URI found: git://github.com/xxx/overcommit.git
+Vulnerabilities found!
+ MSG
+ )
+ end
+
+ it { should warn }
+
+ context 'and it reports bundle up to date' do
+ let(:result) do
+ double(success?: true, stdout: 'No vulnerabilities found')
+ end
+
+ it { should pass }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/bundle_check_spec.rb b/spec/overcommit/hook/pre_commit/bundle_check_spec.rb
index bc11a065..b8a471a1 100644
--- a/spec/overcommit/hook/pre_commit/bundle_check_spec.rb
+++ b/spec/overcommit/hook/pre_commit/bundle_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::BundleCheck do
diff --git a/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb b/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb
new file mode 100644
index 00000000..a734b3dc
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::BundleOutdated do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when Gemfile.lock is ignored' do
+ around do |example|
+ repo do
+ touch 'Gemfile.lock'
+ echo('Gemfile.lock', '.gitignore')
+ `git add .gitignore`
+ `git commit -m "Ignore Gemfile.lock"`
+ example.run
+ end
+ end
+
+ it { should pass }
+ end
+
+ context 'when Gemfile.lock is not ignored' do
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]).
+ and_return(double(stdout: ''))
+ subject.stub(:execute).with(%w[bundle outdated --strict --parseable]).
+ and_return(result)
+ end
+
+ context 'and it reports some outdated gems' do
+ let(:result) do
+ double(stdout: <<-MSG
+Warning: the running version of Bundler is older than the version that created the lockfile. We suggest you upgrade to the latest version of Bundler by running `gem install bundler`.
+airbrake (newest 5.3.0, installed 5.2.3, requested ~> 5.0)
+aws-sdk (newest 2.3.3, installed 2.3.1, requested ~> 2)
+font-awesome-rails (newest 4.6.2.0, installed 4.6.1.0)
+mechanize (newest 2.7.4, installed 2.1.1)
+minimum-omniauth-scaffold (newest 0.4.3, installed 0.4.1)
+airbrake-ruby (newest 1.3.0, installed 1.2.4)
+aws-sdk-core (newest 2.3.3, installed 2.3.1)
+aws-sdk-resources (newest 2.3.3, installed 2.3.1)
+config (newest 1.1.1, installed 1.1.0)
+ruby_parser (newest 3.8.2, installed 3.8.1)
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ context 'and it reports bundle up to date' do
+ let(:result) do
+ double(stdout: <<-MSG
+Warning: the running version of Bundler is older than the version that created the lockfile. We suggest you upgrade to the latest version of Bundler by running `gem install bundler`.
+ MSG
+ )
+ end
+
+ it { should pass }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/case_conflicts_spec.rb b/spec/overcommit/hook/pre_commit/case_conflicts_spec.rb
index 0d7281d0..1ce157d5 100644
--- a/spec/overcommit/hook/pre_commit/case_conflicts_spec.rb
+++ b/spec/overcommit/hook/pre_commit/case_conflicts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::CaseConflicts do
diff --git a/spec/overcommit/hook/pre_commit/chamber_compare_spec.rb b/spec/overcommit/hook/pre_commit/chamber_compare_spec.rb
new file mode 100644
index 00000000..de9fffa2
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/chamber_compare_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::ChamberCompare do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(['my_settings.yml'])
+ end
+
+ context 'when chamber exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return('')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when chamber exits unsucessfully' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return('Some error message')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should warn }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/chamber_security_spec.rb b/spec/overcommit/hook/pre_commit/chamber_security_spec.rb
index ca4c9290..7badd189 100644
--- a/spec/overcommit/hook/pre_commit/chamber_security_spec.rb
+++ b/spec/overcommit/hook/pre_commit/chamber_security_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::ChamberSecurity do
diff --git a/spec/overcommit/hook/pre_commit/chamber_verification_spec.rb b/spec/overcommit/hook/pre_commit/chamber_verification_spec.rb
new file mode 100644
index 00000000..a49e2a98
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/chamber_verification_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::ChamberVerification do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(['my_settings.yml'])
+ end
+
+ context 'when chamber exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return('')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when chamber exits unsuccessfully but because of missing keys' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return('no signature key was found')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when chamber exits unsuccessfully via standard out' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return('Some error message')
+ result.stub(:stderr).and_return('')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should warn }
+ end
+
+ context 'when chamber exits unsuccessfully via standard error' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return('Some error message')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should warn }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/code_spell_check_spec.rb b/spec/overcommit/hook/pre_commit/code_spell_check_spec.rb
new file mode 100644
index 00000000..85a54b38
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/code_spell_check_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::CodeSpellCheck do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
+ context 'when code-spell-check exists successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return('')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when code-spell-check exists unsuccessfully via standard error' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(false)
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return(
+ "file1.rb:35: inkorrectspelling\n✗ Errors in code spellchecking"
+ )
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/coffee_lint_spec.rb b/spec/overcommit/hook/pre_commit/coffee_lint_spec.rb
index 44e00aca..5ece05f9 100644
--- a/spec/overcommit/hook/pre_commit/coffee_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/coffee_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::CoffeeLint do
diff --git a/spec/overcommit/hook/pre_commit/cook_style_spec.rb b/spec/overcommit/hook/pre_commit/cook_style_spec.rb
new file mode 100644
index 00000000..a7dece87
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/cook_style_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::CookStyle do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
+ context 'when cookstyle exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(success?: true, stderr: '', stdout: '')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+
+ context 'and it printed warnings to stderr' do
+ before do
+ result.stub(:stderr).and_return(normalize_indent(<<-MSG))
+ warning: parser/current is loading parser/ruby21, which recognizes
+ warning: 2.1.8-compliant syntax, but you are running 2.1.1.
+ warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
+ MSG
+ end
+
+ it { should pass }
+ end
+ end
+
+ context 'when cookstyle exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports a warning' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.rb:1:1: W: Useless assignment to variable - my_var.',
+ ].join("\n"))
+ result.stub(:stderr).and_return('')
+ end
+
+ it { should warn }
+
+ context 'and it printed warnings to stderr' do
+ before do
+ result.stub(:stderr).and_return(normalize_indent(<<-MSG))
+ warning: parser/current is loading parser/ruby21, which recognizes
+ warning: 2.1.8-compliant syntax, but you are running 2.1.1.
+ warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
+ MSG
+ end
+
+ it { should warn }
+ end
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.rb:1:1: C: Missing top-level class documentation',
+ ].join("\n"))
+ result.stub(:stderr).and_return('')
+ end
+
+ it { should fail_hook }
+
+ context 'and it printed warnings to stderr' do
+ before do
+ result.stub(:stderr).and_return(normalize_indent(<<-MSG))
+ warning: parser/current is loading parser/ruby21, which recognizes
+ warning: 2.1.8-compliant syntax, but you are running 2.1.1.
+ warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri.
+ MSG
+ end
+
+ it { should fail_hook }
+ end
+ end
+
+ context 'when a generic error message is written to stderr' do
+ before do
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return([
+ 'Could not find cookstyle in any of the sources'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/credo_spec.rb b/spec/overcommit/hook/pre_commit/credo_spec.rb
new file mode 100644
index 00000000..0a97a0d6
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/credo_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Credo do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.ex file2.exs])
+ end
+
+ context 'when credo exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when credo exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.ex:1:11: R: Modules should have a @moduledoc tag.',
+ 'file2.ex:1:11: R: Modules should have a @moduledoc tag.'
+ ].join("\n"))
+ result.stub(:stderr).and_return('')
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/css_lint_spec.rb b/spec/overcommit/hook/pre_commit/css_lint_spec.rb
index 1f7133ed..4d2afc4d 100644
--- a/spec/overcommit/hook/pre_commit/css_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/css_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::CssLint do
diff --git a/spec/overcommit/hook/pre_commit/dart_analyzer_spec.rb b/spec/overcommit/hook/pre_commit/dart_analyzer_spec.rb
new file mode 100644
index 00000000..1013c503
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/dart_analyzer_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::DartAnalyzer do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.dart file2.dart])
+ end
+
+ context 'when dartanalyzer exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when dartanalyzer exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'Analyzing file1.dart...',
+ 'error • message_ommitted • lib/file1.dart:35:3 • rule',
+ 'Analyzing file2.dart...',
+ 'hint • message_ommitted • lib/file2.dart:100:13 • rule',
+ 'info • message_ommitted • lib/file2.dart:113:16 • rule',
+ '3 lints found.'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/dogma_spec.rb b/spec/overcommit/hook/pre_commit/dogma_spec.rb
index 0e162ad3..53b5cd62 100644
--- a/spec/overcommit/hook/pre_commit/dogma_spec.rb
+++ b/spec/overcommit/hook/pre_commit/dogma_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Dogma do
diff --git a/spec/overcommit/hook/pre_commit/erb_lint_spec.rb b/spec/overcommit/hook/pre_commit/erb_lint_spec.rb
new file mode 100644
index 00000000..e9272bfe
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/erb_lint_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::ErbLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.html.erb file2.html.erb])
+ end
+
+ context 'when erblint exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when erblint exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return(<<-MSG)
+Linting 1 files with 14 linters...
+
+erb interpolation with '<%= (...).html_safe %>' in this context is never safe
+In file: app/views/posts/show.html.erb:10
+ MSG
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/es_lint_spec.rb b/spec/overcommit/hook/pre_commit/es_lint_spec.rb
index b8119298..39de7d27 100644
--- a/spec/overcommit/hook/pre_commit/es_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/es_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::EsLint do
@@ -9,6 +11,20 @@
subject.stub(:applicable_files).and_return(%w[file1.js file2.js])
end
+ context 'when eslint is unable to run' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:stderr).and_return('SyntaxError: Use of const in strict mode.')
+ result.stub(:stdout).and_return('')
+
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should fail_hook }
+ end
+
context 'when eslint exits successfully' do
let(:result) { double('result') }
@@ -36,6 +52,18 @@
it { should warn }
end
+
+ context 'and it doesnt count false positives error messages' do
+ before do
+ result.stub(:stdout).and_return([
+ '$ yarn eslint --quiet --format=compact /app/project/Error.ts',
+ '$ /app/project/node_modules/.bin/eslint --quiet --format=compact /app/project/Error.ts',
+ '',
+ ].join("\n"))
+ end
+
+ it { should pass }
+ end
end
context 'when eslint exits unsucessfully' do
diff --git a/spec/overcommit/hook/pre_commit/execute_permissions_spec.rb b/spec/overcommit/hook/pre_commit/execute_permissions_spec.rb
index 33b0f10a..3e33b946 100644
--- a/spec/overcommit/hook/pre_commit/execute_permissions_spec.rb
+++ b/spec/overcommit/hook/pre_commit/execute_permissions_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::ExecutePermissions do
@@ -10,7 +12,7 @@ def make_executable_and_add(file, exec_bit)
if Overcommit::OS.windows?
`git update-index --add --chmod=#{exec_bit ? '+' : '-'}x #{file}`
else
- FileUtils.chmod(exec_bit ? 0755 : 0644, file)
+ FileUtils.chmod(exec_bit ? 0o755 : 0o644, file)
`git add #{file}`
end
end
diff --git a/spec/overcommit/hook/pre_commit/fasterer_spec.rb b/spec/overcommit/hook/pre_commit/fasterer_spec.rb
new file mode 100644
index 00000000..afb08791
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/fasterer_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Fasterer do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ let(:applicable_files) { %w[file1.rb file2.rb] }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(applicable_files)
+ end
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ subject.stub(:execute).with(%w[fasterer], args: applicable_files).and_return(result)
+ end
+
+ context 'and has 2 suggestions for speed improvement' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+spec/models/product_spec.rb
+Using each_with_index is slower than while loop. Occurred at lines: 52.
+1 files inspected, 1 offense detected
+spec/models/book_spec.rb
+Using each_with_index is slower than while loop. Occurred at lines: 32.
+1 files inspected, 1 offense detected
+spec/models/blog_spec.rb
+Using each_with_index is slower than while loop. Occurred at lines: 12.
+2 files inspected, 0 offense detected
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ context 'and has single suggestion for speed improvement' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+spec/models/product_spec.rb
+Using each_with_index is slower than while loop. Occurred at lines: 52.
+1 files inspected, 1 offense detected
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ context 'and does not have any suggestion' do
+ let(:result) do
+ double(success?: true, stdout: '55 files inspected, 0 offenses detected')
+ end
+
+ it { should pass }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/file_size_spec.rb b/spec/overcommit/hook/pre_commit/file_size_spec.rb
new file mode 100644
index 00000000..7fb75898
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/file_size_spec.rb
@@ -0,0 +1,57 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::FileSize do
+ let(:config) do
+ Overcommit::ConfigurationLoader.default_configuration.merge(
+ Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'FileSize' => {
+ 'size_limit_bytes' => 10
+ }
+ }
+ )
+ )
+ end
+
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+ let(:staged_file) { 'filename.txt' }
+
+ before do
+ subject.stub(:applicable_files).and_return([staged_file])
+ end
+
+ around do |example|
+ repo do
+ File.open(staged_file, 'w') { |f| f.write(contents) }
+ `git add "#{staged_file}" > #{File::NULL} 2>&1`
+ example.run
+ end
+ end
+
+ context 'when a big file is committed' do
+ let(:contents) { 'longer than 10 bytes' }
+
+ it { should fail_hook }
+ end
+
+ context 'when a small file is committed' do
+ let(:contents) { 'short' }
+
+ it { should pass }
+ end
+
+ context 'when a file is removed' do
+ let(:contents) { 'anything' }
+ before do
+ `git commit -m "Add file"`
+ `git rm "#{staged_file}"`
+ end
+
+ it 'should not raise an exception' do
+ lambda { should pass }.should_not raise_error
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/fix_me_spec.rb b/spec/overcommit/hook/pre_commit/fix_me_spec.rb
new file mode 100644
index 00000000..262c3294
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/fix_me_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::FixMe do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+ let(:staged_file) { 'filename.txt' }
+
+ before do
+ subject.stub(:applicable_files).and_return([staged_file])
+ end
+
+ around do |example|
+ repo do
+ File.open(staged_file, 'w') { |f| f.write(contents) }
+ `git add #{staged_file}`
+ example.run
+ end
+ end
+
+ context 'when file contains FIXME' do
+ let(:contents) { 'eval(params[:q]) # FIXME maybe this is a bad idea?' }
+
+ it { should warn }
+ end
+
+ context 'when file contains TODO with special chars around it' do
+ let(:contents) { 'users = (1..1000).map { |i| User.find(1) } #TODO: make it better' }
+
+ it { should warn }
+ end
+
+ context 'when file does not contain any FixMe words' do
+ let(:contents) { 'if HACKY_CONSTANT.blank?' }
+
+ it { should pass }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/flay_spec.rb b/spec/overcommit/hook/pre_commit/flay_spec.rb
new file mode 100644
index 00000000..2a5d8140
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/flay_spec.rb
@@ -0,0 +1,60 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Flay do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ let(:applicable_files) { %w[file1.rb] }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(applicable_files)
+ end
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ command = %w[flay --mass 16 --fuzzy 1]
+ subject.stub(:execute).with(command, args: applicable_files).and_return(result)
+ end
+
+ context 'flay discovered two issues' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Total score (lower is better) = 268
+
+1) IDENTICAL code found in :defn (mass*2 = 148)
+ app/whatever11.rb:105
+ app/whatever12.rb:76
+
+2) Similar code found in :defn (mass = 120)
+ app/whatever21.rb:105
+ app/whatever22.rb:76
+
+ MSG
+ )
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'flay discovered no issues' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Total score (lower is better) = 0
+ MSG
+ )
+ end
+
+ it { should pass }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/foodcritic_spec.rb b/spec/overcommit/hook/pre_commit/foodcritic_spec.rb
new file mode 100644
index 00000000..2bbe3df2
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/foodcritic_spec.rb
@@ -0,0 +1,126 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Foodcritic do
+ let(:context) { double('context') }
+ let(:result) { double(success?: true) }
+ subject { described_class.new(config, context) }
+
+ before do
+ modified_files = applicable_files.map do |file|
+ File.join(Overcommit::Utils.repo_root, file)
+ end
+ subject.stub(:applicable_files).and_return(modified_files)
+ allow(subject).to receive(:execute).and_return(result)
+ end
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ context 'when working in a single cookbook repository' do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+
+ context 'and files have changed' do
+ let(:applicable_files) do
+ [
+ 'metadata.rb',
+ File.join('recipes', 'default.rb'),
+ ]
+ end
+
+ it 'passes the repository root as the cookbook path' do
+ expect(subject).to receive(:execute).
+ with(subject.command,
+ hash_including(args: ['-B', Overcommit::Utils.repo_root]))
+ subject.run
+ end
+
+ context 'and Foodcritic returns an unsuccessful exit status' do
+ let(:result) do
+ double(
+ success?: false,
+ stderr: '',
+ stdout: <<-MSG,
+ FC023: Prefer conditional attributes: recipes/default.rb:11
+ FC065: Ensure source_url is set in metadata: metadata.rb:1
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ context 'and Foodcritic returns a successful exit status' do
+ it { should pass }
+ end
+ end
+ end
+
+ context 'when working in a repository with many cookbooks' do
+ let(:config) do
+ Overcommit::ConfigurationLoader.default_configuration.merge(
+ Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'Foodcritic' => {
+ 'cookbooks_directory' => 'cookbooks',
+ 'environments_directory' => 'environments',
+ 'roles_directory' => 'roles',
+ }
+ }
+ )
+ )
+ end
+
+ context 'and multiple cookbooks, environments, and roles have changed' do
+ let(:applicable_files) do
+ [
+ File.join('cookbooks', 'cookbook_a', 'metadata.rb'),
+ File.join('cookbooks', 'cookbook_b', 'metadata.rb'),
+ File.join('environments', 'production.json'),
+ File.join('environments', 'staging.json'),
+ File.join('roles', 'role_a.json'),
+ File.join('roles', 'role_b.json'),
+ ]
+ end
+
+ it 'passes the modified cookbook, environment, and role paths' do
+ expect(subject).to receive(:execute).
+ with(subject.command,
+ hash_including(args: [
+ '-B', File.join(Overcommit::Utils.repo_root, 'cookbooks', 'cookbook_a'),
+ '-B', File.join(Overcommit::Utils.repo_root, 'cookbooks', 'cookbook_b'),
+ '-E', File.join(Overcommit::Utils.repo_root, 'environments', 'production.json'),
+ '-E', File.join(Overcommit::Utils.repo_root, 'environments', 'staging.json'),
+ '-R', File.join(Overcommit::Utils.repo_root, 'roles', 'role_a.json'),
+ '-R', File.join(Overcommit::Utils.repo_root, 'roles', 'role_b.json'),
+ ]))
+ subject.run
+ end
+
+ context 'and Foodcritic returns an unsuccessful exit status' do
+ let(:result) do
+ double(
+ success?: false,
+ stderr: '',
+ stdout: <<-MSG,
+ FC023: Prefer conditional attributes: cookbooks/cookbook_a/recipes/default.rb:11
+ FC065: Ensure source_url is set in metadata: cookbooks/cookbook_b/metadata.rb:1
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ context 'and Foodcritic returns a successful exit status' do
+ let(:result) { double(success?: true) }
+
+ it { should pass }
+ end
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/forbidden_branches_spec.rb b/spec/overcommit/hook/pre_commit/forbidden_branches_spec.rb
new file mode 100644
index 00000000..abf6d4b3
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/forbidden_branches_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::ForbiddenBranches do
+ let(:default_config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:branch_patterns) { ['master', 'release/*'] }
+ let(:config) do
+ default_config.merge(
+ Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'ForbiddenBranches' => {
+ 'branch_patterns' => branch_patterns
+ }
+ }
+ )
+ )
+ end
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ around do |example|
+ repo do
+ `git checkout -b #{current_branch} > #{File::NULL} 2>&1`
+ example.run
+ end
+ end
+
+ context 'when committing to a permitted branch' do
+ let(:current_branch) { 'permitted' }
+ it { should pass }
+ end
+
+ context 'when committing to a forbidden branch' do
+ context 'when branch name matches a forbidden branch exactly' do
+ let(:current_branch) { 'master' }
+ it { should fail_hook }
+ end
+
+ context 'when branch name matches a forbidden branch glob pattern' do
+ let(:current_branch) { 'release/1.0' }
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/go_fmt_spec.rb b/spec/overcommit/hook/pre_commit/go_fmt_spec.rb
new file mode 100644
index 00000000..496ca097
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/go_fmt_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::GoFmt do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:files) do
+ %w[
+ pkg1/file1.go
+ pkg1/file2.go
+ pkg2/file1.go
+ file1.go
+ ]
+ end
+ before do
+ subject.stub(:applicable_files).and_return(files)
+ end
+
+ context 'when go fmt exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(success?: true, stderr: '', stdout: '')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it 'executes go fmt for each file' do
+ files.each do |file|
+ expect(subject).to receive(:execute).with(subject.command, args: [file]).once
+ end
+ subject.run
+ end
+
+ it 'passes' do
+ expect(subject).to pass
+ end
+ end
+
+ context 'when go fmt exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when go fmt returns an error to stdout' do
+ let(:error_message) { 'some go fmt error' }
+
+ before do
+ result.stub(:stdout).and_return(error_message)
+ result.stub(:stderr).and_return('')
+ end
+
+ it 'executes go fmt for each file' do
+ files.each do |file|
+ expect(subject).to receive(:execute).with(subject.command, args: [file]).once
+ end
+ subject.run
+ end
+
+ it 'fails' do
+ expect(subject).to fail_hook
+ end
+
+ it 'returns errors' do
+ message = subject.run.last
+ expect(message).to eq Array.new(files.count, error_message).join("\n")
+ end
+ end
+
+ context 'when fo fmt returns an error to stderr' do
+ let(:error_message) { 'go: command not found' }
+ before do
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return(error_message)
+ end
+
+ it 'executes go fmt for each file' do
+ files.each do |file|
+ expect(subject).to receive(:execute).with(subject.command, args: [file]).once
+ end
+ subject.run
+ end
+
+ it 'fails' do
+ expect(subject).to fail_hook
+ end
+
+ it 'returns valid message' do
+ message = subject.run.last
+ expect(message).to eq Array.new(files.count, error_message).join("\n")
+ end
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/go_lint_spec.rb b/spec/overcommit/hook/pre_commit/go_lint_spec.rb
index 96a67523..399a65f2 100644
--- a/spec/overcommit/hook/pre_commit/go_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/go_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::GoLint do
@@ -27,8 +29,9 @@
context 'and it reports an error' do
context 'on stdout' do
before do
- result.stub(stderr: '', stdout:
- 'file1.go:1:1: error should be the last type when returning multiple items'
+ result.stub(
+ stderr: '',
+ stdout: 'file1.go:1:1: error should be the last type when returning multiple items',
)
end
@@ -37,8 +40,9 @@
context 'on stderr' do
before do
- result.stub(stdout: '', stderr:
- "file1.go:1:1: expected 'package', found 'IDENT' foo"
+ result.stub(
+ stdout: '',
+ stderr: "file1.go:1:1: expected 'package', found 'IDENT' foo"
)
end
diff --git a/spec/overcommit/hook/pre_commit/go_vet_spec.rb b/spec/overcommit/hook/pre_commit/go_vet_spec.rb
index 2ad0fe62..69b89c4b 100644
--- a/spec/overcommit/hook/pre_commit/go_vet_spec.rb
+++ b/spec/overcommit/hook/pre_commit/go_vet_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::GoVet do
@@ -29,8 +31,8 @@
context 'when go tool vet is not installed' do
before do
- result.stub(stderr:
- 'go tool: no such tool "vet"; to install:'
+ result.stub(
+ stderr: 'go tool: no such tool "vet"; to install:',
)
end
@@ -39,8 +41,8 @@
context 'and it reports an error' do
before do
- result.stub(stderr:
- 'file1.go:1: possible formatting directive in Print call'
+ result.stub(
+ stderr: 'file1.go:1: possible formatting directive in Print call',
)
end
diff --git a/spec/overcommit/hook/pre_commit/golangci_lint_spec.rb b/spec/overcommit/hook/pre_commit/golangci_lint_spec.rb
new file mode 100644
index 00000000..45e4a7e3
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/golangci_lint_spec.rb
@@ -0,0 +1,98 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::GolangciLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:files) do
+ %w[
+ pkg1/file1.go
+ pkg1/file2.go
+ pkg2/file1.go
+ file1.go
+ ]
+ end
+ let(:packages) { %w[pkg1 pkg2 .] }
+ before do
+ subject.stub(:applicable_files).and_return(files)
+ end
+
+ context 'when golangci-lint exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(success?: true, stderr: '', stdout: '')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it 'passes packages to golangci-lint' do
+ expect(subject).to receive(:execute).with(subject.command, args: packages)
+ subject.run
+ end
+
+ it 'passes' do
+ expect(subject).to pass
+ end
+ end
+
+ context 'when golangci-lint exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when golangci-lint returns an error' do
+ let(:error_message) do
+ 'pkg1/file1.go:8:6: exported type `Test` should have comment or be unexported (golint)'
+ end
+
+ before do
+ result.stub(:stdout).and_return(error_message)
+ result.stub(:stderr).and_return('')
+ end
+
+ it 'passes packages to golangci-lint' do
+ expect(subject).to receive(:execute).with(subject.command, args: packages)
+ subject.run
+ end
+
+ it 'fails' do
+ expect(subject).to fail_hook
+ end
+
+ it 'returns valid message' do
+ message = subject.run.last
+ expect(message.file).to eq 'pkg1/file1.go'
+ expect(message.line).to eq 8
+ expect(message.content).to eq error_message
+ end
+ end
+
+ context 'when a generic error message is written to stderr' do
+ let(:error_message) { 'golangci-lint: command not found' }
+ before do
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return(error_message)
+ end
+
+ it 'passes packages to golangci-lint' do
+ expect(subject).to receive(:execute).with(subject.command, args: packages)
+ subject.run
+ end
+
+ it 'fails' do
+ expect(subject).to fail_hook
+ end
+
+ it 'returns valid message' do
+ message = subject.run.last
+ expect(message).to eq error_message
+ end
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/hadolint_spec.rb b/spec/overcommit/hook/pre_commit/hadolint_spec.rb
new file mode 100644
index 00000000..ad756f30
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/hadolint_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Hadolint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ let(:applicable_files) { %w[Dockerfile Dockerfile.web] }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(applicable_files)
+ end
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ subject.stub(:execute).with(%w[hadolint], args: Array(applicable_files.first)).
+ and_return(result_dockerfile)
+ subject.stub(:execute).with(%w[hadolint], args: Array(applicable_files.last)).
+ and_return(result_dockerfile_web)
+ end
+
+ context 'and has 2 suggestions' do
+ let(:result_dockerfile) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Dockerfile:5 DL3015 Avoid additional packages by specifying `--no-install-recommends`
+ MSG
+ )
+ end
+ let(:result_dockerfile_web) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Dockerfile.web:13 DL3020 Use COPY instead of ADD for files and folders
+ MSG
+ )
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and has single suggestion for double quote' do
+ let(:result_dockerfile) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+Dockerfile:11 SC2086 Double quote to prevent globbing and word splitting.
+ MSG
+ )
+ end
+ let(:result_dockerfile_web) do
+ double(success?: true, stdout: '')
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and does not have any suggestion' do
+ let(:result_dockerfile) do
+ double(success?: true, stdout: '')
+ end
+ let(:result_dockerfile_web) do
+ double(success?: true, stdout: '')
+ end
+
+ it { should pass }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/haml_lint_spec.rb b/spec/overcommit/hook/pre_commit/haml_lint_spec.rb
index adadb799..9eb15ee9 100644
--- a/spec/overcommit/hook/pre_commit/haml_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/haml_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::HamlLint do
diff --git a/spec/overcommit/hook/pre_commit/hard_tabs_spec.rb b/spec/overcommit/hook/pre_commit/hard_tabs_spec.rb
index 1bb662a9..3bee937d 100644
--- a/spec/overcommit/hook/pre_commit/hard_tabs_spec.rb
+++ b/spec/overcommit/hook/pre_commit/hard_tabs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::HardTabs do
diff --git a/spec/overcommit/hook/pre_commit/hlint_spec.rb b/spec/overcommit/hook/pre_commit/hlint_spec.rb
index 08183e18..7a87c427 100644
--- a/spec/overcommit/hook/pre_commit/hlint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/hlint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Hlint do
diff --git a/spec/overcommit/hook/pre_commit/html_hint_spec.rb b/spec/overcommit/hook/pre_commit/html_hint_spec.rb
index b96d8e14..66146d80 100644
--- a/spec/overcommit/hook/pre_commit/html_hint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/html_hint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::HtmlHint do
diff --git a/spec/overcommit/hook/pre_commit/html_tidy_spec.rb b/spec/overcommit/hook/pre_commit/html_tidy_spec.rb
index 389d38d0..77754bca 100644
--- a/spec/overcommit/hook/pre_commit/html_tidy_spec.rb
+++ b/spec/overcommit/hook/pre_commit/html_tidy_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::HtmlTidy do
diff --git a/spec/overcommit/hook/pre_commit/image_optim_spec.rb b/spec/overcommit/hook/pre_commit/image_optim_spec.rb
index 589344fb..7ac17ba5 100644
--- a/spec/overcommit/hook/pre_commit/image_optim_spec.rb
+++ b/spec/overcommit/hook/pre_commit/image_optim_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::ImageOptim do
diff --git a/spec/overcommit/hook/pre_commit/java_checkstyle_spec.rb b/spec/overcommit/hook/pre_commit/java_checkstyle_spec.rb
index 1b699a03..e06c724b 100644
--- a/spec/overcommit/hook/pre_commit/java_checkstyle_spec.rb
+++ b/spec/overcommit/hook/pre_commit/java_checkstyle_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::JavaCheckstyle do
@@ -29,7 +31,7 @@
end
end
- context 'when checkstyle exits unsucessfully' do
+ context 'when checkstyle exits unsuccessfully' do
let(:result) { double('result') }
before do
@@ -37,7 +39,7 @@
subject.stub(:execute).and_return(result)
end
- context 'and it reports an error' do
+ context 'and it reports a message with no severity tag' do
before do
result.stub(:stdout).and_return([
'Starting audit...',
@@ -48,5 +50,41 @@
it { should fail_hook }
end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'Starting audit...',
+ '[ERROR] file1.java:1: Missing a Javadoc comment.',
+ 'Audit done.'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and it reports an warning' do
+ before do
+ result.stub(:stdout).and_return([
+ 'Starting audit...',
+ '[WARN] file1.java:1: Missing a Javadoc comment.',
+ 'Audit done.'
+ ].join("\n"))
+ end
+
+ it { should warn }
+ end
+
+ context 'and it reports an info message' do
+ before do
+ result.stub(:stdout).and_return([
+ 'Starting audit...',
+ '[INFO] file1.java:1: Missing a Javadoc comment.',
+ 'Audit done.'
+ ].join("\n"))
+ end
+
+ it { should warn }
+ end
end
end
diff --git a/spec/overcommit/hook/pre_commit/js_hint_spec.rb b/spec/overcommit/hook/pre_commit/js_hint_spec.rb
index 22c64eb5..4666c1af 100644
--- a/spec/overcommit/hook/pre_commit/js_hint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/js_hint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::JsHint do
diff --git a/spec/overcommit/hook/pre_commit/js_lint_spec.rb b/spec/overcommit/hook/pre_commit/js_lint_spec.rb
index 3fde219a..3d85b80c 100644
--- a/spec/overcommit/hook/pre_commit/js_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/js_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::JsLint do
diff --git a/spec/overcommit/hook/pre_commit/jscs_spec.rb b/spec/overcommit/hook/pre_commit/jscs_spec.rb
index 9edd17de..4cc56dec 100644
--- a/spec/overcommit/hook/pre_commit/jscs_spec.rb
+++ b/spec/overcommit/hook/pre_commit/jscs_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Jscs do
@@ -22,7 +24,7 @@
it { should fail_hook }
end
- context 'when jscs exits unsucessfully with status code 2' do
+ context 'when jscs exits unsuccessfully with status code 2' do
let(:result) { double('result') }
before do
diff --git a/spec/overcommit/hook/pre_commit/jsl_spec.rb b/spec/overcommit/hook/pre_commit/jsl_spec.rb
index c8eb53e2..51fdd998 100644
--- a/spec/overcommit/hook/pre_commit/jsl_spec.rb
+++ b/spec/overcommit/hook/pre_commit/jsl_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Jsl do
diff --git a/spec/overcommit/hook/pre_commit/json_syntax_spec.rb b/spec/overcommit/hook/pre_commit/json_syntax_spec.rb
index f456c081..0ee4a57e 100644
--- a/spec/overcommit/hook/pre_commit/json_syntax_spec.rb
+++ b/spec/overcommit/hook/pre_commit/json_syntax_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'json'
diff --git a/spec/overcommit/hook/pre_commit/kt_lint_spec.rb b/spec/overcommit/hook/pre_commit/kt_lint_spec.rb
new file mode 100644
index 00000000..6ea89467
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/kt_lint_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::KtLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.kt file2.kt])
+ end
+
+ context 'when KtLint exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when KtLint exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.kt:12:10: error message'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/license_finder_spec.rb b/spec/overcommit/hook/pre_commit/license_finder_spec.rb
new file mode 100644
index 00000000..ef0c8f5a
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/license_finder_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::LicenseFinder do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when license_finder exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when license_finder runs unsucessfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(false)
+ result.stub(:stdout).and_return('Some error message')
+ result.stub(:stderr).and_return('')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should fail_hook 'Some error message' }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/license_header_spec.rb b/spec/overcommit/hook/pre_commit/license_header_spec.rb
new file mode 100644
index 00000000..ce454fab
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/license_header_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::LicenseHeader do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ context 'when license file is missing' do
+ it { should fail_hook }
+ end
+
+ context 'when license file exists' do
+ let(:license_contents) { <<-LICENSE }
+ Look at me
+ I'm a license
+ LICENSE
+
+ let(:file) { 'some-file.txt' }
+
+ before do
+ File.open('LICENSE.txt', 'w') { |f| f.write(license_contents) }
+ subject.stub(:applicable_files).and_return([file])
+ end
+
+ context 'when all files contain the license header' do
+ before do
+ File.open(file, 'w') do |f|
+ license_contents.split("\n").each do |line|
+ f.puts("// #{line}")
+ end
+ f.write('And some text')
+ end
+ end
+
+ it { should pass }
+ end
+
+ context 'when a file is missing a license header' do
+ before do
+ File.open(file, 'w') { |f| f.write('Some text without a license') }
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/line_endings_spec.rb b/spec/overcommit/hook/pre_commit/line_endings_spec.rb
new file mode 100644
index 00000000..bcb576d9
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/line_endings_spec.rb
@@ -0,0 +1,80 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::LineEndings do
+ let(:config) do
+ Overcommit::ConfigurationLoader.default_configuration.merge(
+ Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'LineEndings' => {
+ 'eol' => eol
+ }
+ }
+ )
+ )
+ end
+
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+ let(:eol) { "\n" }
+ let(:staged_file) { 'filename.txt' }
+
+ before do
+ skip('Skip LineEndings tests for Git < 2.10') if Overcommit::GIT_VERSION < '2.10'
+ subject.stub(:applicable_files).and_return([staged_file])
+ end
+
+ around do |example|
+ repo do
+ File.open(staged_file, 'w') { |f| f.write(contents) }
+ `git add "#{staged_file}" > #{File::NULL} 2>&1`
+ example.run
+ end
+ end
+
+ context 'when file path contains spaces' do
+ let!(:staged_file) { 'a file with spaces.txt' }
+ let(:contents) { "test\n" }
+
+ it { should_not fail_hook }
+ end
+
+ context 'when enforcing \n' do
+ context 'when file contains \r\n line endings' do
+ let(:contents) { "CR-LF\r\nline\r\nendings\r\n" }
+
+ it { should fail_hook }
+ end
+
+ context 'when file contains \n endings' do
+ let(:contents) { "LF\nline\nendings\n" }
+
+ it { should pass }
+ end
+ end
+
+ context 'when enforcing \r\n' do
+ let(:eol) { "\r\n" }
+
+ context 'when file contains \r\n line endings' do
+ let(:contents) { "CR-LF\r\nline\r\nendings\r\n" }
+
+ it { should pass }
+ end
+
+ context 'when file contains \n line endings' do
+ let(:contents) { "LF\nline\nendings\n" }
+
+ it { should fail_hook }
+ end
+ end
+
+ unless Overcommit::OS.windows?
+ context 'when attempting to check a binary file' do
+ let(:contents) { "\xFF\xD8\xFF\xE0\u0000\u0010JFIF" }
+
+ it { should warn }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/local_paths_in_gemfile_spec.rb b/spec/overcommit/hook/pre_commit/local_paths_in_gemfile_spec.rb
index f6c3755c..ff0109ff 100644
--- a/spec/overcommit/hook/pre_commit/local_paths_in_gemfile_spec.rb
+++ b/spec/overcommit/hook/pre_commit/local_paths_in_gemfile_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::LocalPathsInGemfile do
diff --git a/spec/overcommit/hook/pre_commit/mdl_spec.rb b/spec/overcommit/hook/pre_commit/mdl_spec.rb
index c7da4478..119f9a00 100644
--- a/spec/overcommit/hook/pre_commit/mdl_spec.rb
+++ b/spec/overcommit/hook/pre_commit/mdl_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Mdl do
@@ -23,12 +25,19 @@
context 'when mdl exits unsuccessfully' do
let(:success) { false }
+ let(:message) { subject.run.last }
context 'and it reports an error' do
- let(:stdout) { 'file1.md:1: MD013 Line length' }
+ let(:stdout) do
+ '[{"filename":"file1.md","line":1,"rule":"MD013","aliases":["line-length"],'\
+ '"description":"Line length"}]'
+ end
let(:stderr) { '' }
it { should fail_hook }
+ it { expect(message.file).to eq 'file1.md' }
+ it { expect(message.line).to eq 1 }
+ it { expect(message.content).to eq 'file1.md:1 MD013 Line length' }
end
context 'when there is an error running mdl' do
@@ -36,6 +45,7 @@
let(:stderr) { 'Some runtime error' }
it { should fail_hook }
+ it { expect(message).to eq 'Some runtime error' }
end
end
end
diff --git a/spec/overcommit/hook/pre_commit/merge_conflicts_spec.rb b/spec/overcommit/hook/pre_commit/merge_conflicts_spec.rb
index 8d1747e0..f7790fe9 100644
--- a/spec/overcommit/hook/pre_commit/merge_conflicts_spec.rb
+++ b/spec/overcommit/hook/pre_commit/merge_conflicts_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::MergeConflicts do
diff --git a/spec/overcommit/hook/pre_commit/mix_format_spec.rb b/spec/overcommit/hook/pre_commit/mix_format_spec.rb
new file mode 100644
index 00000000..9acc50ed
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/mix_format_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::MixFormat do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.ex file2.exs])
+ end
+
+ context 'when mix format exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when mix format exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return([
+ '** (Mix) mix format failed due to --check-formatted.',
+ 'The following files are not formatted:',
+ '',
+ ' * lib/file1.ex',
+ ' * lib/file2.ex'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/nginx_test_spec.rb b/spec/overcommit/hook/pre_commit/nginx_test_spec.rb
index cff2f699..b13ad8ef 100644
--- a/spec/overcommit/hook/pre_commit/nginx_test_spec.rb
+++ b/spec/overcommit/hook/pre_commit/nginx_test_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::NginxTest do
diff --git a/spec/overcommit/hook/pre_commit/pep257_spec.rb b/spec/overcommit/hook/pre_commit/pep257_spec.rb
index 65dddb90..2210c650 100644
--- a/spec/overcommit/hook/pre_commit/pep257_spec.rb
+++ b/spec/overcommit/hook/pre_commit/pep257_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Pep257 do
diff --git a/spec/overcommit/hook/pre_commit/pep8_spec.rb b/spec/overcommit/hook/pre_commit/pep8_spec.rb
index 6916073c..f3e01947 100644
--- a/spec/overcommit/hook/pre_commit/pep8_spec.rb
+++ b/spec/overcommit/hook/pre_commit/pep8_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Pep8 do
diff --git a/spec/overcommit/hook/pre_commit/php_lint_spec.rb b/spec/overcommit/hook/pre_commit/php_lint_spec.rb
new file mode 100644
index 00000000..ec755345
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/php_lint_spec.rb
@@ -0,0 +1,47 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::PhpLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(['sample.php'])
+ end
+
+ context 'when php lint exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:status).and_return(0)
+ result.stub(:success?).and_return(true)
+ result.stub(:stdout).and_return('No syntax errors detected in sample.php')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when php lint exits unsuccessfully' do
+ before do
+ # php -l prints the same to both stdout and stderr
+ # rubocop:disable Layout/LineLength
+ sample_output = [
+ '',
+ "Parse error: syntax error, unexpected '0' (T_LNUMBER), expecting variable (T_VARIABLE) or '{' or '$' in sample.php on line 3 ",
+ 'Errors parsing invalid.php',
+ ].join("\n")
+ # rubocop:enable Layout/LineLength
+
+ result = double('result')
+ result.stub(:status).and_return(255)
+ result.stub(:success?).and_return(false)
+ result.stub(:stdout).and_return(sample_output)
+ result.stub(:stderr).and_return(sample_output)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/php_stan_spec.rb b/spec/overcommit/hook/pre_commit/php_stan_spec.rb
new file mode 100644
index 00000000..a4272fd6
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/php_stan_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::PhpStan do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[sample.php])
+ end
+
+ context 'when phpstan exits successfully' do
+ before do
+ sample_output = ''
+
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ result.stub(:stdout).and_return(sample_output)
+ result.stub(:status).and_return(0)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when phpstan exits unsuccessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ result.stub(:status).and_return(2)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports a warning' do
+ before do
+ sample_output = [
+ '/sample1.php:14:Call to an undefined static method Sample1::where()',
+ '/sample2.php:17:Anonymous function has an unused use $myVariable.'
+ ].join("\n")
+ result.stub(:stdout).and_return(sample_output)
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb b/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb
new file mode 100644
index 00000000..a4b1e187
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb
@@ -0,0 +1,86 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::PhpCsFixer do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[sample.php])
+ end
+
+ context 'when phpcs fixer exits successfully with fixed file' do
+ before do
+ # rubocop:disable Layout/LineLength
+ sample_output = [
+ 'Loaded config default.',
+ 'Using cache file ".php_cs.cache".',
+ 'F',
+ 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error',
+ ' 1) foo/fixable.php (braces)',
+ '',
+ 'Fixed all files in 0.001 seconds, 10.000 MB memory used',
+ '',
+ ].join("\n")
+ # rubocop:enable Layout/LineLength
+
+ result = double('result')
+ result.stub(:status).and_return(0)
+ result.stub(:success?).and_return(true)
+ result.stub(:stdout).and_return(sample_output)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should warn }
+ end
+
+ context 'when phpcs fixer exits successfully with no file to fix' do
+ before do
+ # rubocop:disable Layout/LineLength
+ sample_output = [
+ 'Loaded config default.',
+ 'Using cache file ".php_cs.cache".',
+ 'S',
+ 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error',
+ '',
+ ].join("\n")
+ # rubocop:enable Layout/LineLength
+
+ result = double('result')
+ result.stub(:status).and_return(0)
+ result.stub(:success?).and_return(true)
+ result.stub(:stdout).and_return(sample_output)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when phpcs exits unsuccessfully' do
+ before do
+ # rubocop:disable Layout/LineLength
+ sample_output = [
+ 'Loaded config default.',
+ 'Using cache file ".php_cs.cache".',
+ 'I',
+ 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error',
+ 'Fixed all files in 0.001 seconds, 10.000 MB memory used',
+ '',
+ 'Files that were not fixed due to errors reported during linting before fixing:',
+ ' 1) /home/damien/Code/Rezdy/php/foo/broken.php',
+ '',
+ ].join("\n")
+ # rubocop:enable Layout/LineLength
+
+ result = double('result')
+ result.stub(:status).and_return(1)
+ result.stub(:success?).and_return(false)
+ result.stub(:stdout).and_return(sample_output)
+ result.stub(:stderr).and_return(sample_output)
+ subject.stub(:execute).and_return(result)
+ end
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/phpcs_spec.rb b/spec/overcommit/hook/pre_commit/phpcs_spec.rb
new file mode 100644
index 00000000..6982a297
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/phpcs_spec.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::PhpCs do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[sample.php])
+ end
+
+ context 'when phpcs exits successfully' do
+ before do
+ sample_output = [
+ 'File,Line,Column,Type,Message,Source,Severity,Fixable',
+ ''
+ ].join("\n")
+
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ result.stub(:stdout).and_return(sample_output)
+ result.stub(:status).and_return(0)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when phpcs exits unsuccessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ result.stub(:status).and_return(2)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports a warning' do
+ before do
+ # rubocop:disable Layout/LineLength
+ sample_output = [
+ 'File,Line,Column,Type,Message,Source,Severity,Fixable',
+ '"/Users/craig/HelpScout/overcommit-testing/invalid.php",5,1,warning,"Possible parse error: FOREACH has no AS statement",Squiz.ControlStructures.ForEachLoopDeclaration.MissingAs,5,0'
+ ].join("\n")
+ # rubocop:enable Layout/LineLength
+ result.stub(:stdout).and_return(sample_output)
+ end
+
+ it { should warn }
+ end
+
+ context 'and it reports an error' do
+ before do
+ # rubocop:disable Layout/LineLength
+ sample_output = [
+ 'File,Line,Column,Type,Message,Source,Severity,Fixable',
+ '"/Users/craig/HelpScout/overcommit-testing/invalid.php",5,1,error,"Inline control structures are not allowed",Generic.ControlStructures.InlineControlStructure.NotAllowed,5,1'
+ ].join("\n")
+ # rubocop:enable Layout/LineLength
+ result.stub(:stdout).and_return(sample_output)
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/pronto_spec.rb b/spec/overcommit/hook/pre_commit/pronto_spec.rb
new file mode 100644
index 00000000..c9d56abe
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/pronto_spec.rb
@@ -0,0 +1,71 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Pronto do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
+ context 'when pronto exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when pronto exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return([
+ 'file2.rb:10 E: IDENTICAL code found in :iter.',
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and it reports a warning' do
+ before do
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return <<~MESSAGE
+ Running Pronto::Rubocop
+ file1.rb:12 W: Line is too long. [107/80]
+ file2.rb:14 I: Prefer single-quoted strings
+
+ ```suggestion
+ x = 'x'
+ ```
+ MESSAGE
+ end
+
+ it { should warn }
+ end
+
+ context 'and it has a generic error message written to stderr' do
+ before do
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return([
+ 'Could not find pronto in any of the sources'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/puppet_lint_spec.rb b/spec/overcommit/hook/pre_commit/puppet_lint_spec.rb
index 94a0fa7e..518f06e2 100644
--- a/spec/overcommit/hook/pre_commit/puppet_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/puppet_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::PuppetLint do
diff --git a/spec/overcommit/hook/pre_commit/puppet_metadata_json_lint_spec.rb b/spec/overcommit/hook/pre_commit/puppet_metadata_json_lint_spec.rb
new file mode 100644
index 00000000..1edeb21b
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/puppet_metadata_json_lint_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::PuppetMetadataJsonLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.pp file2.pp metadata.json])
+ end
+
+ context 'when metadata-json-lint exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'with no output' do
+ before do
+ result.stub(:stdout).and_return('')
+ end
+
+ it { should pass }
+ end
+
+ context 'and it reports a warning' do
+ before do
+ result.stub(:stdout).and_return(normalize_indent(<<-OUT))
+ (WARN) requirements: The 'pe' requirement is no longer supported by the Forge.
+ OUT
+ end
+
+ it { should warn }
+ end
+ end
+
+ context 'when metadata-json-lint exits unsuccessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports a warning' do
+ before do
+ result.stub(:stdout).and_return(normalize_indent(<<-OUT))
+ (WARN) requirements: The 'pe' requirement is no longer supported by the Forge.
+ OUT
+ end
+
+ it { should warn }
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return(normalize_indent(<<-OUT))
+ (ERR) requirements: The 'pe' requirement is no longer supported by the Forge.
+ OUT
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/pycodestyle_spec.rb b/spec/overcommit/hook/pre_commit/pycodestyle_spec.rb
new file mode 100644
index 00000000..609388a0
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/pycodestyle_spec.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Pycodestyle do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.py file2.py])
+ end
+
+ context 'when pycodestyle exits successfully' do
+ before do
+ result = double('result')
+ result.stub(success?: true, stdout: '')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when pycodestyle exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports a warning' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.py:1:1: W391 blank line at end of file'
+ ].join("\n"))
+ end
+
+ it { should warn }
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.py:1:80: E501 line too long (80 > 79 characters)'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/pydocstyle_spec.rb b/spec/overcommit/hook/pre_commit/pydocstyle_spec.rb
new file mode 100644
index 00000000..2fb61038
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/pydocstyle_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Pydocstyle do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.py file2.py])
+ end
+
+ context 'when pydocstyle exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when pydocstyle exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stderr).and_return([
+ 'file1.py:1 in public method `foo`:',
+ ' D102: Docstring missing'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/pyflakes_spec.rb b/spec/overcommit/hook/pre_commit/pyflakes_spec.rb
index a503d1aa..fd913155 100644
--- a/spec/overcommit/hook/pre_commit/pyflakes_spec.rb
+++ b/spec/overcommit/hook/pre_commit/pyflakes_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Pyflakes do
diff --git a/spec/overcommit/hook/pre_commit/pylint_spec.rb b/spec/overcommit/hook/pre_commit/pylint_spec.rb
index b8b3159d..b0368d6a 100644
--- a/spec/overcommit/hook/pre_commit/pylint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/pylint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Pylint do
diff --git a/spec/overcommit/hook/pre_commit/python_flake8_spec.rb b/spec/overcommit/hook/pre_commit/python_flake8_spec.rb
index 10016a00..a4bb1109 100644
--- a/spec/overcommit/hook/pre_commit/python_flake8_spec.rb
+++ b/spec/overcommit/hook/pre_commit/python_flake8_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::PythonFlake8 do
diff --git a/spec/overcommit/hook/pre_commit/r_spec_spec.rb b/spec/overcommit/hook/pre_commit/r_spec_spec.rb
new file mode 100644
index 00000000..7fdff3b3
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/r_spec_spec.rb
@@ -0,0 +1,116 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::RSpec do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when rspec exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+
+ it {
+ expect(subject).to receive(:execute).with(['rspec']).and_return(result)
+
+ subject.run
+ }
+ end
+
+ context 'with included files set' do
+ let(:result) { double('result') }
+ let(:config) do
+ super().merge(Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'RSpec' => {
+ 'include' => ['**/*_spec.rb'],
+ }
+ }
+ ))
+ end
+
+ let(:context) { double('context') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ subject.stub(:applicable_files).and_return('spec/test_spec.rb')
+ end
+
+ it { should pass }
+
+ it {
+ expect(subject).to receive(:execute).with(['rspec'],
+ args: 'spec/test_spec.rb').and_return(result)
+
+ subject.run
+ }
+ end
+
+ context 'when rspec exits unsuccessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'with a runtime error' do
+ before do
+ result.stub(stdout: '', stderr: <<-MSG)
+ /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `load': /home/user/dev/github/overcommit/spec/overcommit/hook/pre_push/rspec_spec.rb:49: can't find string "EOS" anywhere before EOF (SyntaxError)
+ /home/user/dev/overcommit/spec/overcommit/hook/pre_push/rspec_spec.rb:29: syntax error, unexpected end-of-input
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `block in load_spec_files'
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `each'
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `load_spec_files'
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:97:in `setup'
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:85:in `run'
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:70:in `run'
+ from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:38:in `invoke'
+ from /home/user/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.2/exe/rspec:4:in `'
+ from /home/user/.rbenv/versions/2.2.1/bin/rspec:23:in `load'
+ from /home/user/.rbenv/versions/2.2.1/bin/rspec:23:in `'
+ MSG
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'with a test failure' do
+ before do
+ result.stub(stderr: '', stdout: <<-MSG)
+ .FF
+
+ Failures:
+
+ 1) Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a runtime error should fail
+ Failure/Error: it { should fail_hook }
+ expected that the hook would fail
+ # ./spec/overcommit/hook/pre_push/rspec_spec.rb:45:in `block (4 levels) in '
+
+ 2) Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a test failure should fail
+ Failure/Error: it { should fail_hook }
+ expected that the hook would fail
+ # ./spec/overcommit/hook/pre_push/rspec_spec.rb:57:in `block (4 levels) in '
+
+ Finished in 0.00505 seconds (files took 0.27437 seconds to load)
+ 3 examples, 2 failures
+
+ Failed examples:
+
+ rspec ./spec/overcommit/hook/pre_push/rspec_spec.rb:45 # Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a runtime error should fail
+ rspec ./spec/overcommit/hook/pre_push/rspec_spec.rb:57 # Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a test failure should fail
+ MSG
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/rails_best_practices_spec.rb b/spec/overcommit/hook/pre_commit/rails_best_practices_spec.rb
index 145838e5..6e451f25 100644
--- a/spec/overcommit/hook/pre_commit/rails_best_practices_spec.rb
+++ b/spec/overcommit/hook/pre_commit/rails_best_practices_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::RailsBestPractices do
@@ -5,6 +7,10 @@
let(:context) { double('context') }
subject { described_class.new(config, context) }
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
context 'when rails_best_practices exits successfully' do
before do
result = double('result')
diff --git a/spec/overcommit/hook/pre_commit/rails_schema_up_to_date_spec.rb b/spec/overcommit/hook/pre_commit/rails_schema_up_to_date_spec.rb
index 4e7078d2..9ee704b7 100644
--- a/spec/overcommit/hook/pre_commit/rails_schema_up_to_date_spec.rb
+++ b/spec/overcommit/hook/pre_commit/rails_schema_up_to_date_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::RailsSchemaUpToDate do
@@ -44,7 +46,7 @@
around do |example|
repo do
FileUtils.mkdir_p('db/migrate')
- File.open(ruby_schema_file, 'w') { |f| f.write('schema') }
+ File.open(ruby_schema_file, 'w') { |f| f.write('version: 20160904205635') }
`git add #{ruby_schema_file}`
example.run
end
@@ -61,13 +63,40 @@
around do |example|
repo do
FileUtils.mkdir_p('db/migrate')
- File.open(sql_schema_file, 'w') { |f| f.write('schema') }
+ File.open(sql_schema_file, 'w') { |f| f.write("VALUES ('20151214213046')") }
`git add #{sql_schema_file}`
example.run
end
end
it { should fail_hook }
+
+ context 'when non ASCII encoding is required' do
+ let!(:config) do
+ super().merge(Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'RailsSchemaUpToDate' => {
+ 'encoding' => 'utf-8'
+ }
+ }
+ ))
+ end
+
+ before do
+ subject.stub(:applicable_files).and_return([sql_schema_file])
+ end
+
+ around do |example|
+ repo do
+ FileUtils.mkdir_p('db/migrate')
+ File.open(sql_schema_file, 'w') { |f| f.write("version: 12345678901234\nVALUES ('字')") }
+ `git add #{sql_schema_file}`
+ example.run
+ end
+ end
+
+ it { should fail_hook }
+ end
end
context 'when a Ruby schema file with the latest version and migrations are added' do
@@ -94,6 +123,30 @@
it { should pass }
end
+ context 'when a Ruby schema generated by Rails 5.2+ format and migrations are added' do
+ before do
+ subject.stub(:applicable_files).and_return(migration_files << ruby_schema_file)
+ end
+
+ around do |example|
+ repo do
+ FileUtils.mkdir_p('db/migrate')
+
+ File.open(ruby_schema_file, 'w') { |f| f.write('2014_03_05_123456') }
+ `git add #{ruby_schema_file}`
+
+ migration_files.each do |migration_file|
+ File.open(migration_file, 'w') { |f| f.write('migration') }
+ `git add #{migration_file}`
+ end
+
+ example.run
+ end
+ end
+
+ it { should pass }
+ end
+
context 'when a Ruby schema file which is not at the latest version and migrations are added' do
before do
subject.stub(:applicable_files).and_return(migration_files << ruby_schema_file)
@@ -193,4 +246,23 @@
it { should fail_hook }
end
+
+ context 'when the schema file is at version 0 and there are no migrations' do
+ before do
+ subject.stub(:applicable_files).and_return([ruby_schema_file])
+ end
+
+ around do |example|
+ repo do
+ FileUtils.mkdir_p('db')
+
+ File.open(ruby_schema_file, 'w') { |f| f.write('version: 0') }
+ `git add #{ruby_schema_file}`
+
+ example.run
+ end
+ end
+
+ it { should pass }
+ end
end
diff --git a/spec/overcommit/hook/pre_commit/rake_target_spec.rb b/spec/overcommit/hook/pre_commit/rake_target_spec.rb
new file mode 100644
index 00000000..e295e61f
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/rake_target_spec.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::RakeTarget do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'without targets parameters' do
+ let(:result) { double('result') }
+ it 'raises' do
+ expect { subject.run }.to raise_error(
+ RuntimeError, /RakeTarget: targets parameter is empty.*/
+ )
+ end
+ end
+
+ context 'with targets parameter set' do
+ let(:config) do
+ super().merge(Overcommit::Configuration.new(
+ 'PreCommit' => {
+ 'RakeTarget' => {
+ 'targets' => ['test'],
+ }
+ }
+ ))
+ end
+ let(:result) { double('result') }
+
+ context 'when rake exits successfully' do
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ result.stub(:stdout).and_return('ANYTHING')
+ end
+
+ it { should pass }
+ end
+
+ context 'when rake exits unsuccessfully' do
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ result.stub(:stdout).and_return('ANYTHING')
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/reek_spec.rb b/spec/overcommit/hook/pre_commit/reek_spec.rb
index 7d72ebfa..8e111e7c 100644
--- a/spec/overcommit/hook/pre_commit/reek_spec.rb
+++ b/spec/overcommit/hook/pre_commit/reek_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Reek do
diff --git a/spec/overcommit/hook/pre_commit/rst_lint_spec.rb b/spec/overcommit/hook/pre_commit/rst_lint_spec.rb
new file mode 100644
index 00000000..ed9edbf8
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/rst_lint_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::RstLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ let(:result) { double('result') }
+
+ before do
+ result.stub(success?: success, stdout: stdout, stderr: stderr)
+ subject.stub(:applicable_files).and_return(%w[file1.rst file2.rst])
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'when rst-lint exits successfully' do
+ let(:success) { true }
+ let(:stdout) { '' }
+ let(:stderr) { '' }
+
+ it { should pass }
+ end
+
+ context 'when rst-lint exits unsuccessfully' do
+ let(:success) { false }
+
+ context 'and it reports an error' do
+ let(:stdout) { 'WARNING file1.rst:7 Title underline too short.' }
+ let(:stderr) { '' }
+
+ it { should fail_hook }
+ end
+
+ context 'when there is an error running rst-lint' do
+ let(:stdout) { '' }
+ let(:stderr) { 'Some runtime error' }
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/rubo_cop_spec.rb b/spec/overcommit/hook/pre_commit/rubo_cop_spec.rb
index 4a3d25d5..f06c8d1d 100644
--- a/spec/overcommit/hook/pre_commit/rubo_cop_spec.rb
+++ b/spec/overcommit/hook/pre_commit/rubo_cop_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::RuboCop do
diff --git a/spec/overcommit/hook/pre_commit/ruby_lint_spec.rb b/spec/overcommit/hook/pre_commit/ruby_lint_spec.rb
index 0d0d8780..062a3296 100644
--- a/spec/overcommit/hook/pre_commit/ruby_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/ruby_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::RubyLint do
diff --git a/spec/overcommit/hook/pre_commit/ruby_syntax_spec.rb b/spec/overcommit/hook/pre_commit/ruby_syntax_spec.rb
new file mode 100644
index 00000000..0eaa4459
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/ruby_syntax_spec.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::RubySyntax do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
+ context 'when ruby_syntax exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'with no errors' do
+ before do
+ result.stub(:stderr).and_return('')
+ end
+
+ it { should pass }
+ end
+ end
+
+ context 'when ruby_syntax exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stderr).and_return([
+ "file1.rb:2: syntax error, unexpected '^'"
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/scalariform_spec.rb b/spec/overcommit/hook/pre_commit/scalariform_spec.rb
index 2d258678..cb24df97 100644
--- a/spec/overcommit/hook/pre_commit/scalariform_spec.rb
+++ b/spec/overcommit/hook/pre_commit/scalariform_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Scalariform do
diff --git a/spec/overcommit/hook/pre_commit/scalastyle_spec.rb b/spec/overcommit/hook/pre_commit/scalastyle_spec.rb
index 375e36f9..90e44d4c 100644
--- a/spec/overcommit/hook/pre_commit/scalastyle_spec.rb
+++ b/spec/overcommit/hook/pre_commit/scalastyle_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Scalastyle do
@@ -41,7 +43,21 @@
OUT
end
- it { should warn }
+ it { should warn(/Use : Unit = for procedures/) }
+ end
+
+ context 'and it reports a warning with no line' do
+ before do
+ result.stub(stderr: '', stdout: normalize_indent(<<-OUT))
+ warning file=file1.scala message=File must end with newline character
+ Processed 1 file(s)
+ Found 0 errors
+ Found 1 warnings
+ Finished in 490 ms
+ OUT
+ end
+
+ it { should warn(/File must end with newline character/) }
end
end
@@ -64,7 +80,21 @@
OUT
end
- it { should fail_hook }
+ it { should fail_hook(/Use : Unit = for procedures/) }
+ end
+
+ context 'and it reports an error with no line' do
+ before do
+ result.stub(stderr: '', stdout: normalize_indent(<<-OUT))
+ error file=file1.scala message=File must end with newline character
+ Processed 1 file(s)
+ Found 1 errors
+ Found 0 warnings
+ Finished in 490 ms
+ OUT
+ end
+
+ it { should fail_hook(/File must end with newline character/) }
end
context 'with a usage message' do
@@ -83,7 +113,7 @@
OUT
end
- it { should fail_hook }
+ it { should fail_hook(/Usage/) }
end
context 'with a runtime error' do
@@ -104,7 +134,7 @@
ERR
end
- it { should fail_hook }
+ it { should fail_hook(/Exception/) }
end
end
end
diff --git a/spec/overcommit/hook/pre_commit/scss_lint_spec.rb b/spec/overcommit/hook/pre_commit/scss_lint_spec.rb
index a38144d9..ae8b2f68 100644
--- a/spec/overcommit/hook/pre_commit/scss_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/scss_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::ScssLint do
@@ -30,9 +32,21 @@
context 'and it reports a warning' do
before do
result.stub(:status).and_return(1)
- result.stub(:stdout).and_return([
- 'file1.scss:1 [W] Prefer single quoted strings',
- ].join("\n"))
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return(<<-JSON)
+ {
+ "test.scss": [
+ {
+ "line": 1,
+ "column": 1,
+ "length": 2,
+ "severity": "warning",
+ "reason": "Empty rule",
+ "linter": "EmptyRule"
+ }
+ ]
+ }
+ JSON
end
it { should warn }
@@ -41,17 +55,39 @@
context 'and it reports an error' do
before do
result.stub(:status).and_return(2)
- result.stub(:stdout).and_return([
- 'file1.scss:1 [E] Syntax Error: Invalid CSS',
- ].join("\n"))
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return(<<-JSON)
+ {
+ "test.scss": [
+ {
+ "line": 1,
+ "column": 1,
+ "length": 2,
+ "severity": "error",
+ "reason": "Syntax error",
+ }
+ ]
+ }
+ JSON
end
it { should fail_hook }
end
+ context 'and it returns invalid JSON' do
+ before do
+ result.stub(:status).and_return(1)
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return('This is not JSON')
+ end
+
+ it { should fail_hook /Unable to parse JSON returned by SCSS-Lint/ }
+ end
+
context 'and it returns status code indicating all files were filtered' do
before do
result.stub(:status).and_return(81)
+ result.stub(:stderr).and_return('')
result.stub(:stdout).and_return('')
end
diff --git a/spec/overcommit/hook/pre_commit/semi_standard_spec.rb b/spec/overcommit/hook/pre_commit/semi_standard_spec.rb
index 1e967e4c..cc69025a 100644
--- a/spec/overcommit/hook/pre_commit/semi_standard_spec.rb
+++ b/spec/overcommit/hook/pre_commit/semi_standard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::SemiStandard do
diff --git a/spec/overcommit/hook/pre_commit/shell_check_spec.rb b/spec/overcommit/hook/pre_commit/shell_check_spec.rb
index 22cb9e78..f9fba49c 100644
--- a/spec/overcommit/hook/pre_commit/shell_check_spec.rb
+++ b/spec/overcommit/hook/pre_commit/shell_check_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::ShellCheck do
diff --git a/spec/overcommit/hook/pre_commit/slim_lint_spec.rb b/spec/overcommit/hook/pre_commit/slim_lint_spec.rb
index 7d4e57d1..3485c226 100644
--- a/spec/overcommit/hook/pre_commit/slim_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/slim_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::SlimLint do
diff --git a/spec/overcommit/hook/pre_commit/sorbet_spec.rb b/spec/overcommit/hook/pre_commit/sorbet_spec.rb
new file mode 100644
index 00000000..60f811cb
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/sorbet_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Sorbet do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
+ context 'when Sorbet exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+
+ context 'and it printed a message to stderr' do
+ before do
+ result.stub(:stderr).and_return("No errors! Great job.\n")
+ end
+
+ it { should pass }
+ end
+ end
+
+ context 'when Sorbet exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stderr).and_return(normalize_indent(<<-MSG))
+ sorbet.rb:1: Method `foo` does not exist on `T.class_of(Bar)` https://srb.help/7003
+ 5 | foo 'bar'
+ ^^^
+ Errors: 1
+ MSG
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/sqlint_spec.rb b/spec/overcommit/hook/pre_commit/sqlint_spec.rb
index 11bcec60..bc9edec2 100644
--- a/spec/overcommit/hook/pre_commit/sqlint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/sqlint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Sqlint do
diff --git a/spec/overcommit/hook/pre_commit/standard_spec.rb b/spec/overcommit/hook/pre_commit/standard_spec.rb
index 16150285..c382e61d 100644
--- a/spec/overcommit/hook/pre_commit/standard_spec.rb
+++ b/spec/overcommit/hook/pre_commit/standard_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Standard do
diff --git a/spec/overcommit/hook/pre_commit/stylelint_spec.rb b/spec/overcommit/hook/pre_commit/stylelint_spec.rb
new file mode 100644
index 00000000..68e83f65
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/stylelint_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::Stylelint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.scss file2.scss])
+ end
+
+ context 'when stylelint exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return('')
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when stylelint exits unsucessfully with messages on stdout (stylelint < 16)' do
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:success?).and_return(false)
+ result.stub(:stderr).and_return('')
+ result.stub(:stdout).and_return([
+ 'index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation)',
+ 'form.css: line 10, col 6, error - Expected indentation of 4 spaces (indentation)',
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+
+ it 'extracts lines numbers correctly from output' do
+ expect(subject.run.map(&:line)).to eq([4, 10])
+ end
+ end
+ end
+
+ context 'when stylelint exits unsucessfully with messages on stderr (stylelint >= 16)' do
+ let(:result) { double('result') }
+
+ before do
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:success?).and_return(false)
+ result.stub(:stdout).and_return('')
+ result.stub(:stderr).and_return([
+ 'index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation)',
+ 'form.css: line 10, col 6, error - Expected indentation of 4 spaces (indentation)',
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+
+ it 'extracts lines numbers correctly from output' do
+ expect(subject.run.map(&:line)).to eq([4, 10])
+ end
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/swift_lint_spec.rb b/spec/overcommit/hook/pre_commit/swift_lint_spec.rb
new file mode 100644
index 00000000..d2fb78fd
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/swift_lint_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::SwiftLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.swift file2.swift])
+ end
+
+ context 'when SwiftLint exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when SwiftLint exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return([
+ 'file1.swift:12: warning: message: details'
+ ].join("\n"))
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/terraform_format_spec.rb b/spec/overcommit/hook/pre_commit/terraform_format_spec.rb
new file mode 100644
index 00000000..6897f8c3
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/terraform_format_spec.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::TerraformFormat do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.tf file2.tf])
+ end
+
+ context 'when Terraform exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when Terraform exits unsucessfully' do
+ let(:result_ok) { double('result') }
+ let(:result_bad) { double('result') }
+ let(:cmdline) { %w[terraform fmt -check=true -diff=false] }
+
+ before do
+ result_ok.stub(:success?).and_return(true)
+ result_bad.stub(:success?).and_return(false)
+ subject.stub(:execute).with(cmdline, args: ['file1.tf']).and_return(result_ok)
+ subject.stub(:execute).with(cmdline, args: ['file2.tf']).and_return(result_bad)
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/trailing_whitespace_spec.rb b/spec/overcommit/hook/pre_commit/trailing_whitespace_spec.rb
index c7d8c84a..af94b7f9 100644
--- a/spec/overcommit/hook/pre_commit/trailing_whitespace_spec.rb
+++ b/spec/overcommit/hook/pre_commit/trailing_whitespace_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::TrailingWhitespace do
diff --git a/spec/overcommit/hook/pre_commit/travis_lint_spec.rb b/spec/overcommit/hook/pre_commit/travis_lint_spec.rb
index c0cea2a9..0a6e5624 100644
--- a/spec/overcommit/hook/pre_commit/travis_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/travis_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::TravisLint do
diff --git a/spec/overcommit/hook/pre_commit/ts_lint_spec.rb b/spec/overcommit/hook/pre_commit/ts_lint_spec.rb
new file mode 100644
index 00000000..6cc8e55b
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/ts_lint_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::TsLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[anotherfile.ts])
+ end
+
+ context 'when tslint exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'with no output' do
+ before do
+ result.stub(:stdout).and_return('')
+ end
+
+ it { should pass }
+ end
+ end
+
+ context 'when tslint exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return(
+ 'src/file/anotherfile.ts[298, 1]: exceeds maximum line length of 140'
+ )
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and it reports an error with an "ERROR" severity' do
+ before do
+ result.stub(:stdout).and_return(
+ 'ERROR: src/AccountController.ts[4, 28]: expected call-signature to have a typedef'
+ )
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and it reports an warning' do
+ before do
+ result.stub(:stdout).and_return(
+ 'WARNING: src/AccountController.ts[4, 28]: expected call-signature to have a typedef'
+ )
+ end
+
+ it { should warn }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/vint_spec.rb b/spec/overcommit/hook/pre_commit/vint_spec.rb
index e4d6d504..c884d655 100644
--- a/spec/overcommit/hook/pre_commit/vint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/vint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::Vint do
diff --git a/spec/overcommit/hook/pre_commit/w3c_css_spec.rb b/spec/overcommit/hook/pre_commit/w3c_css_spec.rb
index d69cf484..9ec45800 100644
--- a/spec/overcommit/hook/pre_commit/w3c_css_spec.rb
+++ b/spec/overcommit/hook/pre_commit/w3c_css_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::W3cCss do
diff --git a/spec/overcommit/hook/pre_commit/w3c_html_spec.rb b/spec/overcommit/hook/pre_commit/w3c_html_spec.rb
index 7c3b612f..e517bb34 100644
--- a/spec/overcommit/hook/pre_commit/w3c_html_spec.rb
+++ b/spec/overcommit/hook/pre_commit/w3c_html_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::W3cHtml do
diff --git a/spec/overcommit/hook/pre_commit/xml_lint_spec.rb b/spec/overcommit/hook/pre_commit/xml_lint_spec.rb
index 967e6964..dc48df40 100644
--- a/spec/overcommit/hook/pre_commit/xml_lint_spec.rb
+++ b/spec/overcommit/hook/pre_commit/xml_lint_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::XmlLint do
diff --git a/spec/overcommit/hook/pre_commit/xml_syntax_spec.rb b/spec/overcommit/hook/pre_commit/xml_syntax_spec.rb
index 1a086792..d2a31c05 100644
--- a/spec/overcommit/hook/pre_commit/xml_syntax_spec.rb
+++ b/spec/overcommit/hook/pre_commit/xml_syntax_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
require 'rexml/document'
diff --git a/spec/overcommit/hook/pre_commit/yaml_lint_spec.rb b/spec/overcommit/hook/pre_commit/yaml_lint_spec.rb
new file mode 100644
index 00000000..e5aea475
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/yaml_lint_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::YamlLint do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ let(:applicable_files) { %w[file1.yaml file2.yml] }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(applicable_files)
+ end
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ subject.stub(:execute).with(%w[yamllint --format=parsable --strict], args: applicable_files).
+ and_return(result)
+ end
+
+ context 'and has 2 suggestions for line length' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+file1.yaml:3:81: [error] line too long (253 > 80 characters) (line-length)
+file2.yml:41:81: [error] line too long (261 > 80 characters) (line-length)
+ MSG
+ )
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'and has 1 error and 1 warning' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+file1.yaml:3:81: [error] line too long (253 > 80 characters) (line-length)
+file2.yml:41:81: [warning] missing document start "---" (document-start)
+ MSG
+ )
+ end
+
+ it { should fail_hook }
+ end
+ context 'and has single suggestion for missing file header' do
+ let(:result) do
+ double(
+ success?: false,
+ stdout: <<-MSG
+file1.yaml:1:1: [warning] missing document start "---" (document-start)
+ MSG
+ )
+ end
+
+ it { should warn }
+ end
+
+ context 'and does not have any suggestion' do
+ let(:result) do
+ double(success?: true, stdout: '')
+ end
+
+ it { should pass }
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb b/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb
index 54ae727b..a6f61f2c 100644
--- a/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb
+++ b/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Overcommit::Hook::PreCommit::YamlSyntax do
@@ -20,6 +22,7 @@
context 'when YAML file has errors' do
before do
+ YAML.stub(:load_file).with(staged_file, { aliases: true }).and_raise(ArgumentError)
YAML.stub(:load_file).with(staged_file).and_raise(ArgumentError)
end
diff --git a/spec/overcommit/hook/pre_commit/yard_coverage_spec.rb b/spec/overcommit/hook/pre_commit/yard_coverage_spec.rb
new file mode 100644
index 00000000..d463d25d
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/yard_coverage_spec.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::YardCoverage do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ before do
+ subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb])
+ end
+
+ context 'when yard exits successfully' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return(
+ <<-HEREDOC
+ Files: 72
+ Modules: 12 ( 0 undocumented)
+ Classes: 63 ( 0 undocumented)
+ Constants: 91 ( 0 undocumented)
+ Attributes: 11 ( 0 undocumented)
+ Methods: 264 ( 0 undocumented)
+ 100.0% documented
+ HEREDOC
+ )
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when somehow yard exits a non-stats output' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return(
+ <<-HEREDOC
+ WHATEVER OUTPUT THAT IS NOT YARD STATS ONE
+ HEREDOC
+ )
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should warn }
+ end
+
+ context 'when somehow yard coverage is not a valid value' do
+ before do
+ result = double('result')
+ result.stub(:stdout).and_return(
+ <<-HEREDOC
+ Files: 72
+ Modules: 12 ( 0 undocumented)
+ Classes: 63 ( 0 undocumented)
+ Constants: 91 ( 0 undocumented)
+ Attributes: 11 ( 0 undocumented)
+ Methods: 264 ( 0 undocumented)
+ AAAAAA documented
+ HEREDOC
+ )
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should warn }
+ end
+
+ context 'when yard exits unsucessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'and it reports an error' do
+ before do
+ result.stub(:stdout).and_return(
+ <<-HEREDOC
+ Files: 72
+ Modules: 12 ( 3 undocumented)
+ Classes: 63 ( 15 undocumented)
+ Constants: 91 ( 79 undocumented)
+ Attributes: 11 ( 0 undocumented)
+ Methods: 264 ( 55 undocumented)
+ 65.53% documented
+
+ Undocumented Objects:
+ ApplicationCable (app/channels/application_cable/channel.rb:1)
+ ApplicationCable::Channel (app/channels/application_cable/channel.rb:2)
+ ApplicationCable::Connection (app/channels/application_cable/connection.rb:2)
+ HEREDOC
+ )
+ end
+
+ it { should fail_hook }
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/yarn_check_spec.rb b/spec/overcommit/hook/pre_commit/yarn_check_spec.rb
new file mode 100644
index 00000000..f67fa9b2
--- /dev/null
+++ b/spec/overcommit/hook/pre_commit/yarn_check_spec.rb
@@ -0,0 +1,89 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PreCommit::YarnCheck do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when yarn.lock is ignored' do
+ around do |example|
+ repo do
+ touch 'yarn.lock'
+ echo('yarn.lock', '.gitignore')
+ `git add .gitignore`
+ `git commit -m "Ignore yarn.lock"`
+ example.run
+ end
+ end
+
+ it { should pass }
+ end
+
+ context 'when yarn.lock is not ignored' do
+ let(:result) { double('result') }
+
+ around do |example|
+ repo do
+ example.run
+ end
+ end
+
+ before do
+ result.stub(stderr: stderr)
+ subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]).
+ and_return(double(stdout: ''))
+ subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]).
+ and_return(result)
+ end
+
+ context 'and yarn check reports no errors' do
+ let(:stderr) { '' }
+
+ it { should pass }
+
+ context 'and there was a change to the yarn.lock' do
+ before do
+ subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]) do
+ echo('stuff', 'yarn.lock')
+ double(stderr: '')
+ end
+ end
+
+ it { should fail_hook }
+ end
+ end
+
+ context 'and yarn check contains only warnings' do
+ let(:stderr) do
+ < exclude_remotes }
+ end
+
+ context 'exclude_remotes is nil' do
+ let(:exclude_remotes) { nil }
+
+ it { subject.should == true }
+ end
+
+ context 'exclude_remotes includes the remote' do
+ let(:exclude_remotes) { [remote_name] }
+
+ it { subject.should == false }
+ end
+
+ context 'exclude_remotes does not include the remote' do
+ let(:exclude_remotes) { ['heroku'] }
+
+ it { subject.should == true }
+ end
+ end
+
+ context 'with include_remote_ref_deletions specified' do
+ let(:hook_config) do
+ { 'include_remote_ref_deletions' => include_remote_ref_deletions }
+ end
+ let(:remote_ref_deletion?) { false }
+ let(:include_remote_ref_deletions) { false }
+
+ context 'when remote branch is not being deleted' do
+ let(:remote_ref_deletion?) { false }
+
+ context 'when include_remote_ref_deletions is not specified' do
+ let(:include_remote_ref_deletions) { nil }
+
+ it { subject.should == true }
+ end
+
+ context 'when include_remote_ref_deletions is false' do
+ let(:include_remote_ref_deletions) { false }
+
+ it { subject.should == true }
+ end
+
+ context 'when include_remote_ref_deletions is true' do
+ let(:include_remote_ref_deletions) { true }
+
+ it { subject.should == true }
+ end
+ end
+
+ context 'when remote branch is being deleted' do
+ let(:remote_ref_deletion?) { true }
+
+ context 'when include_remote_ref_deletions is not specified' do
+ let(:include_remote_ref_deletions) { nil }
+
+ it { subject.should == false }
+ end
+
+ context 'when include_remote_ref_deletions is false' do
+ let(:include_remote_ref_deletions) { false }
+
+ it { subject.should == false }
+ end
+
+ context 'when include_remote_ref_deletions is true' do
+ let(:include_remote_ref_deletions) { true }
+
+ it { subject.should == true }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/overcommit/hook/pre_commit/brakeman_spec.rb b/spec/overcommit/hook/pre_push/brakeman_spec.rb
similarity index 84%
rename from spec/overcommit/hook/pre_commit/brakeman_spec.rb
rename to spec/overcommit/hook/pre_push/brakeman_spec.rb
index ed6d0767..c8dad7f0 100644
--- a/spec/overcommit/hook/pre_commit/brakeman_spec.rb
+++ b/spec/overcommit/hook/pre_push/brakeman_spec.rb
@@ -1,14 +1,12 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Overcommit::Hook::PreCommit::Brakeman do
+describe Overcommit::Hook::PrePush::Brakeman do
let(:config) { Overcommit::ConfigurationLoader.default_configuration }
let(:context) { double('context') }
subject { described_class.new(config, context) }
- before do
- subject.stub(:applicable_files).and_return(['my_class.rb'])
- end
-
context 'when brakeman exits successfully' do
before do
result = double('result')
diff --git a/spec/overcommit/hook/pre_push/cargo_test_spec.rb b/spec/overcommit/hook/pre_push/cargo_test_spec.rb
new file mode 100644
index 00000000..a21c96dc
--- /dev/null
+++ b/spec/overcommit/hook/pre_push/cargo_test_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PrePush::CargoTest do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when all tests succeed' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when one test fails' do
+ before do
+ result = double('result')
+ result.stub(:success?).and_return(false)
+ result.stub(stdout: <<-ERRORMSG)
+ running 2 tests
+ test tests::foo ... ok
+ test tests::bar ... FAILED
+
+ failures:
+
+ ---- tests::bar stdout ----
+ thread 'tests::bar' panicked at 'assertion failed: `(left == right)`
+ left: `None`,
+ right: `Some(Bar)`', src/foobar.rs:88:9
+ note: Run with `RUST_BACKTRACE=1` for a backtrace.
+
+
+ failures:
+ tests::bar
+
+ test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out
+ ERRORMSG
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should fail_hook }
+ end
+end
diff --git a/spec/overcommit/hook/pre_push/flutter_test_spec.rb b/spec/overcommit/hook/pre_push/flutter_test_spec.rb
new file mode 100644
index 00000000..a5bce582
--- /dev/null
+++ b/spec/overcommit/hook/pre_push/flutter_test_spec.rb
@@ -0,0 +1,167 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Overcommit::Hook::PrePush::FlutterTest do
+ let(:config) { Overcommit::ConfigurationLoader.default_configuration }
+ let(:context) { double('context') }
+ subject { described_class.new(config, context) }
+
+ context 'when flutter test exits successfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(true)
+ subject.stub(:execute).and_return(result)
+ end
+
+ it { should pass }
+ end
+
+ context 'when flutter test exits unsuccessfully' do
+ let(:result) { double('result') }
+
+ before do
+ result.stub(:success?).and_return(false)
+ subject.stub(:execute).and_return(result)
+ end
+
+ context 'with a runtime error' do
+ before do
+ result.stub(stdout: '', stderr: <<-MSG)
+ 0:03 +0: Counter increments smoke test
+ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
+ The following _Exception was thrown running a test:
+ Exception
+
+ When the exception was thrown, this was the stack:
+ #0 main. (file:///Users/user/project/test/widget_test.dart:18:5)
+
+ #1 main. (file:///Users/user/project/test/widget_test.dart)
+ #2 testWidgets.. (package:flutter_test/src/widget_tester.dart:146:29)
+
+ #3 testWidgets.. (package:flutter_test/src/widget_tester.dart)
+ #4 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:784:19)
+
+ #7 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:764:14)
+ #8 AutomatedTestWidgetsFlutterBinding.runTest. (package:flutter_test/src/binding.dart:1173:24)
+ #9 FakeAsync.run.. (package:fake_async/fake_async.dart:178:54)
+ #14 withClock (package:clock/src/default.dart:48:10)
+ #15 FakeAsync.run. (package:fake_async/fake_async.dart:178:22)
+ #20 FakeAsync.run (package:fake_async/fake_async.dart:178:7)
+ #21 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1170:15)
+ #22 testWidgets. (package:flutter_test/src/widget_tester.dart:138:24)
+ #23 Declarer.test.. (package:test_api/src/backend/declarer.dart:175:19)
+
+ #24 Declarer.test.. (package:test_api/src/backend/declarer.dart)
+ #29 Declarer.test. (package:test_api/src/backend/declarer.dart:173:13)
+ #30 Invoker.waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:231:15)
+ #35 Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:228:5)
+ #36 Invoker._onRun... (package:test_api/src/backend/invoker.dart:383:17)
+
+ #37 Invoker._onRun... (package:test_api/src/backend/invoker.dart)
+ #42 Invoker._onRun.. (package:test_api/src/backend/invoker.dart:370:9)
+ #43 Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:415:15)
+ #44 Invoker._onRun. (package:test_api/src/backend/invoker.dart:369:7)
+ #51 Invoker._onRun (package:test_api/src/backend/invoker.dart:368:11)
+ #52 LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11)
+ #53 RemoteListener._runLiveTest. (package:test_api/src/remote_listener.dart:256:16)
+ #58 RemoteListener._runLiveTest (package:test_api/src/remote_listener.dart:255:5)
+ #59 RemoteListener._serializeTest. (package:test_api/src/remote_listener.dart:208:7)
+ #77 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12)
+ #78 new _MultiChannel. (package:stream_channel/src/multi_channel.dart:159:31)
+ #82 CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11)
+ #116 new _WebSocketImpl._fromSocket. (dart:_http/websocket_impl.dart:1145:21)
+ #124 _WebSocketProtocolTransformer._messageFrameEnd (dart:_http/websocket_impl.dart:338:23)
+ #125 _WebSocketProtocolTransformer.add (dart:_http/websocket_impl.dart:232:46)
+ #135 _Socket._onData (dart:io-patch/socket_patch.dart:2044:41)
+ #144 new _RawSocket. (dart:io-patch/socket_patch.dart:1580:33)
+ #145 _NativeSocket.issueReadEvent.issue (dart:io-patch/socket_patch.dart:1076:14)
+ (elided 111 frames from dart:async and package:stack_trace)
+
+ The test description was:
+ Counter increments smoke test
+ ════════════════════════════════════════════════════════════════════════════════════════════════════
+ 00:03 +0 -1: Counter increments smoke test [E]
+ Test failed. See exception logs above.
+ The test description was: Counter increments smoke test
+
+ 00:03 +0 -1: Some tests failed.
+ MSG
+ end
+
+ it { should fail_hook }
+ end
+
+ context 'with a test failure' do
+ before do
+ result.stub(stderr: '', stdout: <<-MSG)
+ 00:02 +0: Counter increments smoke test
+ ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
+ The following TestFailure object was thrown running a test:
+ Expected: exactly one matching node in the widget tree
+ Actual: _TextFinder:
+ Which: means none were found but one was expected
+
+ When the exception was thrown, this was the stack:
+ #4 main. (file:///Users/user/project/test/widget_test.dart:19:5)
+
+ #5 main. (file:///Users/user/project/test/widget_test.dart)
+ #6 testWidgets.. (package:flutter_test/src/widget_tester.dart:146:29)
+
+ #7 testWidgets.. (package:flutter_test/src/widget_tester.dart)
+ #8 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:784:19)
+
+ #11 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:764:14)
+ #12 AutomatedTestWidgetsFlutterBinding.runTest. (package:flutter_test/src/binding.dart:1173:24)
+ #13 FakeAsync.run.. (package:fake_async/fake_async.dart:178:54)
+ #18 withClock (package:clock/src/default.dart:48:10)
+ #19 FakeAsync.run. (package:fake_async/fake_async.dart:178:22)
+ #24 FakeAsync.run (package:fake_async/fake_async.dart:178:7)
+ #25 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1170:15)
+ #26 testWidgets. (package:flutter_test/src/widget_tester.dart:138:24)
+ #27 Declarer.test.. (package:test_api/src/backend/declarer.dart:175:19)
+ ]