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/.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 56197f7d..1b4e7ad7 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,9 @@ +inherit_from: .rubocop_todo.yml + AllCops: - TargetRubyVersion: 2.4 + TargetRubyVersion: 2.6 + NewCops: disable + SuggestExtensions: false Layout/ClosingParenthesisIndentation: Enabled: false @@ -14,12 +18,15 @@ Layout/EndOfLine: Layout/FirstParameterIndentation: Enabled: false -Layout/IndentArray: +Layout/FirstArrayElementIndentation: Enabled: false -Layout/IndentHeredoc: +Layout/HeredocIndentation: Enabled: false +Layout/LineLength: + Max: 100 + Layout/MultilineMethodCallIndentation: Enabled: false @@ -48,9 +55,6 @@ Metrics/AbcSize: Metrics/BlockLength: Enabled: false -Metrics/LineLength: - Max: 100 - Metrics/MethodLength: Max: 20 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 f12366a7..00000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: ruby - -cache: bundler - -rvm: - - 2.4 - - 2.5 - - 2.6 - -before_script: - - git config --global user.email "travis@travis.ci" - - git config --global user.name "Travis CI" - -before_install: - - gem update --system - - gem install bundler - -script: - - bundle exec rspec - - bundle exec overcommit --sign - - bundle exec overcommit --sign pre-commit - - bundle exec overcommit --run diff --git a/CHANGELOG.md b/CHANGELOG.md index 1711cb54..ad1cb477 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,107 @@ # Overcommit Changelog +## 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 diff --git a/Gemfile b/Gemfile index 961e5d5a..49dc7923 100644 --- a/Gemfile +++ b/Gemfile @@ -8,10 +8,14 @@ gemspec gem 'rspec', '~> 3.0' -# Generate coverage information in Travis builds -gem 'coveralls', '~> 0.8' +gem 'simplecov', '~> 0.21.0' +gem 'simplecov-lcov', '~> 0.8.0' -# Pin RuboCop for Travis builds. -gem 'rubocop', '0.54.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/README.md b/README.md index af718032..a0d46726 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,8 @@ [![Gem Version](https://badge.fury.io/rb/overcommit.svg)](https://badge.fury.io/rb/overcommit) -[![Build Status](https://travis-ci.org/sds/overcommit.svg?branch=master)](https://travis-ci.org/sds/overcommit) -[![Windows Build Status](https://ci.appveyor.com/api/projects/status/umui038jlm597ryf?svg=true)](https://ci.appveyor.com/project/sds/overcommit) -[![Coverage Status](https://coveralls.io/repos/github/sds/overcommit/badge.svg?branch=master)](https://coveralls.io/github/sds/overcommit?branch=master) +[![Build Status](https://github.com/sds/overcommit/actions/workflows/tests.yml/badge.svg?branch=main)](https://github.com/sds/overcommit/actions/workflows/tests.yml/badge.svg?branch=main) +[![Coverage Status](https://coveralls.io/repos/github/sds/overcommit/badge.svg?branch=main)](https://coveralls.io/github/sds/overcommit?branch=main) [![Maintainability](https://api.codeclimate.com/v1/badges/5da42f7f365e5fef6b4c/maintainability)](https://codeclimate.com/github/sds/overcommit/maintainability) -[![Inline docs](http://inch-ci.org/github/sds/overcommit.svg?branch=master)](http://inch-ci.org/github/sds/overcommit) +[![Inline docs](http://inch-ci.org/github/sds/overcommit.svg?branch=main)](http://inch-ci.org/github/sds/overcommit)

Overcommit Logo @@ -12,59 +11,60 @@ `overcommit` is a tool to manage and configure [Git hooks](http://git-scm.com/book/en/Customizing-Git-Git-Hooks). -![Demonstration](https://sds.github.io/overcommit/overcommit.gif) - 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 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) - * [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) +- [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 on both \*nix and Windows: - -* Ruby 2.4+ +This project aims to support the following Ruby runtimes on \*nix (and best effort on Windows): -### Windows - -If you are using Overcommit on **Windows**, make sure you include the `ffi` gem in your -list of dependencies. Overcommit does not include the `ffi` gem by default since it -significantly increases the install time for non-Windows platforms. +* 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 +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 @@ -79,9 +79,9 @@ available during your hook runs. `overcommit` is installed via [RubyGems](https://rubygems.org/). It is strongly recommended that your environment support running `gem install` without -requiring `sudo` privileges. Using a Ruby version manager like -[`rbenv`](https://github.com/rbenv/rbenv/) or [`rvm`](https://rvm.io/) can help -here. +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: @@ -131,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 @@ -211,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 @@ -500,7 +505,9 @@ issue](https://github.com/sds/overcommit/issues/238) for more details. * [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) @@ -558,6 +565,7 @@ issue](https://github.com/sds/overcommit/issues/238) for more details. * [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) @@ -581,10 +589,12 @@ 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) diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index fcb90b95..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,61 +0,0 @@ -version: "{build}" - -clone_depth: 1 - -environment: - matrix: - - RUBY_FOLDER_VERSION: "24-x64" - - RUBY_FOLDER_VERSION: "25-x64" - - RUBY_FOLDER_VERSION: "26-x64" -matrix: - fast_finish: true - -cache: - # Cache installed gems unless dependencies change - - vendor\bundle -> 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-document - - gem --version - - where gem - - # Install latest Bundler to work around version issues - - gem install bundler:'< 2' --force - - where bundler - - # Install ruby dependencies - - bundle install --retry 3 --path vendor\bundle - -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.autocrlf true - - git config --global core.safecrlf false - -test_script: - - bundle exec rspec --tty --backtrace --color - - bundle exec overcommit --sign - - bundle exec overcommit --sign pre-commit - - bundle exec overcommit --run diff --git a/bin/overcommit b/bin/overcommit index ebcf56a4..d54d179f 100755 --- a/bin/overcommit +++ b/bin/overcommit @@ -18,15 +18,16 @@ if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil ensure $stderr = old_stderr end - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 => ex + 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 ex.message =~ /already activated/i + raise unless e.message =~ /already activated/i + exec('bundle', 'exec', $0, *ARGV) end end diff --git a/config/default.yml b/config/default.yml index 74503e8e..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 @@ -266,6 +270,14 @@ PreCommit: 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: 'Analyze with dogma' @@ -275,6 +287,13 @@ PreCommit: - '**/*.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: 'Analyze with ESLint' @@ -338,7 +357,7 @@ PreCommit: keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen'] GoFmt: - enabled: true + enabled: false description: 'Fix with go fmt' required_executable: 'go' command: ['go', 'fmt'] @@ -521,6 +540,16 @@ PreCommit: 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' @@ -684,6 +713,11 @@ PreCommit: install_command: 'pip install restructuredtext_lint' include: '**/*.rst' + RSpec: + enabled: false + description: 'Run tests with Rspec' + required_executable: 'rspec' + RuboCop: enabled: false description: 'Analyze with RuboCop' @@ -765,6 +799,14 @@ PreCommit: 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: 'Analyze with sqlint' @@ -885,7 +927,7 @@ PreCommit: enabled: false description: 'Analyze with YAMLlint' required_executable: 'yamllint' - flags: ['--format=parsable'] + flags: ['--format=parsable', '--strict'] install_command: 'pip install yamllint' include: - '**/*.yaml' @@ -1237,9 +1279,14 @@ PrepareCommitMsg: ReplaceBranch: enabled: false description: 'Prepends the commit message with text based on the branch name' - branch_pattern: '\A.*\w+[-_](\d+).*\z' + branch_pattern: '\A(\d+)-(\w+).*\z' replacement_text: '[#\1]' - skipped_commit_types: ['message', 'template', 'merge', 'squash'] + 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 @@ -1264,6 +1311,12 @@ PrePush: 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' @@ -1290,6 +1343,12 @@ PrePush: 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' @@ -1310,6 +1369,12 @@ PrePush: 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' diff --git a/lib/overcommit/cli.rb b/lib/overcommit/cli.rb index 54a3f9b7..dafc545a 100644 --- a/lib/overcommit/cli.rb +++ b/lib/overcommit/cli.rb @@ -6,9 +6,10 @@ 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 = {} @@ -28,12 +29,14 @@ def run sign when :run_all run_all + when :diff + diff end - rescue Overcommit::Exceptions::ConfigurationSignatureChanged => ex - puts ex + rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e exit 78 # EX_CONFIG - rescue Overcommit::Exceptions::HookContextLoadError => ex - puts ex + rescue Overcommit::Exceptions::HookContextLoadError => e + puts e exit 64 # EX_USAGE end @@ -45,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 @@ -94,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 @@ -123,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 @@ -199,7 +206,20 @@ 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(config, log, context) diff --git a/lib/overcommit/command_splitter.rb b/lib/overcommit/command_splitter.rb index 32c5299c..c334b6f9 100644 --- a/lib/overcommit/command_splitter.rb +++ b/lib/overcommit/command_splitter.rb @@ -107,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 ae3efa50..72086457 100644 --- a/lib/overcommit/configuration.rb +++ b/lib/overcommit/configuration.rb @@ -5,7 +5,7 @@ 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 diff --git a/lib/overcommit/configuration_loader.rb b/lib/overcommit/configuration_loader.rb index f884c41d..8fd68a01 100644 --- a/lib/overcommit/configuration_loader.rb +++ b/lib/overcommit/configuration_loader.rb @@ -24,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 @@ -50,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 @@ -61,9 +68,11 @@ 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? } verify_signatures(config) @@ -72,10 +81,10 @@ def load_file(file) config rescue Overcommit::Exceptions::ConfigurationSignatureChanged raise - rescue StandardError => error + 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/constants.rb b/lib/overcommit/constants.rb index dd027932..e7c19728 100644 --- a/lib/overcommit/constants.rb +++ b/lib/overcommit/constants.rb @@ -4,6 +4,7 @@ module Overcommit HOME = File.expand_path(File.join(File.dirname(__FILE__), '..', '..')).freeze CONFIG_FILE_NAME = '.overcommit.yml' + LOCAL_CONFIG_FILE_NAME = '.local-overcommit.yml' HOOK_DIRECTORY = File.join(HOME, 'lib', 'overcommit', 'hook').freeze diff --git a/lib/overcommit/exceptions.rb b/lib/overcommit/exceptions.rb index 3246c567..050982fb 100644 --- a/lib/overcommit/exceptions.rb +++ b/lib/overcommit/exceptions.rb @@ -1,52 +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 < StandardError; end + 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 d2ad2ad6..c1243861 100644 --- a/lib/overcommit/git_config.rb +++ b/lib/overcommit/git_config.rb @@ -16,7 +16,8 @@ def comment_character def hooks_path path = `git config --get core.hooksPath`.chomp return File.join(Overcommit::Utils.git_dir, 'hooks') if path.empty? - File.absolute_path(path, Dir.pwd) + + File.expand_path(path, Dir.pwd) end end end diff --git a/lib/overcommit/git_repo.rb b/lib/overcommit/git_repo.rb index bd659eb7..1af73e81 100644 --- a/lib/overcommit/git_repo.rb +++ b/lib/overcommit/git_repo.rb @@ -14,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 @@ -22,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` @@ -109,15 +109,16 @@ def modified_files(options) # @return [Array] list of absolute file paths def list_files(paths = [], options = {}) ref = options[:ref] || 'HEAD' - path_list = - if OS.windows? - paths = paths.map { |path| path.gsub('"', '""') } - paths.empty? ? '' : "\"#{paths.join('" "')}\"" - else - paths.shelljoin - end - `git ls-tree --name-only #{ref} #{path_list}`. - 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 @@ -261,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. diff --git a/lib/overcommit/hook/base.rb b/lib/overcommit/hook/base.rb index 37696d08..3c2eaeb3 100644 --- a/lib/overcommit/hook/base.rb +++ b/lib/overcommit/hook/base.rb @@ -228,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? diff --git a/lib/overcommit/hook/commit_msg/base.rb b/lib/overcommit/hook/commit_msg/base.rb index 2b46a66e..f99f53fc 100644 --- a/lib/overcommit/hook/commit_msg/base.rb +++ b/lib/overcommit/hook/commit_msg/base.rb @@ -9,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/spell_check.rb b/lib/overcommit/hook/commit_msg/spell_check.rb index 241df378..ec6c4d4a 100644 --- a/lib/overcommit/hook/commit_msg/spell_check.rb +++ b/lib/overcommit/hook/commit_msg/spell_check.rb @@ -9,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 011e7e2a..52de3bd7 100644 --- a/lib/overcommit/hook/commit_msg/text_width.rb +++ b/lib/overcommit/hook/commit_msg/text_width.rb @@ -32,7 +32,7 @@ def find_errors_in_subject(subject) min_subject_width = config['min_subject_width'] if subject.length < min_subject_width @errors << "Commit message subject must be >= #{min_subject_width} characters" - return + nil end end @@ -41,7 +41,7 @@ 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" diff --git a/lib/overcommit/hook/post_checkout/base.rb b/lib/overcommit/hook/post_checkout/base.rb index 842e3154..78798b9e 100644 --- a/lib/overcommit/hook/post_checkout/base.rb +++ b/lib/overcommit/hook/post_checkout/base.rb @@ -16,6 +16,7 @@ def skip_file_checkout? def enabled? return false if file_checkout? && skip_file_checkout? + super end end diff --git a/lib/overcommit/hook/post_commit/git_guilt.rb b/lib/overcommit/hook/post_commit/git_guilt.rb index 41980425..b4f1ead8 100644 --- a/lib/overcommit/hook/post_commit/git_guilt.rb +++ b/lib/overcommit/hook/post_commit/git_guilt.rb @@ -5,12 +5,13 @@ module Overcommit::Hook::PostCommit # # @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/pre_commit/author_name.rb b/lib/overcommit/hook/pre_commit/author_name.rb index 63d4243a..fc89792a 100644 --- a/lib/overcommit/hook/pre_commit/author_name.rb +++ b/lib/overcommit/hook/pre_commit/author_name.rb @@ -12,9 +12,9 @@ def run 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" \ + "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 diff --git a/lib/overcommit/hook/pre_commit/bundle_audit.rb b/lib/overcommit/hook/pre_commit/bundle_audit.rb index 465cc3c8..1f9f24c7 100644 --- a/lib/overcommit/hook/pre_commit/bundle_audit.rb +++ b/lib/overcommit/hook/pre_commit/bundle_audit.rb @@ -17,7 +17,7 @@ def run if result.success? :pass else - return [:warn, result.stdout] + [:warn, result.stdout] end end end diff --git a/lib/overcommit/hook/pre_commit/bundle_check.rb b/lib/overcommit/hook/pre_commit/bundle_check.rb index 10d30d2c..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' + LOCK_FILE = File.basename(ENV['BUNDLE_GEMFILE'] || 'Gemfile') + '.lock' def run # Ignore if Gemfile.lock is not tracked by git diff --git a/lib/overcommit/hook/pre_commit/chamber_compare.rb b/lib/overcommit/hook/pre_commit/chamber_compare.rb index f0c4f146..b3e0366c 100644 --- a/lib/overcommit/hook/pre_commit/chamber_compare.rb +++ b/lib/overcommit/hook/pre_commit/chamber_compare.rb @@ -14,11 +14,11 @@ def run next unless second result = execute( - command, - args: [ - "--first=#{first.join(' ')}", - "--second=#{second.join(' ')}", - ], + command, + args: [ + "--first=#{first.join(' ')}", + "--second=#{second.join(' ')}", + ], ) unless result.stdout.empty? diff --git a/lib/overcommit/hook/pre_commit/chamber_security.rb b/lib/overcommit/hook/pre_commit/chamber_security.rb index b47672df..c639fbf8 100644 --- a/lib/overcommit/hook/pre_commit/chamber_security.rb +++ b/lib/overcommit/hook/pre_commit/chamber_security.rb @@ -9,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/coffee_lint.rb b/lib/overcommit/hook/pre_commit/coffee_lint.rb index 7e68b40d..57c80a58 100644 --- a/lib/overcommit/hook/pre_commit/coffee_lint.rb +++ b/lib/overcommit/hook/pre_commit/coffee_lint.rb @@ -10,7 +10,7 @@ class CoffeeLint < Base ,(?\d*),\d* ,(?\w+) ,(?.+)$ - /x + /x.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('w') ? :warning : :error diff --git a/lib/overcommit/hook/pre_commit/css_lint.rb b/lib/overcommit/hook/pre_commit/css_lint.rb index 7ca82283..b110d074 100644 --- a/lib/overcommit/hook/pre_commit/css_lint.rb +++ b/lib/overcommit/hook/pre_commit/css_lint.rb @@ -9,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/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 ebbdd112..240d749d 100644 --- a/lib/overcommit/hook/pre_commit/es_lint.rb +++ b/lib/overcommit/hook/pre_commit/es_lint.rb @@ -19,20 +19,17 @@ module Overcommit::Hook::PreCommit # @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(/Warning|Error/) + 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( - messages, - /^(?(?:\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/fasterer.rb b/lib/overcommit/hook/pre_commit/fasterer.rb index dc584368..871be80f 100644 --- a/lib/overcommit/hook/pre_commit/fasterer.rb +++ b/lib/overcommit/hook/pre_commit/fasterer.rb @@ -12,7 +12,7 @@ def run if extract_offense_num(output) == 0 :pass else - return [:warn, output] + [:warn, output] end end diff --git a/lib/overcommit/hook/pre_commit/foodcritic.rb b/lib/overcommit/hook/pre_commit/foodcritic.rb index 98ac9873..b40adfbe 100644 --- a/lib/overcommit/hook/pre_commit/foodcritic.rb +++ b/lib/overcommit/hook/pre_commit/foodcritic.rb @@ -102,7 +102,7 @@ def run if result.success? :pass else - return [:warn, result.stderr + result.stdout] + [:warn, result.stderr + result.stdout] end end @@ -137,12 +137,14 @@ def modified_cookbooks_args 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 diff --git a/lib/overcommit/hook/pre_commit/hlint.rb b/lib/overcommit/hook/pre_commit/hlint.rb index 1ac2775c..a52d1b62 100644 --- a/lib/overcommit/hook/pre_commit/hlint.rb +++ b/lib/overcommit/hook/pre_commit/hlint.rb @@ -10,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 e8cb4a68..ddbe37d1 100644 --- a/lib/overcommit/hook/pre_commit/html_hint.rb +++ b/lib/overcommit/hook/pre_commit/html_hint.rb @@ -14,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 b4a2d89d..2667e2c1 100644 --- a/lib/overcommit/hook/pre_commit/html_tidy.rb +++ b/lib/overcommit/hook/pre_commit/html_tidy.rb @@ -10,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/java_checkstyle.rb b/lib/overcommit/hook/pre_commit/java_checkstyle.rb index 4961eb12..5d627a64 100644 --- a/lib/overcommit/hook/pre_commit/java_checkstyle.rb +++ b/lib/overcommit/hook/pre_commit/java_checkstyle.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @see http://checkstyle.sourceforge.net/ class JavaCheckstyle < Base - MESSAGE_REGEX = /^(\[(?[^\]]+)\]\s+)?(?(?:\w:)?[^:]+):(?\d+)/ + MESSAGE_REGEX = /^(\[(?[^\]]+)\]\s+)?(?(?:\w:)?[^:]+):(?\d+)/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| %w[WARN INFO].include?(type.to_s) ? :warning : :error diff --git a/lib/overcommit/hook/pre_commit/js_lint.rb b/lib/overcommit/hook/pre_commit/js_lint.rb index 3b6b8b87..956dfd2c 100644 --- a/lib/overcommit/hook/pre_commit/js_lint.rb +++ b/lib/overcommit/hook/pre_commit/js_lint.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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/jsl.rb b/lib/overcommit/hook/pre_commit/jsl.rb index 38f8a0cf..98eb7175 100644 --- a/lib/overcommit/hook/pre_commit/jsl.rb +++ b/lib/overcommit/hook/pre_commit/jsl.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @see http://www.javascriptlint.com/ class Jsl < Base - MESSAGE_REGEX = /(?(?:\w:)?.+)\((?\d+)\):(?[^:]+)/ + MESSAGE_REGEX = /(?(?:\w:)?.+)\((?\d+)\):(?[^:]+)/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.match?(/warning/) ? :warning : :error diff --git a/lib/overcommit/hook/pre_commit/json_syntax.rb b/lib/overcommit/hook/pre_commit/json_syntax.rb index 04972b91..bd162f7d 100644 --- a/lib/overcommit/hook/pre_commit/json_syntax.rb +++ b/lib/overcommit/hook/pre_commit/json_syntax.rb @@ -7,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 index 8e80a581..4b81ed21 100644 --- a/lib/overcommit/hook/pre_commit/kt_lint.rb +++ b/lib/overcommit/hook/pre_commit/kt_lint.rb @@ -4,7 +4,7 @@ module Overcommit::Hook::PreCommit # Runs `ktlint` against modified Kotlin files. # @see https://github.com/shyiko/ktlint class KtLint < Base - MESSAGE_REGEX = /((?[^:]+):(?\d+):(\d+):(?.+))/ + MESSAGE_REGEX = /((?[^:]+):(?\d+):(\d+):(?.+))/.freeze def run result = execute(command, args: applicable_files) diff --git a/lib/overcommit/hook/pre_commit/license_finder.rb b/lib/overcommit/hook/pre_commit/license_finder.rb index fc3739fe..e2b8611b 100644 --- a/lib/overcommit/hook/pre_commit/license_finder.rb +++ b/lib/overcommit/hook/pre_commit/license_finder.rb @@ -7,6 +7,7 @@ class LicenseFinder < Base def run result = execute(command) return :pass if result.success? + output = result.stdout + result.stderr [:fail, output] end diff --git a/lib/overcommit/hook/pre_commit/line_endings.rb b/lib/overcommit/hook/pre_commit/line_endings.rb index 8f8eb9c7..ab66001b 100644 --- a/lib/overcommit/hook/pre_commit/line_endings.rb +++ b/lib/overcommit/hook/pre_commit/line_endings.rb @@ -12,14 +12,14 @@ def run file = File.open(file_name) begin messages += check_file(file, file_name) - rescue ArgumentError => ex + 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}:#{ex.message}" + "#{file_name}:#{file.lineno}:#{e.message}" ) end end @@ -59,6 +59,7 @@ def offending_files i = info.split.first next if i == 'l/-text' # ignore binary files next if i == "l/#{eol}" + path end.compact end 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 cff43bbe..b681ded0 100644 --- a/lib/overcommit/hook/pre_commit/nginx_test.rb +++ b/lib/overcommit/hook/pre_commit/nginx_test.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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/php_cs.rb b/lib/overcommit/hook/pre_commit/php_cs.rb index 8e837406..41d2f2b4 100644 --- a/lib/overcommit/hook/pre_commit/php_cs.rb +++ b/lib/overcommit/hook/pre_commit/php_cs.rb @@ -4,7 +4,7 @@ module Overcommit::Hook::PreCommit # Runs `phpcs` against any modified PHP files. class PhpCs < Base # Parse `phpcs` csv mode output - MESSAGE_REGEX = /^\"(?.+)\",(?\d+),\d+,(?.+),\"(?.+)\"/ + MESSAGE_REGEX = /^\"(?.+)\",(?\d+),\d+,(?.+),\"(?.+)\"/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| 'error'.include?(type) ? :error : :warning end diff --git a/lib/overcommit/hook/pre_commit/php_cs_fixer.rb b/lib/overcommit/hook/pre_commit/php_cs_fixer.rb index 02f8932e..87e63744 100644 --- a/lib/overcommit/hook/pre_commit/php_cs_fixer.rb +++ b/lib/overcommit/hook/pre_commit/php_cs_fixer.rb @@ -3,7 +3,7 @@ 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+)?\))?/ + MESSAGE_REGEX = /\s+\d+\)\s+(?.*\.php)(?\s+\(\w+(?:,\s+)?\))?/.freeze def run messages = [] diff --git a/lib/overcommit/hook/pre_commit/php_lint.rb b/lib/overcommit/hook/pre_commit/php_lint.rb index 98582c3c..f2ba2f24 100644 --- a/lib/overcommit/hook/pre_commit/php_lint.rb +++ b/lib/overcommit/hook/pre_commit/php_lint.rb @@ -4,10 +4,10 @@ module Overcommit::Hook::PreCommit # Runs `php -l` against any modified PHP files. class PhpLint < Base # Sample String - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength # PHP Parse error: syntax error, unexpected 'require_once' (T_REQUIRE_ONCE) in site/sumo.php on line 12 - # rubocop:enable Metrics/LineLength - MESSAGE_REGEX = /^(?.+)\:\s+(?.+) in (?.+) on line (?\d+)/ + # rubocop:enable Layout/LineLength + MESSAGE_REGEX = /^(?.+)\:\s+(?.+) in (?.+) on line (?\d+)/.freeze def run # A list of error messages diff --git a/lib/overcommit/hook/pre_commit/php_stan.rb b/lib/overcommit/hook/pre_commit/php_stan.rb index d9971ff0..9b8550f7 100644 --- a/lib/overcommit/hook/pre_commit/php_stan.rb +++ b/lib/overcommit/hook/pre_commit/php_stan.rb @@ -8,7 +8,7 @@ module Overcommit::Hook::PreCommit # https://github.com/phpstan/phpstan/issues/239 # https://gist.github.com/edmondscommerce/89695c9cd2584fefdf540fb1c528d2c2 class PhpStan < Base - MESSAGE_REGEX = /^(?.+)\:(?\d+)\:(?.+)/ + MESSAGE_REGEX = /^(?.+)\:(?\d+)\:(?.+)/.freeze def run messages = [] diff --git a/lib/overcommit/hook/pre_commit/puppet_lint.rb b/lib/overcommit/hook/pre_commit/puppet_lint.rb index c67c248a..2ddf3a10 100644 --- a/lib/overcommit/hook/pre_commit/puppet_lint.rb +++ b/lib/overcommit/hook/pre_commit/puppet_lint.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 index b020b79c..311a09c3 100644 --- a/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb +++ b/lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb @@ -8,7 +8,7 @@ module Overcommit::Hook::PreCommit # @see https://voxpupuli.org/blog/2014/11/06/linting-metadata-json/ # class PuppetMetadataJsonLint < Base - MESSAGE_REGEX = /\((?.*)\).*/ + MESSAGE_REGEX = /\((?.*)\).*/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type == 'WARN' ? :warning : :error diff --git a/lib/overcommit/hook/pre_commit/pyflakes.rb b/lib/overcommit/hook/pre_commit/pyflakes.rb index 871f0594..ff7824c4 100644 --- a/lib/overcommit/hook/pre_commit/pyflakes.rb +++ b/lib/overcommit/hook/pre_commit/pyflakes.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 1ee92c9a..526bee2d 100644 --- a/lib/overcommit/hook/pre_commit/pylint.rb +++ b/lib/overcommit/hook/pre_commit/pylint.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 0e03bdf9..d506cf62 100644 --- a/lib/overcommit/hook/pre_commit/python_flake8.rb +++ b/lib/overcommit/hook/pre_commit/python_flake8.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 bda0101f..7c3ba79a 100644 --- a/lib/overcommit/hook/pre_commit/rails_best_practices.rb +++ b/lib/overcommit/hook/pre_commit/rails_best_practices.rb @@ -7,7 +7,7 @@ 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, args: applicable_files) 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 da0951bf..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 @@ -6,7 +6,7 @@ module Overcommit::Hook::PreCommit # 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? && non_zero_schema_version? @@ -18,7 +18,7 @@ def run # rubocop:disable CyclomaticComplexity, PerceivedComplexity # their username. latest_version = migration_files.map do |file| File.basename(file)[/\d+/] - end.sort.last + end.max up_to_date = schema.include?(latest_version) @@ -34,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} @@ -47,7 +53,7 @@ def schema_files end def schema - @schema ||= schema_files.map { |file| File.read(file) }.join + @schema ||= schema_files.map { |file| File.read(file, **(encoding || {})) }.join @schema.tr('_', '') end diff --git a/lib/overcommit/hook/pre_commit/rst_lint.rb b/lib/overcommit/hook/pre_commit/rst_lint.rb index 15f93584..5647508d 100644 --- a/lib/overcommit/hook/pre_commit/rst_lint.rb +++ b/lib/overcommit/hook/pre_commit/rst_lint.rb @@ -7,7 +7,7 @@ module Overcommit::Hook::PreCommit class RstLint < Base MESSAGE_REGEX = / ^(?INFO|WARNING|ERROR|SEVERE)(?(?:\w:)?[^:]+):(?\d+)\s(?.+) - /x + /x.freeze def run result = execute(command, args: applicable_files) diff --git a/lib/overcommit/hook/pre_commit/scalariform.rb b/lib/overcommit/hook/pre_commit/scalariform.rb index 6b766dd0..63bf5e6c 100644 --- a/lib/overcommit/hook/pre_commit/scalariform.rb +++ b/lib/overcommit/hook/pre_commit/scalariform.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 55c6dc4f..6494521d 100644 --- a/lib/overcommit/hook/pre_commit/scalastyle.rb +++ b/lib/overcommit/hook/pre_commit/scalastyle.rb @@ -10,7 +10,7 @@ class Scalastyle < Base file=(?(?:\w:)?.+)\s message=.+\s* (line=(?\d+))? - /x + /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 6d279cec..1bbaba93 100644 --- a/lib/overcommit/hook/pre_commit/scss_lint.rb +++ b/lib/overcommit/hook/pre_commit/scss_lint.rb @@ -18,9 +18,9 @@ def run begin collect_lint_messages(JSON.parse(result.stdout)) - rescue JSON::ParserError => ex - return :fail, "Unable to parse JSON returned by SCSS-Lint: #{ex.message}\n" \ - "STDOUT: #{result.stdout}\nSTDERR: #{result.stderr}" + rescue JSON::ParserError => e + [:fail, "Unable to parse JSON returned by SCSS-Lint: #{e.message}\n" \ + "STDOUT: #{result.stdout}\nSTDERR: #{result.stderr}"] end end diff --git a/lib/overcommit/hook/pre_commit/semi_standard.rb b/lib/overcommit/hook/pre_commit/semi_standard.rb index ce40119b..f6746d2f 100644 --- a/lib/overcommit/hook/pre_commit/semi_standard.rb +++ b/lib/overcommit/hook/pre_commit/semi_standard.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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/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 6568a987..2e5c5a88 100644 --- a/lib/overcommit/hook/pre_commit/sqlint.rb +++ b/lib/overcommit/hook/pre_commit/sqlint.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 4d2ee277..7b70ab2c 100644 --- a/lib/overcommit/hook/pre_commit/standard.rb +++ b/lib/overcommit/hook/pre_commit/standard.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 index 403c5e27..aae26f08 100644 --- a/lib/overcommit/hook/pre_commit/stylelint.rb +++ b/lib/overcommit/hook/pre_commit/stylelint.rb @@ -8,12 +8,13 @@ class Stylelint < Base # example of output: # index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation) - MESSAGE_REGEX = /^(?.+):\D*(?\d+).*$/ + MESSAGE_REGEX = /^(?[^:]+):\D*(?\d+).*$/.freeze def run result = execute(command, args: applicable_files) - output = result.stdout.chomp + output = result.stdout + result.stderr.chomp return :pass if result.success? && output.empty? + extract_messages( output.split("\n"), MESSAGE_REGEX diff --git a/lib/overcommit/hook/pre_commit/swift_lint.rb b/lib/overcommit/hook/pre_commit/swift_lint.rb index 40e4a5eb..a1d8ef09 100644 --- a/lib/overcommit/hook/pre_commit/swift_lint.rb +++ b/lib/overcommit/hook/pre_commit/swift_lint.rb @@ -4,7 +4,7 @@ module Overcommit::Hook::PreCommit # Runs `swiftlint lint` against modified Swift files. # @see https://github.com/realm/SwiftLint class SwiftLint < 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/ts_lint.rb b/lib/overcommit/hook/pre_commit/ts_lint.rb index 7f8cc503..1340cbcf 100644 --- a/lib/overcommit/hook/pre_commit/ts_lint.rb +++ b/lib/overcommit/hook/pre_commit/ts_lint.rb @@ -8,7 +8,7 @@ class TsLint < Base # "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+).*?/ + MESSAGE_REGEX = /^(?.+: )?(?.+?(?=\[))[^\d]+(?\d+).*?/.freeze def run result = execute(command, args: applicable_files) @@ -19,9 +19,9 @@ def run type_categorizer = ->(type) { type.nil? || type.include?('ERROR') ? :error : :warning } extract_messages( - output_lines, - MESSAGE_REGEX, - type_categorizer + output_lines, + MESSAGE_REGEX, + type_categorizer ) end end diff --git a/lib/overcommit/hook/pre_commit/w3c_css.rb b/lib/overcommit/hook/pre_commit/w3c_css.rb index f36230f6..cb32c109 100644 --- a/lib/overcommit/hook/pre_commit/w3c_css.rb +++ b/lib/overcommit/hook/pre_commit/w3c_css.rb @@ -42,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 5330ef4e..945e7997 100644 --- a/lib/overcommit/hook/pre_commit/w3c_html.rb +++ b/lib/overcommit/hook/pre_commit/w3c_html.rb @@ -42,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 618f3e74..71cb403f 100644 --- a/lib/overcommit/hook/pre_commit/xml_lint.rb +++ b/lib/overcommit/hook/pre_commit/xml_lint.rb @@ -5,7 +5,7 @@ module Overcommit::Hook::PreCommit # # @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 7ac05360..99465182 100644 --- a/lib/overcommit/hook/pre_commit/xml_syntax.rb +++ b/lib/overcommit/hook/pre_commit/xml_syntax.rb @@ -7,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 index 2b24a932..f780725f 100644 --- a/lib/overcommit/hook/pre_commit/yaml_lint.rb +++ b/lib/overcommit/hook/pre_commit/yaml_lint.rb @@ -5,13 +5,34 @@ module Overcommit::Hook::PreCommit # # @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}/") - if result.success? - :pass - else - return [:warn, result.stdout] + text = "#{relpath}:#{line}:#{col}:#{type} #{msg}" + Overcommit::Hook::Message.new(type, file, line, text) end end end diff --git a/lib/overcommit/hook/pre_commit/yaml_syntax.rb b/lib/overcommit/hook/pre_commit/yaml_syntax.rb index fa0c4e66..83ff6789 100644 --- a/lib/overcommit/hook/pre_commit/yaml_syntax.rb +++ b/lib/overcommit/hook/pre_commit/yaml_syntax.rb @@ -7,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 index b9336f0f..c9188edb 100644 --- a/lib/overcommit/hook/pre_commit/yard_coverage.rb +++ b/lib/overcommit/hook/pre_commit/yard_coverage.rb @@ -1,4 +1,3 @@ - # frozen_string_literal: true module Overcommit::Hook::PreCommit diff --git a/lib/overcommit/hook/pre_push/cargo_test.rb b/lib/overcommit/hook/pre_push/cargo_test.rb index 33e5076b..ab782e0c 100644 --- a/lib/overcommit/hook/pre_push/cargo_test.rb +++ b/lib/overcommit/hook/pre_push/cargo_test.rb @@ -6,6 +6,7 @@ class CargoTest < Base def run result = execute(command) return :pass if result.success? + [:fail, result.stdout] 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/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/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/r_spec.rb b/lib/overcommit/hook/pre_push/r_spec.rb index a3b4474c..7ce3df29 100644 --- a/lib/overcommit/hook/pre_push/r_spec.rb +++ b/lib/overcommit/hook/pre_push/r_spec.rb @@ -1,16 +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/prepare_commit_msg/base.rb b/lib/overcommit/hook/prepare_commit_msg/base.rb index 5c0c68d4..a4fbf31e 100644 --- a/lib/overcommit/hook/prepare_commit_msg/base.rb +++ b/lib/overcommit/hook/prepare_commit_msg/base.rb @@ -12,6 +12,7 @@ class Base < Overcommit::Hook::Base 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 diff --git a/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb b/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb index c64f4c22..8852b0b5 100644 --- a/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb +++ b/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb @@ -2,11 +2,37 @@ 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 skipped_commit_types.include? commit_message_source + return :pass if skip? Overcommit::Utils.log.debug( "Checking if '#{Overcommit::GitRepo.current_branch}' matches #{branch_pattern}" @@ -17,21 +43,25 @@ def run Overcommit::Utils.log.debug("Writing #{commit_message_filename} with #{new_template}") modify_commit_message do |old_contents| - "#{new_template} #{old_contents}" + "#{new_template}#{old_contents}" end :pass end def new_template - @new_template ||= Overcommit::GitRepo.current_branch.gsub(branch_pattern, replacement_text) + @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? ? '\A.*\w+[-_](\d+).*\z' : pattern) + Regexp.new((pattern || '').empty? ? DEFAULT_BRANCH_PATTERN : pattern) end end @@ -39,7 +69,7 @@ def replacement_text @replacement_text ||= begin if File.exist?(replacement_text_config) - File.read(replacement_text_config) + File.read(replacement_text_config).chomp else replacement_text_config end @@ -53,5 +83,9 @@ def replacement_text_config 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 b39d4ada..cfcde92f 100644 --- a/lib/overcommit/hook/shared/bower_install.rb +++ b/lib/overcommit/hook/shared/bower_install.rb @@ -9,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 060e30c5..01acaa32 100644 --- a/lib/overcommit/hook/shared/bundle_install.rb +++ b/lib/overcommit/hook/shared/bundle_install.rb @@ -9,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 index 839dd65b..2d2ff01d 100644 --- a/lib/overcommit/hook/shared/composer_install.rb +++ b/lib/overcommit/hook/shared/composer_install.rb @@ -9,6 +9,7 @@ module ComposerInstall def run result = execute(command) return :fail, result.stdout unless result.success? + :pass end end diff --git a/lib/overcommit/hook/shared/npm_install.rb b/lib/overcommit/hook/shared/npm_install.rb index 7fa06772..f487c8e7 100644 --- a/lib/overcommit/hook/shared/npm_install.rb +++ b/lib/overcommit/hook/shared/npm_install.rb @@ -9,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 index b1d71442..cf8a3139 100644 --- a/lib/overcommit/hook/shared/pronto.rb +++ b/lib/overcommit/hook/shared/pronto.rb @@ -1,21 +1,33 @@ # frozen_string_literal: true module Overcommit::Hook::Shared - # Shared code used by all Pronto hooks. Runs pronto linter. + # 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? - extract_messages( - result.stdout.split("\n"), - /^(?(?:\w:)?[^:]+):(?\d+) (?[^ ]+)/, + # 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/yarn_install.rb b/lib/overcommit/hook/shared/yarn_install.rb index bc3b7869..fd548f07 100644 --- a/lib/overcommit/hook/shared/yarn_install.rb +++ b/lib/overcommit/hook/shared/yarn_install.rb @@ -9,6 +9,7 @@ module YarnInstall def run result = execute(command) return :fail, result.stderr unless result.success? + :pass end end diff --git a/lib/overcommit/hook_context.rb b/lib/overcommit/hook_context.rb index df12b86c..5863ed94 100644 --- a/lib/overcommit/hook_context.rb +++ b/lib/overcommit/hook_context.rb @@ -2,18 +2,18 @@ # 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 077394fb..b50698c9 100644 --- a/lib/overcommit/hook_context/base.rb +++ b/lib/overcommit/hook_context/base.rb @@ -18,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 diff --git a/lib/overcommit/hook_context/commit_msg.rb b/lib/overcommit/hook_context/commit_msg.rb index e73aa3ff..ae32e55d 100644 --- a/lib/overcommit/hook_context/commit_msg.rb +++ b/lib/overcommit/hook_context/commit_msg.rb @@ -1,8 +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 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_commit.rb b/lib/overcommit/hook_context/post_commit.rb index 0e780077..8327b0d9 100644 --- a/lib/overcommit/hook_context/post_commit.rb +++ b/lib/overcommit/hook_context/post_commit.rb @@ -27,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/pre_commit.rb b/lib/overcommit/hook_context/pre_commit.rb index 6c73cb3b..cd510605 100644 --- a/lib/overcommit/hook_context/pre_commit.rb +++ b/lib/overcommit/hook_context/pre_commit.rb @@ -2,6 +2,8 @@ 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. @@ -9,204 +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 - 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 - - # 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 - - # 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 - - # 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 - - # 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 - - private - - 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 - - # 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 tracked files which have not yet - # been staged. - def unstaged_changes? - result = Overcommit::Utils.execute(%w[git --no-pager diff --quiet]) - !result.success? - 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 0b2e72a1..39ec19ba 100644 --- a/lib/overcommit/hook_context/pre_push.rb +++ b/lib/overcommit/hook_context/pre_push.rb @@ -76,6 +76,7 @@ def ref_range 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/run_all.rb b/lib/overcommit/hook_context/run_all.rb index e7acee57..5ed7efa0 100644 --- a/lib/overcommit/hook_context/run_all.rb +++ b/lib/overcommit/hook_context/run_all.rb @@ -36,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 93e83ccc..9173e9f1 100644 --- a/lib/overcommit/hook_loader/base.rb +++ b/lib/overcommit/hook_loader/base.rb @@ -38,10 +38,10 @@ def create_hook(hook_name) Overcommit::Hook.const_get(@context.hook_class_name). const_get(hook_name). 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_loader/plugin_hook_loader.rb b/lib/overcommit/hook_loader/plugin_hook_loader.rb index b57d7095..d168a0de 100644 --- a/lib/overcommit/hook_loader/plugin_hook_loader.rb +++ b/lib/overcommit/hook_loader/plugin_hook_loader.rb @@ -93,10 +93,10 @@ def run 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 8ac01e99..c7c313ab 100644 --- a/lib/overcommit/hook_runner.rb +++ b/lib/overcommit/hook_runner.rb @@ -94,6 +94,7 @@ def consume loop do hook = @lock.synchronize { @hooks_left.pop } break unless hook + run_hook(hook) end end @@ -159,12 +160,12 @@ def run_hook(hook) # rubocop:disable Metrics/CyclomaticComplexity return if should_skip?(hook) status, output = hook.run_and_transform - rescue Overcommit::Exceptions::MessageProcessingError => ex + rescue Overcommit::Exceptions::MessageProcessingError => e status = :fail - output = ex.message - rescue StandardError => ex + 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 @@ -202,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'] @@ -212,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 f432ca48..fab99c37 100644 --- a/lib/overcommit/hook_signer.rb +++ b/lib/overcommit/hook_signer.rb @@ -52,6 +52,7 @@ def hook_path def signable_file?(file) return unless file + sep = Overcommit::OS.windows? ? '\\' : File::SEPARATOR file.start_with?(".#{sep}") || file.start_with?(Overcommit::Utils.repo_root) diff --git a/lib/overcommit/installer.rb b/lib/overcommit/installer.rb index 11d598e8..79287b30 100644 --- a/lib/overcommit/installer.rb +++ b/lib/overcommit/installer.rb @@ -4,7 +4,7 @@ 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') @@ -174,6 +174,7 @@ 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 diff --git a/lib/overcommit/logger.rb b/lib/overcommit/logger.rb index a9ff793c..9f5d4248 100644 --- a/lib/overcommit/logger.rb +++ b/lib/overcommit/logger.rb @@ -31,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. diff --git a/lib/overcommit/printer.rb b/lib/overcommit/printer.rb index 02d0995d..fcb69653 100644 --- a/lib/overcommit/printer.rb +++ b/lib/overcommit/printer.rb @@ -43,16 +43,14 @@ def end_hook(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.warning '⚠ If files appear modified/missing, check your stash to recover them' - 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. @@ -91,6 +89,7 @@ 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) # rubocop:disable Metrics/CyclomaticComplexity diff --git a/lib/overcommit/subprocess.rb b/lib/overcommit/subprocess.rb index 47713ff2..41175fb9 100644 --- a/lib/overcommit/subprocess.rb +++ b/lib/overcommit/subprocess.rb @@ -2,6 +2,7 @@ require 'childprocess' require 'tempfile' +require 'overcommit/os' module Overcommit # Manages execution of a child process, collecting the exit status and @@ -38,7 +39,7 @@ def spawn(args, options = {}) if options[:input] begin process.io.stdin.puts(options[:input]) - rescue StandardError # 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/messages_utils.rb b/lib/overcommit/utils/messages_utils.rb index 642e3e22..31c0f8db 100644 --- a/lib/overcommit/utils/messages_utils.rb +++ b/lib/overcommit/utils/messages_utils.rb @@ -26,7 +26,7 @@ def extract_messages(output_messages, regex, type_categorizer = nil) raise Overcommit::Exceptions::MessageProcessingError, 'Unexpected output: unable to determine line number or type ' \ "of error/warning for output:\n" \ - "#{output_messages[index..-1].join("\n")}" + "#{output_messages[index..].join("\n")}" end file = extract_file(match, message) @@ -52,6 +52,7 @@ def extract_file(match, message) def extract_line(match, message) return unless match.names.include?('line') + Integer(match[:line]) rescue ArgumentError, TypeError raise Overcommit::Exceptions::MessageProcessingError, diff --git a/lib/overcommit/version.rb b/lib/overcommit/version.rb index d4659a8b..872c5327 100644 --- a/lib/overcommit/version.rb +++ b/lib/overcommit/version.rb @@ -2,5 +2,5 @@ # Defines the gem version. module Overcommit - VERSION = '0.53.0' + 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/overcommit.gemspec b/overcommit.gemspec index 4e06bfc8..caaa8499 100644 --- a/overcommit.gemspec +++ b/overcommit.gemspec @@ -15,6 +15,10 @@ Gem::Specification.new do |s| 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'] @@ -25,8 +29,9 @@ Gem::Specification.new do |s| Dir['libexec/**/*'] + Dir['template-dir/**/*'] - s.required_ruby_version = '>= 2.4' + s.required_ruby_version = '>= 2.6' - s.add_dependency 'childprocess', '>= 0.6.3', '< 4' + 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 6bdd0f08..1e1f65a6 100644 --- a/spec/integration/committing_spec.rb +++ b/spec/integration/committing_spec.rb @@ -26,7 +26,7 @@ context 'when a hook fails' do before do - `git config --local user.name "John"` + `git config --local user.name ""` end it 'exits with a non-zero status' do @@ -34,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/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/gemfile_option_spec.rb b/spec/integration/gemfile_option_spec.rb index 9cf25877..a6a6a4ba 100644 --- a/spec/integration/gemfile_option_spec.rb +++ b/spec/integration/gemfile_option_spec.rb @@ -3,97 +3,156 @@ 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}' - 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.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/parallelize_spec.rb b/spec/integration/parallelize_spec.rb index 20a86be3..471aa7b3 100644 --- a/spec/integration/parallelize_spec.rb +++ b/spec/integration/parallelize_spec.rb @@ -27,8 +27,14 @@ end end - it 'does not hang' do - result = Timeout.timeout(5) { subject } - result.stderr.should_not include 'No live threads left. Deadlock?' + # 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/resolving_cherry_pick_conflict_spec.rb b/spec/integration/resolving_cherry_pick_conflict_spec.rb index 2ea05d7b..2a7f053c 100644 --- a/spec/integration/resolving_cherry_pick_conflict_spec.rb +++ b/spec/integration/resolving_cherry_pick_conflict_spec.rb @@ -3,7 +3,7 @@ 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: @@ -39,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 679ba136..997dcb09 100644 --- a/spec/integration/resolving_merge_conflict_spec.rb +++ b/spec/integration/resolving_merge_conflict_spec.rb @@ -3,7 +3,7 @@ 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/overcommit/cli_spec.rb b/spec/overcommit/cli_spec.rb index 40923ad3..3d95b09d 100644 --- a/spec/overcommit/cli_spec.rb +++ b/spec/overcommit/cli_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' require 'overcommit/cli' +require 'overcommit/hook_context/diff' require 'overcommit/hook_context/run_all' describe Overcommit::CLI do @@ -125,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/configuration_loader_spec.rb b/spec/overcommit/configuration_loader_spec.rb index a99608d4..90497201 100644 --- a/spec/overcommit/configuration_loader_spec.rb +++ b/spec/overcommit/configuration_loader_spec.rb @@ -57,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/default_configuration_spec.rb b/spec/overcommit/default_configuration_spec.rb index a86b1fe3..706af872 100644 --- a/spec/overcommit/default_configuration_spec.rb +++ b/spec/overcommit/default_configuration_spec.rb @@ -4,7 +4,11 @@ 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 22ad7c02..9cc51862 100644 --- a/spec/overcommit/git_config_spec.rb +++ b/spec/overcommit/git_config_spec.rb @@ -78,5 +78,19 @@ 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 b5376212..b887415b 100644 --- a/spec/overcommit/git_repo_spec.rb +++ b/spec/overcommit/git_repo_spec.rb @@ -24,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 @@ -150,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') } @@ -178,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 @@ -217,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 @@ -268,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 @@ -313,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 @@ -329,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 } @@ -355,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` @@ -363,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/commit_msg/message_format_spec.rb b/spec/overcommit/hook/commit_msg/message_format_spec.rb index d4aaab62..c9dde29b 100644 --- a/spec/overcommit/hook/commit_msg/message_format_spec.rb +++ b/spec/overcommit/hook/commit_msg/message_format_spec.rb @@ -15,11 +15,11 @@ context 'when pattern is empty' do let(:config) do super().merge(Overcommit::Configuration.new( - 'CommitMsg' => { - 'MessageFormat' => { - 'pattern' => nil - } - } + 'CommitMsg' => { + 'MessageFormat' => { + 'pattern' => nil + } + } )) end @@ -43,11 +43,11 @@ context 'when multiline message matches the pattern' do let(:config) do super().merge(Overcommit::Configuration.new( - 'CommitMsg' => { - 'MessageFormat' => { - 'pattern' => '^Some .* Message$' - } - } + 'CommitMsg' => { + 'MessageFormat' => { + 'pattern' => '^Some .* Message$' + } + } )) end diff --git a/spec/overcommit/hook/commit_msg/text_width_spec.rb b/spec/overcommit/hook/commit_msg/text_width_spec.rb index e07d777e..8829668e 100644 --- a/spec/overcommit/hook/commit_msg/text_width_spec.rb +++ b/spec/overcommit/hook/commit_msg/text_width_spec.rb @@ -98,13 +98,13 @@ context 'when custom lengths are specified' do let(:config) do super().merge(Overcommit::Configuration.new( - 'CommitMsg' => { - 'TextWidth' => { - 'max_subject_width' => 70, - 'min_subject_width' => 4, - 'max_body_width' => 80 - } - } + 'CommitMsg' => { + 'TextWidth' => { + 'max_subject_width' => 70, + 'min_subject_width' => 4, + 'max_body_width' => 80 + } + } )) end diff --git a/spec/overcommit/hook/pre_commit/author_email_spec.rb b/spec/overcommit/hook/pre_commit/author_email_spec.rb index 613f114e..8e0dd768 100644 --- a/spec/overcommit/hook/pre_commit/author_email_spec.rb +++ b/spec/overcommit/hook/pre_commit/author_email_spec.rb @@ -30,11 +30,11 @@ context 'when a custom pattern is specified' do let(:config) do super().merge(Overcommit::Configuration.new( - 'PreCommit' => { - 'AuthorEmail' => { - 'pattern' => '^[^@]+@brigade\.com$' - } - } + 'PreCommit' => { + 'AuthorEmail' => { + 'pattern' => '^[^@]+@brigade\.com$' + } + } )) end diff --git a/spec/overcommit/hook/pre_commit/author_name_spec.rb b/spec/overcommit/hook/pre_commit/author_name_spec.rb index 7cf230d5..1ed0502f 100644 --- a/spec/overcommit/hook/pre_commit/author_name_spec.rb +++ b/spec/overcommit/hook/pre_commit/author_name_spec.rb @@ -18,7 +18,7 @@ context 'when user has only a first name' do let(:name) { 'John' } - it { should fail_hook } + it { should pass } end context 'when user has first and last name' do diff --git a/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb b/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb index 633e14fe..b8360e2e 100644 --- a/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb +++ b/spec/overcommit/hook/pre_commit/bundle_audit_spec.rb @@ -54,7 +54,7 @@ Title: Rest-Client Gem for Ruby logs password information in plaintext Solution: upgrade to >= 1.7.3 Vulnerabilities found! -MSG + MSG ) end @@ -67,7 +67,7 @@ stdout: <<-MSG Insecure Source URI found: git://github.com/xxx/overcommit.git Vulnerabilities found! -MSG + MSG ) end diff --git a/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb b/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb index c61e4873..a734b3dc 100644 --- a/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb +++ b/spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb @@ -49,7 +49,7 @@ 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 + MSG ) end @@ -60,7 +60,7 @@ 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 + MSG ) end 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/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 bcb2cfdc..39de7d27 100644 --- a/spec/overcommit/hook/pre_commit/es_lint_spec.rb +++ b/spec/overcommit/hook/pre_commit/es_lint_spec.rb @@ -52,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/fasterer_spec.rb b/spec/overcommit/hook/pre_commit/fasterer_spec.rb index 405888ab..afb08791 100644 --- a/spec/overcommit/hook/pre_commit/fasterer_spec.rb +++ b/spec/overcommit/hook/pre_commit/fasterer_spec.rb @@ -36,7 +36,7 @@ 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 + MSG ) end @@ -51,7 +51,7 @@ 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 + MSG ) end diff --git a/spec/overcommit/hook/pre_commit/flay_spec.rb b/spec/overcommit/hook/pre_commit/flay_spec.rb index a64cd2c3..2a5d8140 100644 --- a/spec/overcommit/hook/pre_commit/flay_spec.rb +++ b/spec/overcommit/hook/pre_commit/flay_spec.rb @@ -38,7 +38,7 @@ app/whatever21.rb:105 app/whatever22.rb:76 -MSG + MSG ) end @@ -51,7 +51,7 @@ success?: false, stdout: <<-MSG Total score (lower is better) = 0 -MSG + MSG ) end diff --git a/spec/overcommit/hook/pre_commit/hadolint_spec.rb b/spec/overcommit/hook/pre_commit/hadolint_spec.rb index 4091ca0b..ad756f30 100644 --- a/spec/overcommit/hook/pre_commit/hadolint_spec.rb +++ b/spec/overcommit/hook/pre_commit/hadolint_spec.rb @@ -31,7 +31,7 @@ success?: false, stdout: <<-MSG Dockerfile:5 DL3015 Avoid additional packages by specifying `--no-install-recommends` - MSG + MSG ) end let(:result_dockerfile_web) do @@ -39,7 +39,7 @@ success?: false, stdout: <<-MSG Dockerfile.web:13 DL3020 Use COPY instead of ADD for files and folders - MSG + MSG ) end @@ -52,7 +52,7 @@ success?: false, stdout: <<-MSG Dockerfile:11 SC2086 Double quote to prevent globbing and word splitting. - MSG + MSG ) end let(:result_dockerfile_web) 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/php_lint_spec.rb b/spec/overcommit/hook/pre_commit/php_lint_spec.rb index aff0b97e..ec755345 100644 --- a/spec/overcommit/hook/pre_commit/php_lint_spec.rb +++ b/spec/overcommit/hook/pre_commit/php_lint_spec.rb @@ -26,13 +26,13 @@ context 'when php lint exits unsuccessfully' do before do # php -l prints the same to both stdout and stderr - # rubocop:disable Metrics/LineLength + # 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 Metrics/LineLength + # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(255) diff --git a/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb b/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb index 33b0d7c1..a4b1e187 100644 --- a/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb +++ b/spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb @@ -13,7 +13,7 @@ context 'when phpcs fixer exits successfully with fixed file' do before do - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength sample_output = [ 'Loaded config default.', 'Using cache file ".php_cs.cache".', @@ -24,7 +24,7 @@ 'Fixed all files in 0.001 seconds, 10.000 MB memory used', '', ].join("\n") - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(0) @@ -38,7 +38,7 @@ context 'when phpcs fixer exits successfully with no file to fix' do before do - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength sample_output = [ 'Loaded config default.', 'Using cache file ".php_cs.cache".', @@ -46,7 +46,7 @@ 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error', '', ].join("\n") - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(0) @@ -60,7 +60,7 @@ context 'when phpcs exits unsuccessfully' do before do - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength sample_output = [ 'Loaded config default.', 'Using cache file ".php_cs.cache".', @@ -72,7 +72,7 @@ ' 1) /home/damien/Code/Rezdy/php/foo/broken.php', '', ].join("\n") - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(1) diff --git a/spec/overcommit/hook/pre_commit/phpcs_spec.rb b/spec/overcommit/hook/pre_commit/phpcs_spec.rb index 3df6cfb5..6982a297 100644 --- a/spec/overcommit/hook/pre_commit/phpcs_spec.rb +++ b/spec/overcommit/hook/pre_commit/phpcs_spec.rb @@ -39,12 +39,12 @@ context 'and it reports a warning' do before do - # rubocop:disable Metrics/LineLength + # 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 Metrics/LineLength + # rubocop:enable Layout/LineLength result.stub(:stdout).and_return(sample_output) end @@ -53,12 +53,12 @@ context 'and it reports an error' do before do - # rubocop:disable Metrics/LineLength + # 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 Metrics/LineLength + # rubocop:enable Layout/LineLength result.stub(:stdout).and_return(sample_output) end diff --git a/spec/overcommit/hook/pre_commit/pronto_spec.rb b/spec/overcommit/hook/pre_commit/pronto_spec.rb index ed099ace..c9d56abe 100644 --- a/spec/overcommit/hook/pre_commit/pronto_spec.rb +++ b/spec/overcommit/hook/pre_commit/pronto_spec.rb @@ -31,6 +31,7 @@ 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")) @@ -41,13 +42,30 @@ context 'and it reports a warning' do before do - result.stub(:stdout).and_return([ - 'file1.rb:12 W: Line is too long. [107/80]', - 'file2.rb:14 I: Prefer single-quoted strings' - ].join("\n")) + 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/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_schema_up_to_date_spec.rb b/spec/overcommit/hook/pre_commit/rails_schema_up_to_date_spec.rb index ad53485e..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 @@ -70,6 +70,33 @@ 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 diff --git a/spec/overcommit/hook/pre_commit/rake_target_spec.rb b/spec/overcommit/hook/pre_commit/rake_target_spec.rb index 4a0cf400..e295e61f 100644 --- a/spec/overcommit/hook/pre_commit/rake_target_spec.rb +++ b/spec/overcommit/hook/pre_commit/rake_target_spec.rb @@ -19,11 +19,11 @@ context 'with targets parameter set' do let(:config) do super().merge(Overcommit::Configuration.new( - 'PreCommit' => { - 'RakeTarget' => { - 'targets' => ['test'], - } - } + 'PreCommit' => { + 'RakeTarget' => { + 'targets' => ['test'], + } + } )) end let(:result) { double('result') } 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/stylelint_spec.rb b/spec/overcommit/hook/pre_commit/stylelint_spec.rb index d31ba948..68e83f65 100644 --- a/spec/overcommit/hook/pre_commit/stylelint_spec.rb +++ b/spec/overcommit/hook/pre_commit/stylelint_spec.rb @@ -15,6 +15,7 @@ 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 @@ -22,7 +23,7 @@ it { should pass } end - context 'when stylelint exits unsucessfully' do + context 'when stylelint exits unsucessfully with messages on stdout (stylelint < 16)' do let(:result) { double('result') } before do @@ -32,6 +33,7 @@ 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)', @@ -45,4 +47,29 @@ 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/yaml_lint_spec.rb b/spec/overcommit/hook/pre_commit/yaml_lint_spec.rb index 72ebc6e8..e5aea475 100644 --- a/spec/overcommit/hook/pre_commit/yaml_lint_spec.rb +++ b/spec/overcommit/hook/pre_commit/yaml_lint_spec.rb @@ -19,7 +19,7 @@ end before do - subject.stub(:execute).with(%w[yamllint --format=parsable], args: applicable_files). + subject.stub(:execute).with(%w[yamllint --format=parsable --strict], args: applicable_files). and_return(result) end @@ -30,20 +30,33 @@ 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 + MSG ) end - it { should warn } + 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 + MSG ) end diff --git a/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb b/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb index 29cb7b02..a6f61f2c 100644 --- a/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb +++ b/spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb @@ -22,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 index d59779c9..d463d25d 100644 --- a/spec/overcommit/hook/pre_commit/yard_coverage_spec.rb +++ b/spec/overcommit/hook/pre_commit/yard_coverage_spec.rb @@ -76,7 +76,7 @@ context 'and it reports an error' do before do result.stub(:stdout).and_return( - <<-HEREDOC + <<-HEREDOC Files: 72 Modules: 12 ( 3 undocumented) Classes: 63 ( 15 undocumented) 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) + + #28 Declarer.test.. (package:test_api/src/backend/declarer.dart) + #33 Declarer.test. (package:test_api/src/backend/declarer.dart:173:13) + #34 Invoker.waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:231:15) + #39 Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:228:5) + #40 Invoker._onRun... (package:test_api/src/backend/invoker.dart:383:17) + + #41 Invoker._onRun... (package:test_api/src/backend/invoker.dart) + #46 Invoker._onRun.. (package:test_api/src/backend/invoker.dart:370:9) + #47 Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:415:15) + #48 Invoker._onRun. (package:test_api/src/backend/invoker.dart:369:7) + #55 Invoker._onRun (package:test_api/src/backend/invoker.dart:368:11) + #56 LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11) + #57 RemoteListener._runLiveTest. (package:test_api/src/remote_listener.dart:256:16) + #62 RemoteListener._runLiveTest (package:test_api/src/remote_listener.dart:255:5) + #63 RemoteListener._serializeTest. (package:test_api/src/remote_listener.dart:208:7) + #81 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12) + #82 new _MultiChannel. (package:stream_channel/src/multi_channel.dart:159:31) + #86 CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11) + #120 new _WebSocketImpl._fromSocket. (dart:_http/websocket_impl.dart:1145:21) + #128 _WebSocketProtocolTransformer._messageFrameEnd (dart:_http/websocket_impl.dart:338:23) + #129 _WebSocketProtocolTransformer.add (dart:_http/websocket_impl.dart:232:46) + #139 _Socket._onData (dart:io-patch/socket_patch.dart:2044:41) + #148 new _RawSocket. (dart:io-patch/socket_patch.dart:1580:33) + #149 _NativeSocket.issueReadEvent.issue (dart:io-patch/socket_patch.dart:1076:14) + (elided 111 frames from dart:async and package:stack_trace) + + This was caught by the test expectation on the following line: + file:///Users/user/project/test/widget_test.dart line 19 + The test description was: + Counter increments smoke test + ════════════════════════════════════════════════════════════════════════════════════════════════════ + 00:02 +0 -1: Counter increments smoke test [E] + Test failed. See exception logs above. + The test description was: Counter increments smoke test + + 00:02 +0 -1: Some tests failed. + MSG + end + + it { should fail_hook } + end + end +end diff --git a/spec/overcommit/hook/pre_push/mix_test_spec.rb b/spec/overcommit/hook/pre_push/mix_test_spec.rb new file mode 100644 index 00000000..798da7f7 --- /dev/null +++ b/spec/overcommit/hook/pre_push/mix_test_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Overcommit::Hook::PrePush::MixTest do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + context 'when mix test 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 test exits 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_push/pronto_spec.rb b/spec/overcommit/hook/pre_push/pronto_spec.rb index 2851a860..5f48b7d7 100644 --- a/spec/overcommit/hook/pre_push/pronto_spec.rb +++ b/spec/overcommit/hook/pre_push/pronto_spec.rb @@ -31,6 +31,7 @@ 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")) @@ -41,13 +42,30 @@ context 'and it reports a warning' do before do - result.stub(:stdout).and_return([ - 'file1.rb:12 W: Line is too long. [107/80]', - 'file2.rb:14 I: Prefer single-quoted strings' - ].join("\n")) + 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_push/pub_test_spec.rb b/spec/overcommit/hook/pre_push/pub_test_spec.rb new file mode 100644 index 00000000..41ff5c5d --- /dev/null +++ b/spec/overcommit/hook/pre_push/pub_test_spec.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Overcommit::Hook::PrePush::PubTest do + let(:config) { Overcommit::ConfigurationLoader.default_configuration } + let(:context) { double('context') } + subject { described_class.new(config, context) } + + context 'when pub 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 pub 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) + 00:01 +0 -1: test/test_test.dart: String.split() splits the string on the delimiter [E] + Exception + test/test_test.dart 6:5 main. + + 00:01 +1 -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:01 +0 -1: test/test_test.dart: String.split() splits the string on the delimiter [E] + Expected: ['fooo', 'bar', 'baz'] + Actual: ['foo', 'bar', 'baz'] + Which: at location [0] is 'foo' instead of 'fooo' + + package:test_api expect + test/test_test.dart 6:5 main. + + 00:01 +1 -1: Some tests failed. + MSG + end + + it { should fail_hook } + end + end +end diff --git a/spec/overcommit/hook/pre_push/r_spec_spec.rb b/spec/overcommit/hook/pre_push/r_spec_spec.rb index 26f6ea0f..1efc56d7 100644 --- a/spec/overcommit/hook/pre_push/r_spec_spec.rb +++ b/spec/overcommit/hook/pre_push/r_spec_spec.rb @@ -16,6 +16,42 @@ 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( + 'PrePush' => { + '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 diff --git a/spec/overcommit/hook/pre_push/rake_target_spec.rb b/spec/overcommit/hook/pre_push/rake_target_spec.rb index 45c93752..5147d089 100644 --- a/spec/overcommit/hook/pre_push/rake_target_spec.rb +++ b/spec/overcommit/hook/pre_push/rake_target_spec.rb @@ -19,11 +19,11 @@ context 'with targets parameter set' do let(:config) do super().merge(Overcommit::Configuration.new( - 'PrePush' => { - 'RakeTarget' => { - 'targets' => ['test'], - } - } + 'PrePush' => { + 'RakeTarget' => { + 'targets' => ['test'], + } + } )) end let(:result) { double('result') } diff --git a/spec/overcommit/hook/prepare_commit_msg/base_spec.rb b/spec/overcommit/hook/prepare_commit_msg/base_spec.rb index 616d6b8e..873ed637 100644 --- a/spec/overcommit/hook/prepare_commit_msg/base_spec.rb +++ b/spec/overcommit/hook/prepare_commit_msg/base_spec.rb @@ -38,9 +38,9 @@ contents + "bravo\n" end end - Thread.new { hook_1.run } - Thread.new { hook_2.run } - Thread.list.each { |t| t.join unless t == Thread.current } + t1 = Thread.new { hook_1.run } + t2 = Thread.new { hook_2.run } + [t1, t2].each(&:join) expect(File.read(tempfile)).to match(/alpha\n#{initial_content}bravo\n/m) end end diff --git a/spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb b/spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb index 71eb7c13..e355517f 100644 --- a/spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb +++ b/spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb @@ -4,77 +4,155 @@ require 'overcommit/hook_context/prepare_commit_msg' describe Overcommit::Hook::PrepareCommitMsg::ReplaceBranch do - let(:config) { Overcommit::ConfigurationLoader.default_configuration } - let(:context) do - Overcommit::HookContext::PrepareCommitMsg.new( - config, [prepare_commit_message_file, 'commit'], StringIO.new + def checkout_branch(branch) + allow(Overcommit::GitRepo).to receive(:current_branch).and_return(branch) + end + + def new_config(opts = {}) + default = Overcommit::ConfigurationLoader.default_configuration + + return default if opts.empty? + + default.merge( + Overcommit::Configuration.new( + 'PrepareCommitMsg' => { + 'ReplaceBranch' => opts.merge('enabled' => true) + } + ) ) end - let(:prepare_commit_message_file) { 'prepare_commit_message_file.txt' } + def new_context(config, argv) + Overcommit::HookContext::PrepareCommitMsg.new(config, argv, StringIO.new) + end - subject(:hook) { described_class.new(config, context) } + def hook_for(config, context) + described_class.new(config, context) + end - before do - File.open(prepare_commit_message_file, 'w') - allow(Overcommit::Utils).to receive_message_chain(:log, :debug) - allow(Overcommit::GitRepo).to receive(:current_branch).and_return(new_head) + def add_file(name, contents) + File.open(name, 'w') { |f| f.puts contents } end - after do - File.delete(prepare_commit_message_file) unless ENV['APPVEYOR'] + def remove_file(name) + File.delete(name) end - let(:new_head) { 'userbeforeid-12345-branch-description' } + before { allow(Overcommit::Utils).to receive_message_chain(:log, :debug) } + + let(:config) { new_config } + let(:normal_context) { new_context(config, ['COMMIT_EDITMSG']) } + let(:message_context) { new_context(config, %w[COMMIT_EDITMSG message]) } + let(:commit_context) { new_context(config, %w[COMMIT_EDITMSG commit HEAD]) } + let(:merge_context) { new_context(config, %w[MERGE_MSG merge]) } + let(:squash_context) { new_context(config, %w[SQUASH_MSG squash]) } + let(:template_context) { new_context(config, ['template.txt', 'template']) } + subject(:hook) { hook_for(config, normal_context) } describe '#run' do + before { add_file 'COMMIT_EDITMSG', '' } + after { remove_file 'COMMIT_EDITMSG' } + context 'when the checked out branch matches the pattern' do + before { checkout_branch '123-topic' } + before { hook.run } + it { is_expected.to pass } - context 'template contents' do - subject(:template) { hook.new_template } + it 'prepends the replacement text' do + expect(File.read('COMMIT_EDITMSG')).to eq("[#123]\n") + end + + context 'when the replacement text contains a space' do + let(:config) { new_config('replacement_text' => '[\1] ') } - before do - hook.stub(:replacement_text).and_return('Id is: \1') + it 'prepends the replacement text, including the space' do + expect(File.read('COMMIT_EDITMSG')).to eq("[123] \n") end - - it { is_expected.to eq('Id is: 12345') } end - end - context 'when the checked out branch does not match the pattern' do - let(:new_head) { "this shouldn't match the default pattern" } + context 'when skip_if exits with a zero status' do + let(:config) { new_config('skip_if' => ['bash', '-c', 'exit 0']) } - context 'when the commit type is in `skipped_commit_types`' do - let(:context) do - Overcommit::HookContext::PrepareCommitMsg.new( - config, [prepare_commit_message_file, 'template'], StringIO.new - ) + it { is_expected.to pass } + + it 'does not change the commit message' do + expect(File.read('COMMIT_EDITMSG')).to eq("\n") end + end + + context 'when skip_if exits with a non-zero status' do + let(:config) { new_config('skip_if' => ['bash', '-c', 'exit 1']) } it { is_expected.to pass } + + it 'does change the commit message' do + expect(File.read('COMMIT_EDITMSG')).to eq("[#123]\n") + end end + end + + context "when the checked out branch doesn't matches the pattern" do + before { checkout_branch 'topic-123' } + before { hook.run } - context 'when the commit type is not in `skipped_commit_types`' do + context 'with the default `skipped_commit_types`' do it { is_expected.to warn } end - end - end - describe '#replacement_text' do - subject(:replacement_text) { hook.replacement_text } - let(:replacement_template_file) { 'valid_filename.txt' } - let(:replacement) { 'Id is: \1' } + context 'when merging, and `skipped_commit_types` includes `merge`' do + let(:config) { new_config('skipped_commit_types' => ['merge']) } + subject(:hook) { hook_for(config, merge_context) } - context 'when the replacement text points to a valid filename' do - before do - hook.stub(:replacement_text_config).and_return(replacement_template_file) - File.stub(:exist?).and_return(true) - File.stub(:read).with(replacement_template_file).and_return(replacement) + it { is_expected.to pass } end - describe 'it reads it as the replacement template' do - it { is_expected.to eq(replacement) } + context 'when merging, and `skipped_commit_types` includes `template`' do + let(:config) { new_config('skipped_commit_types' => ['template']) } + subject(:hook) { hook_for(config, template_context) } + + it { is_expected.to pass } + end + + context 'when merging, and `skipped_commit_types` includes `message`' do + let(:config) { new_config('skipped_commit_types' => ['message']) } + subject(:hook) { hook_for(config, message_context) } + + it { is_expected.to pass } + end + + context 'when merging, and `skipped_commit_types` includes `commit`' do + let(:config) { new_config('skipped_commit_types' => ['commit']) } + subject(:hook) { hook_for(config, commit_context) } + + it { is_expected.to pass } + end + + context 'when merging, and `skipped_commit_types` includes `squash`' do + let(:config) { new_config('skipped_commit_types' => ['squash']) } + subject(:hook) { hook_for(config, squash_context) } + + it { is_expected.to pass } + end + end + + context 'when the replacement text points to a valid filename' do + before { checkout_branch '123-topic' } + before { add_file 'replacement_text.txt', 'FOO' } + after { remove_file 'replacement_text.txt' } + + let(:config) { new_config('replacement_text' => 'replacement_text.txt') } + let(:normal_context) { new_context(config, ['COMMIT_EDITMSG']) } + subject(:hook) { hook_for(config, normal_context) } + + before { hook.run } + + it { is_expected.to pass } + + let(:commit_msg) { File.read('COMMIT_EDITMSG') } + + it 'uses the file contents as the replacement text' do + expect(commit_msg).to eq(File.read('replacement_text.txt')) end end end diff --git a/spec/overcommit/hook_context/commit_msg_spec.rb b/spec/overcommit/hook_context/commit_msg_spec.rb index 2078f463..058f1508 100644 --- a/spec/overcommit/hook_context/commit_msg_spec.rb +++ b/spec/overcommit/hook_context/commit_msg_spec.rb @@ -99,4 +99,669 @@ subject.should end_with "git commit --edit --file=#{commit_message_file}" end end + + describe '#amendment?' do + subject { context.amendment? } + + before do + Overcommit::Utils.stub(:parent_command).and_return(command) + end + + context 'when amending a commit using `git commit --amend`' do + let(:command) { 'git commit --amend' } + + it { should == true } + end + + context 'when the parent command contains invalid byte sequence' do + let(:command) { "git commit --amend -m \xE3M^AM^B" } + + it { should == true } + end + + context 'when amending a commit using a git alias' do + around do |example| + repo do + `git config alias.amend "commit --amend"` + `git config alias.other-amend "commit --amend"` + example.run + end + end + + context 'when using one of multiple aliases' do + let(:command) { 'git amend' } + + it { should == true } + end + + context 'when using another of multiple aliases' do + let(:command) { 'git other-amend' } + + it { should == true } + end + end + + context 'when not amending a commit' do + context 'using `git commit`' do + let(:command) { 'git commit' } + + it { should == false } + end + + context 'using a git alias containing "--amend"' do + let(:command) { 'git no--amend' } + + around do |example| + repo do + `git config alias.no--amend commit` + example.run + end + end + + it { should == false } + end + end + end + + describe '#setup_environment' do + subject { context.setup_environment } + + context 'when there are no staged changes' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + echo('Hello Other World', 'other-tracked-file') + `git add tracked-file other-tracked-file` + `git commit -m "Add tracked-file and other-tracked-file"` + echo('Hello Again', 'untracked-file') + echo('Some more text', 'other-tracked-file', append: true) + example.run + end + end + + it 'keeps already-committed files' do + subject + File.open('tracked-file', 'r').read.should == "Hello World\n" + end + + it 'does not keep unstaged changes' do + subject + File.open('other-tracked-file', 'r').read.should == "Hello Other World\n" + end + + it 'keeps untracked files' do + subject + File.open('untracked-file', 'r').read.should == "Hello Again\n" + end + + it 'keeps modification times the same' do + sleep 1 + expect { subject }.to_not change { + [ + File.mtime('tracked-file'), + File.mtime('other-tracked-file'), + File.mtime('untracked-file') + ] + } + end + end + + context 'when there are staged changes' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + echo('Hello Other World', 'other-tracked-file') + `git add tracked-file other-tracked-file` + `git commit -m "Add tracked-file and other-tracked-file"` + echo('Hello Again', 'untracked-file') + echo('Some more text', 'tracked-file', append: true) + echo('Some more text', 'other-tracked-file', append: true) + `git add tracked-file` + echo('Yet some more text', 'tracked-file', append: true) + example.run + end + end + + it 'keeps staged changes' do + subject + File.open('tracked-file', 'r').read.should == "Hello World\nSome more text\n" + end + + it 'does not keep unstaged changes' do + subject + File.open('other-tracked-file', 'r').read.should == "Hello Other World\n" + end + + it 'keeps untracked files' do + subject + File.open('untracked-file', 'r').read.should == "Hello Again\n" + end + + it 'keeps modification times the same' do + sleep 1 + expect { subject }.to_not change { + [ + File.mtime('tracked-file'), + File.mtime('other-tracked-file'), + File.mtime('untracked-file') + ] + } + end + end + + context 'when all changes have been staged' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + `git add tracked-file` + `git commit -m "Add tracked-file"` + echo('Hello Other World', 'other-tracked-file') + `git add other-tracked-file` + example.run + end + end + + it 'does not stash changes' do + expect(context.private_methods).to include :stash_changes + expect(context).not_to receive(:stash_changes) + subject + end + end + + context 'when renaming a file during an amendment' do + around do |example| + repo do + `git commit --allow-empty -m "Initial commit"` + touch 'some-file' + `git add some-file` + `git commit -m "Add file"` + `git mv some-file renamed-file` + example.run + end + end + + before do + context.stub(:amendment?).and_return(true) + end + + it 'does not try to update modification time of the old non-existent file' do + File.should_receive(:mtime).with(/renamed-file/) + File.should_not_receive(:mtime).with(/some-file/) + subject + end + end + + context 'when only a submodule change is staged' do + around do |example| + submodule = repo do + `git commit --allow-empty -m "Initial commit"` + end + + repo do + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git commit -m "Add submodule"` + echo('Hello World', 'sub/submodule-file') + `git submodule foreach "git add submodule-file" < #{File::NULL}` + `git submodule foreach "git config --local commit.gpgsign false"` + `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` + `git add sub` + example.run + end + end + + it 'keeps staged submodule change' do + `git config diff.submodule short` + expect { subject }.to_not change { + (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? + }.from(false) + end + end + + # Git cannot track Windows symlinks + unless Overcommit::OS.windows? + context 'when a broken symlink is staged' do + around do |example| + repo do + Overcommit::Utils::FileUtils.symlink('non-existent-file', 'symlink') + `git add symlink` + example.run + end + end + + it 'does not attempt to update/restore the modification time of the file' do + File.should_not_receive(:mtime) + File.should_not_receive(:utime) + subject + end + end + end + end + + describe '#cleanup_environment' do + subject { context.cleanup_environment } + + before do + context.setup_environment + end + + context 'when there were no staged changes' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + echo('Hello Other World', 'other-tracked-file') + `git add tracked-file other-tracked-file` + `git commit -m "Add tracked-file and other-tracked-file"` + echo('Hello Again', 'untracked-file') + echo('Some more text', 'other-tracked-file', append: true) + example.run + end + end + + it 'restores the unstaged changes' do + subject + File.open('other-tracked-file', 'r').read. + should == "Hello Other World\nSome more text\n" + end + + it 'keeps already-committed files' do + subject + File.open('tracked-file', 'r').read.should == "Hello World\n" + end + + it 'keeps untracked files' do + subject + File.open('untracked-file', 'r').read.should == "Hello Again\n" + end + + it 'keeps modification times the same' do + sleep 1 + expect { subject }.to_not change { + [ + File.mtime('tracked-file'), + File.mtime('other-tracked-file'), + File.mtime('untracked-file') + ] + } + end + end + + context 'when there were staged changes' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + echo('Hello Other World', 'other-tracked-file') + `git add tracked-file other-tracked-file` + `git commit -m "Add tracked-file and other-tracked-file"` + echo('Hello Again', 'untracked-file') + echo('Some more text', 'tracked-file', append: true) + echo('Some more text', 'other-tracked-file', append: true) + `git add tracked-file` + echo('Yet some more text', 'tracked-file', append: true) + example.run + end + end + + it 'restores the unstaged changes' do + subject + File.open('tracked-file', 'r').read. + should == "Hello World\nSome more text\nYet some more text\n" + end + + it 'keeps staged changes' do + subject + `git show :tracked-file`.should == "Hello World\nSome more text\n" + end + + it 'keeps untracked files' do + subject + File.open('untracked-file', 'r').read.should == "Hello Again\n" + end + + it 'keeps modification times the same' do + sleep 1 + expect { subject }.to_not change { + [ + File.mtime('tracked-file'), + File.mtime('other-tracked-file'), + File.mtime('untracked-file') + ] + } + end + end + + context 'when all changes were staged' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + `git add tracked-file` + `git commit -m "Add tracked-file"` + echo('Hello Other World', 'other-tracked-file') + `git add other-tracked-file` + example.run + end + end + + it 'does not touch the working tree' do + expect(context.private_methods).to include :clear_working_tree + expect(context.private_methods).to include :restore_working_tree + expect(context).not_to receive(:clear_working_tree) + expect(context).not_to receive(:restore_working_tree) + subject + end + end + + context 'when there were deleted files' do + around do |example| + repo do + echo('Hello World', 'tracked-file') + `git add tracked-file` + `git commit -m "Add tracked-file"` + `git rm tracked-file` + example.run + end + end + + it 'deletes the file' do + subject + File.exist?('tracked-file').should == false + end + end + + context 'when only a submodule change was staged' do + around do |example| + submodule = repo do + `git commit --allow-empty -m "Initial commit"` + end + + repo do + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git commit -m "Add submodule"` + echo('Hello World', 'sub/submodule-file') + `git submodule foreach "git add submodule-file" < #{File::NULL}` + `git submodule foreach "git config --local commit.gpgsign false"` + `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` + `git add sub` + example.run + end + end + + it 'keeps staged submodule change' do + `git config diff.submodule short` + expect { subject }.to_not change { + (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? + }.from(false) + end + end + + context 'when submodule changes were staged along with other changes' do + around do |example| + submodule = repo do + `git commit --allow-empty -m "Initial commit"` + end + + repo do + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git commit -m "Add submodule"` + echo('Hello World', 'sub/submodule-file') + `git submodule foreach "git add submodule-file" < #{File::NULL}` + `git submodule foreach "git config --local commit.gpgsign false"` + `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` + echo('Hello Again', 'tracked-file') + `git add sub tracked-file` + example.run + end + end + + it 'keeps staged submodule change' do + `git config diff.submodule short` + expect { subject }.to_not change { + (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? + }.from(false) + end + + it 'keeps staged file change' do + subject + `git show :tracked-file`.should == "Hello Again\n" + end + end + + context 'when a submodule removal was staged' do + around do |example| + submodule = repo do + `git commit --allow-empty -m "Initial commit"` + end + + repo do + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git commit -m "Add submodule"` + `git rm sub` + example.run + end + end + + it 'does not leave behind an empty submodule directory' do + subject + File.exist?('sub').should == false + end + end + end + + describe '#modified_files' do + subject { context.modified_files } + + before do + context.stub(:amendment?).and_return(false) + end + + it 'does not include submodules' do + submodule = repo do + touch 'foo' + `git add foo` + `git commit -m "Initial commit"` + end + + repo do + `git -c protocol.file.allow=always submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` + expect(subject).to_not include File.expand_path('test-sub') + end + end + + context 'when no files were staged' do + around do |example| + repo do + example.run + end + end + + it { should be_empty } + end + + context 'when files were added' do + around do |example| + repo do + touch('some-file') + `git add some-file` + example.run + end + end + + it { should == [File.expand_path('some-file')] } + end + + context 'when files were modified' do + around do |example| + repo do + touch('some-file') + `git add some-file` + `git commit -m "Initial commit"` + echo('Hello', 'some-file') + `git add some-file` + example.run + end + end + + it { should == [File.expand_path('some-file')] } + end + + context 'when files were deleted' do + around do |example| + repo do + touch('some-file') + `git add some-file` + `git commit -m "Initial commit"` + `git rm some-file` + example.run + end + end + + it { should be_empty } + end + + context 'when amending last commit' do + around do |example| + repo do + touch('some-file') + `git add some-file` + `git commit -m "Initial commit"` + touch('other-file') + `git add other-file` + example.run + end + end + + before do + context.stub(:amendment?).and_return(true) + end + + it { should =~ [File.expand_path('some-file'), File.expand_path('other-file')] } + end + + context 'when renaming a file during an amendment' do + around do |example| + repo do + `git commit --allow-empty -m "Initial commit"` + touch 'some-file' + `git add some-file` + `git commit -m "Add file"` + `git mv some-file renamed-file` + example.run + end + end + + before do + context.stub(:amendment?).and_return(true) + end + + it 'does not include the old file name in the list of modified files' do + subject.should_not include File.expand_path('some-file') + end + end + + # Git cannot track Windows symlinks + unless Overcommit::OS.windows? + context 'when changing a symlink to a directory during an amendment' do + around do |example| + repo do + `git commit --allow-empty -m "Initial commit"` + FileUtils.mkdir 'some-directory' + symlink('some-directory', 'some-symlink') + `git add some-symlink some-directory` + `git commit -m "Add file"` + `git rm some-symlink` + FileUtils.mkdir 'some-symlink' + touch File.join('some-symlink', 'another-file') + `git add some-symlink` + example.run + end + end + + before do + context.stub(:amendment?).and_return(true) + end + + it 'does not include the directory in the list of modified files' do + subject.should_not include File.expand_path('some-symlink') + end + end + + context 'when breaking a symlink during an amendment' do + around do |example| + repo do + `git commit --allow-empty -m "Initial commit"` + FileUtils.mkdir 'some-directory' + touch File.join('some-directory', 'some-file') + symlink('some-directory', 'some-symlink') + `git add some-symlink some-directory` + `git commit -m "Add file"` + `git rm -rf some-directory` + example.run + end + end + + before do + context.stub(:amendment?).and_return(true) + end + + it 'still includes the broken symlink in the list of modified files' do + subject.should include File.expand_path('some-symlink') + end + end + end + end + + describe '#modified_lines_in_file' do + let(:modified_file) { 'some-file' } + subject { context.modified_lines_in_file(modified_file) } + + before do + context.stub(:amendment?).and_return(false) + end + + context 'when file contains a trailing newline' do + around do |example| + repo do + File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } + `git add #{modified_file}` + example.run + end + end + + it { should == Set.new(1..3) } + end + + context 'when file does not contain a trailing newline' do + around do |example| + repo do + File.open(modified_file, 'w') do |f| + (1..2).each { |i| f.write("#{i}\n") } + f.write(3) + end + + `git add #{modified_file}` + example.run + end + end + + it { should == Set.new(1..3) } + end + + context 'when amending last commit' do + around do |example| + repo do + File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } + `git add #{modified_file}` + `git commit -m "Add files"` + File.open(modified_file, 'a') { |f| f.puts 4 } + `git add #{modified_file}` + example.run + end + end + + before do + context.stub(:amendment?).and_return(true) + end + + it { should == Set.new(1..4) } + end + end end diff --git a/spec/overcommit/hook_context/diff_spec.rb b/spec/overcommit/hook_context/diff_spec.rb new file mode 100644 index 00000000..0c712fbc --- /dev/null +++ b/spec/overcommit/hook_context/diff_spec.rb @@ -0,0 +1,143 @@ +# frozen_string_literal: true + +require 'spec_helper' +require 'overcommit/hook_context/diff' + +describe Overcommit::HookContext::Diff do + let(:config) { double('config') } + let(:args) { [] } + let(:input) { double('input') } + let(:context) { described_class.new(config, args, input, diff: 'master') } + + describe '#modified_files' do + subject { context.modified_files } + + context 'when repo contains no files' do + around do |example| + repo do + `git commit --allow-empty -m "Initial commit"` + `git checkout -b other-branch 2>&1` + example.run + end + end + + it { should be_empty } + end + + context 'when the repo contains files that are unchanged from the ref' do + around do |example| + repo do + touch('some-file') + `git add some-file` + touch('some-other-file') + `git add some-other-file` + `git commit -m "Add files"` + `git checkout -b other-branch 2>&1` + example.run + end + end + + it { should be_empty } + end + + context 'when repo contains files that have been changed from the ref' do + around do |example| + repo do + touch('some-file') + `git add some-file` + touch('some-other-file') + `git add some-other-file` + `git commit -m "Add files"` + `git checkout -b other-branch 2>&1` + File.open('some-file', 'w') { |f| f.write("hello\n") } + `git add some-file` + `git commit -m "Edit file"` + example.run + end + end + + it { should == %w[some-file].map { |file| File.expand_path(file) } } + end + + context 'when repo contains submodules' do + around do |example| + submodule = repo do + touch 'foo' + `git add foo` + `git commit -m "Initial commit"` + end + + repo do + `git submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` + `git commit --allow-empty -m "Initial commit"` + `git checkout -b other-branch 2>&1` + example.run + end + end + + it { should_not include File.expand_path('test-sub') } + end + end + + describe '#modified_lines_in_file' do + let(:modified_file) { 'some-file' } + subject { context.modified_lines_in_file(modified_file) } + + context 'when file contains a trailing newline' do + around do |example| + repo do + touch(modified_file) + `git add #{modified_file}` + `git commit -m "Add file"` + `git checkout -b other-branch 2>&1` + File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } + `git add #{modified_file}` + `git commit -m "Edit file"` + example.run + end + end + + it { should == Set.new(1..3) } + end + + context 'when file does not contain a trailing newline' do + around do |example| + repo do + touch(modified_file) + `git add #{modified_file}` + `git commit -m "Add file"` + `git checkout -b other-branch 2>&1` + File.open(modified_file, 'w') do |f| + (1..2).each { |i| f.write("#{i}\n") } + f.write(3) + end + `git add #{modified_file}` + `git commit -m "Edit file"` + example.run + end + end + + it { should == Set.new(1..3) } + end + end + + describe '#hook_type_name' do + subject { context.hook_type_name } + + it { should == 'pre_commit' } + end + + describe '#hook_script_name' do + subject { context.hook_script_name } + + it { should == 'pre-commit' } + end + + describe '#initial_commit?' do + subject { context.initial_commit? } + + before { Overcommit::GitRepo.stub(:initial_commit?).and_return(true) } + + it { should == true } + end +end diff --git a/spec/overcommit/hook_context/pre_commit_spec.rb b/spec/overcommit/hook_context/pre_commit_spec.rb index dae42e66..719982a1 100644 --- a/spec/overcommit/hook_context/pre_commit_spec.rb +++ b/spec/overcommit/hook_context/pre_commit_spec.rb @@ -207,7 +207,7 @@ end repo do - `git submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` @@ -383,7 +383,7 @@ end repo do - `git submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` @@ -409,7 +409,7 @@ end repo do - `git submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` @@ -441,7 +441,7 @@ end repo do - `git submodule add #{submodule} sub > #{File::NULL} 2>&1` + `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` `git rm sub` example.run @@ -470,7 +470,7 @@ end repo do - `git submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` + `git -c protocol.file.allow=always submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` expect(subject).to_not include File.expand_path('test-sub') end end diff --git a/spec/overcommit/installer_spec.rb b/spec/overcommit/installer_spec.rb index c7cfb05e..5283b2b6 100644 --- a/spec/overcommit/installer_spec.rb +++ b/spec/overcommit/installer_spec.rb @@ -231,8 +231,8 @@ def hook_files_installed?(hooks_dir) context 'which has an external git dir' do let(:submodule) { File.join(target, 'submodule') } before do - system 'git', 'submodule', 'add', target, 'submodule', - chdir: target, out: :close, err: :close + system 'git', '-c', 'protocol.file.allow=always', 'submodule', 'add', target, + 'submodule', chdir: target, out: :close, err: :close end let(:submodule_git_file) { File.join(submodule, '.git') } let(:submodule_git_dir) do diff --git a/spec/overcommit/utils_spec.rb b/spec/overcommit/utils_spec.rb index 3be592ff..acaf3eba 100644 --- a/spec/overcommit/utils_spec.rb +++ b/spec/overcommit/utils_spec.rb @@ -119,17 +119,17 @@ describe '.supported_hook_types' do subject { described_class.supported_hook_types } - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength it { should =~ %w[commit-msg pre-commit post-checkout post-commit post-merge post-rewrite pre-push pre-rebase prepare-commit-msg] } - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength end describe '.supported_hook_type_classes' do subject { described_class.supported_hook_type_classes } - # rubocop:disable Metrics/LineLength + # rubocop:disable Layout/LineLength it { should =~ %w[CommitMsg PreCommit PostCheckout PostCommit PostMerge PostRewrite PrePush PreRebase PrepareCommitMsg] } - # rubocop:enable Metrics/LineLength + # rubocop:enable Layout/LineLength end describe '.parent_command' do @@ -190,7 +190,7 @@ it 'invokes CommandSplitter.execute' do Overcommit::CommandSplitter. should_receive(:execute). - with(arguments, args: splittable_args). + with(arguments, { args: splittable_args }). and_return(double(status: 0, stdout: '', stderr: '')) subject end @@ -215,7 +215,7 @@ end it 'executes the command' do - wait_until { subject.exited? } # Make sure process terminated before checking + wait_until(timeout: 5) { subject.exited? } # Make sure process terminated before checking File.exist?('some-file').should == true end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f0d93a7f..b6a24d82 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,23 @@ # frozen_string_literal: true -if ENV['TRAVIS'] - # When running in Travis, report coverage stats to Coveralls. - require 'coveralls' - Coveralls.wear! -else - # Otherwise render coverage information in coverage/index.html and display - # coverage percentage in the console. - require 'simplecov' +require 'bundler' +require 'simplecov' +SimpleCov.start do + add_filter 'bin/' + add_filter 'libexec/' + add_filter 'spec/' + add_filter 'template-dir/' + + if ENV['CI'] + require 'simplecov-lcov' + + SimpleCov::Formatter::LcovFormatter.config do |c| + c.report_with_single_file = true + c.single_report_path = 'coverage/lcov.info' + end + + formatter SimpleCov::Formatter::LcovFormatter + end end require 'overcommit' @@ -27,7 +37,7 @@ each { |f| require f } end -Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f } +Dir[File.dirname(__FILE__) + '/support/**/*.rb'].sort.each { |f| require f } RSpec.configure do |config| config.include GitSpecHelpers diff --git a/spec/support/git_spec_helpers.rb b/spec/support/git_spec_helpers.rb index 23e46c51..28eb94da 100644 --- a/spec/support/git_spec_helpers.rb +++ b/spec/support/git_spec_helpers.rb @@ -13,7 +13,7 @@ module GitSpecHelpers # @return [String] path of the repository def repo(options = {}) directory('some-repo') do - create_cmd = %w[git init] + create_cmd = %w[git init --initial-branch=master] create_cmd += ['--template', options[:template_dir]] if options[:template_dir] create_cmd += ['--separate-git-dir', options[:git_dir]] if options[:git_dir] diff --git a/spec/support/shell_helpers.rb b/spec/support/shell_helpers.rb index d035e13a..786dac63 100644 --- a/spec/support/shell_helpers.rb +++ b/spec/support/shell_helpers.rb @@ -26,6 +26,7 @@ def wait_until(options = {}) Timeout.timeout(options.fetch(:timeout) { 1 }) do loop do return if yield + sleep options.fetch(:check_interval) { 0.1 } end end diff --git a/template-dir/hooks/commit-msg b/template-dir/hooks/commit-msg index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/commit-msg +++ b/template-dir/hooks/commit-msg @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/overcommit-hook b/template-dir/hooks/overcommit-hook index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/overcommit-hook +++ b/template-dir/hooks/overcommit-hook @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/post-checkout b/template-dir/hooks/post-checkout index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/post-checkout +++ b/template-dir/hooks/post-checkout @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/post-commit b/template-dir/hooks/post-commit index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/post-commit +++ b/template-dir/hooks/post-commit @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/post-merge b/template-dir/hooks/post-merge index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/post-merge +++ b/template-dir/hooks/post-merge @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/post-rewrite b/template-dir/hooks/post-rewrite index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/post-rewrite +++ b/template-dir/hooks/post-rewrite @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/pre-commit b/template-dir/hooks/pre-commit index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/pre-commit +++ b/template-dir/hooks/pre-commit @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/pre-push b/template-dir/hooks/pre-push index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/pre-push +++ b/template-dir/hooks/pre-push @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/pre-rebase b/template-dir/hooks/pre-rebase index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/pre-rebase +++ b/template-dir/hooks/pre-rebase @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end diff --git a/template-dir/hooks/prepare-commit-msg b/template-dir/hooks/prepare-commit-msg index 8b8c118c..87ffeb58 100755 --- a/template-dir/hooks/prepare-commit-msg +++ b/template-dir/hooks/prepare-commit-msg @@ -27,17 +27,18 @@ if hook_type == 'overcommit-hook' end # Check if Overcommit should invoke a Bundler context for loading gems -require 'yaml' -# rubocop:disable Style/RescueModifier -if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil +File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ +gemfile = Regexp.last_match(1) + +if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup - rescue Bundler::BundlerError => ex - puts "Problem loading '#{gemfile}': #{ex.message}" - puts "Try running:\nbundle install --gemfile=#{gemfile}" if ex.is_a?(Bundler::GemNotFound) + 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 end end @@ -80,37 +81,37 @@ begin status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR -rescue Overcommit::Exceptions::ConfigurationError => error - puts error +rescue Overcommit::Exceptions::ConfigurationError => e + puts e exit 78 # EX_CONFIG -rescue Overcommit::Exceptions::HookContextLoadError => error - puts error +rescue Overcommit::Exceptions::HookContextLoadError => e + puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, - Overcommit::Exceptions::InvalidHookDefinition => error - puts error.message - puts error.backtrace + Overcommit::Exceptions::InvalidHookDefinition => e + puts e.message + puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, - Overcommit::Exceptions::HookCleanupFailed => error - puts error.message + Overcommit::Exceptions::HookCleanupFailed => e + puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel -rescue Overcommit::Exceptions::InvalidGitRepo => error - puts error +rescue Overcommit::Exceptions::InvalidGitRepo => e + puts e exit 64 # EX_USAGE -rescue Overcommit::Exceptions::ConfigurationSignatureChanged => error - puts error +rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e + puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 -rescue StandardError => error - puts error.message - puts error.backtrace +rescue StandardError => e + puts e.message + puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end