diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index b1879f09..8cf8b4a0 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -1,9 +1,9 @@
 name: Lint
 on:
   push:
-    branches: [master]
+    branches: [main]
   pull_request:
-    branches: [master]
+    branches: [main]
 
 jobs:
   overcommit:
@@ -11,12 +11,12 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v4
 
       - name: Set up Ruby
         uses: ruby/setup-ruby@v1
         with:
-          ruby-version: 2.7
+          ruby-version: 3.3
           bundler-cache: true
 
       - name: Prepare environment
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 848db60c..0e2aac20 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,9 +1,9 @@
 name: Tests
 on:
   push:
-    branches: [master]
+    branches: [main]
   pull_request:
-    branches: [master]
+    branches: [main]
 
 jobs:
   rspec:
@@ -14,22 +14,21 @@ jobs:
       fail-fast: false
       matrix:
         ruby-version:
-          - '2.6'
-          - '2.7'
-          - '3.0'
-          - '3.1'
+          - "2.6"
+          - "2.7"
+          - "3.0"
+          - "3.1"
+          - "3.2"
+          - "3.3"
         os:
           - ubuntu
-          - windows
-
-        # Tempfile behavior has changed on Ruby 3.1 such that tests
-        # fail with permission denied. Would welcome a PR with a fix.
-        exclude:
-          - ruby-version: '3.1'
-            os: windows
+          # 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@v2
+      - uses: actions/checkout@v4
 
       - name: Set up Ruby ${{ matrix.ruby-version }}
         uses: ruby/setup-ruby@v1
@@ -44,7 +43,7 @@ jobs:
           bundle exec rspec
 
       - name: Code coverage reporting
-        uses: coverallsapp/github-action@master
+        uses: coverallsapp/github-action@v2
         with:
           github-token: ${{ secrets.github_token }}
           flag-name: ruby${{ matrix.ruby-version }}-${{ matrix.os }}
@@ -56,7 +55,7 @@ jobs:
 
     steps:
       - name: Finalize code coverage report
-        uses: coverallsapp/github-action@master
+        uses: coverallsapp/github-action@v2
         with:
           github-token: ${{ secrets.github_token }}
           parallel-finished: true
diff --git a/.rubocop.yml b/.rubocop.yml
index 35002594..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
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/CHANGELOG.md b/CHANGELOG.md
index aa326ff2..ad1cb477 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,54 @@
 # 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.
diff --git a/Gemfile b/Gemfile
index ff22be0c..49dc7923 100644
--- a/Gemfile
+++ b/Gemfile
@@ -11,7 +11,11 @@ gem 'rspec', '~> 3.0'
 gem 'simplecov', '~> 0.21.0'
 gem 'simplecov-lcov', '~> 0.8.0'
 
-# Pin RuboCop for Travis builds.
-gem 'rubocop', '0.82.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 0464e604..a0d46726 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 [![Gem Version](https://badge.fury.io/rb/overcommit.svg)](https://badge.fury.io/rb/overcommit)
-[![Build Status](https://github.com/sds/overcommit/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/sds/overcommit/actions/workflows/tests.yml/badge.svg?branch=master)
-[![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)
 
 <p align="center">
   <img src="https://raw.githubusercontent.com/sds/overcommit/master/logo/horizontal.png" width="65%" alt="Overcommit Logo"/>
@@ -17,46 +17,49 @@ 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:
+This project aims to support the following Ruby runtimes on \*nix (and best effort on Windows):
 
-* Ruby 2.4+
-
-### 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
 
@@ -128,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 <ref>`            | Run pre-commit hook against all changed files relative to `<ref>`
 `-t`/`--template-dir`     | Print location of template directory
 `-h`/`--help`             | Show command-line flag documentation
 `-v`/`--version`          | Show version
@@ -208,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
@@ -557,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)
diff --git a/config/default.yml b/config/default.yml
index bfd91e08..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
@@ -536,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'
@@ -699,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'
@@ -780,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'
@@ -1316,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'
diff --git a/lib/overcommit/cli.rb b/lib/overcommit/cli.rb
index bc2ec994..dafc545a 100644
--- a/lib/overcommit/cli.rb
+++ b/lib/overcommit/cli.rb
@@ -9,6 +9,7 @@ module Overcommit
   class CLI # rubocop:disable Metrics/ClassLength
     def initialize(arguments, input, logger)
       @arguments = arguments
+      @cli_options = {}
       @input     = input
       @log       = logger
       @options   = {}
@@ -28,6 +29,8 @@ def run
         sign
       when :run_all
         run_all
+      when :diff
+        diff
       end
     rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e
       puts e
@@ -45,7 +48,7 @@ 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
@@ -98,6 +101,11 @@ def add_installation_options(opts)
         @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
 
     def add_other_options(opts)
@@ -124,15 +132,13 @@ def install_or_uninstall
       end
 
       @options[:targets].each do |target|
-        begin
-          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
+        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
 
@@ -211,6 +217,19 @@ def run_all
       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)
+      runner  = Overcommit::HookRunner.new(config, log, context, printer)
+
+      status = runner.run
+
+      halt(status ? 0 : 65)
+    end
+
     # Used for ease of stubbing in tests
     def halt(status = 0)
       exit status
diff --git a/lib/overcommit/configuration_loader.rb b/lib/overcommit/configuration_loader.rb
index 77a08549..8fd68a01 100644
--- a/lib/overcommit/configuration_loader.rb
+++ b/lib/overcommit/configuration_loader.rb
@@ -53,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
@@ -64,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)
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/git_config.rb b/lib/overcommit/git_config.rb
index 392dd9da..c1243861 100644
--- a/lib/overcommit/git_config.rb
+++ b/lib/overcommit/git_config.rb
@@ -17,7 +17,7 @@ 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/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/text_width.rb b/lib/overcommit/hook/commit_msg/text_width.rb
index 63addbcf..52de3bd7 100644
--- a/lib/overcommit/hook/commit_msg/text_width.rb
+++ b/lib/overcommit/hook/commit_msg/text_width.rb
@@ -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/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/erb_lint.rb b/lib/overcommit/hook/pre_commit/erb_lint.rb
index a903b10b..ae5af164 100644
--- a/lib/overcommit/hook/pre_commit/erb_lint.rb
+++ b/lib/overcommit/hook/pre_commit/erb_lint.rb
@@ -12,7 +12,7 @@ def run
       return :pass if result.success?
 
       extract_messages(
-        result.stdout.split("\n\n")[1..-1],
+        result.stdout.split("\n\n")[1..],
         MESSAGE_REGEX
       )
     end
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}" },
           /^(?<file>(?:\w:)?[^:]+): line (?<line>\d+)/
         )
       end.flatten
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/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+(?<file>.+)$/.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/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_schema_up_to_date.rb b/lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb
index 13d7932f..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
@@ -34,6 +34,12 @@ def run # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplex
 
     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/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 = /^(?<file>[^:]+):(?<line>\d+): (?<message>.*)$/.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/stylelint.rb b/lib/overcommit/hook/pre_commit/stylelint.rb
index b129cbd2..aae26f08 100644
--- a/lib/overcommit/hook/pre_commit/stylelint.rb
+++ b/lib/overcommit/hook/pre_commit/stylelint.rb
@@ -12,7 +12,7 @@ class Stylelint < Base
 
     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(
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_syntax.rb b/lib/overcommit/hook/pre_commit/yaml_syntax.rb
index 493e9c5a..83ff6789 100644
--- a/lib/overcommit/hook/pre_commit/yaml_syntax.rb
+++ b/lib/overcommit/hook/pre_commit/yaml_syntax.rb
@@ -7,18 +7,25 @@ def run
       messages = []
 
       applicable_files.each do |file|
+        YAML.load_file(file, aliases: true)
+      rescue ArgumentError
         begin
-          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
+          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_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/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/replace_branch.rb b/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb
index 7a40ed69..8852b0b5 100644
--- a/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb
+++ b/lib/overcommit/hook/prepare_commit_msg/replace_branch.rb
@@ -11,9 +11,9 @@ module Overcommit::Hook::PrepareCommitMsg
   # For instance, if your current branch is `123-topic` then this config
   #
   #    branch_pattern: '(\d+)-(\w+)'
-  #    replacement_text: '[#\1]'
+  #    replacement_text: '[#\1] '
   #
-  # would make this hook prepend commit messages with `[#123]`.
+  # would make this hook prepend commit messages with `[#123] `.
   #
   # Similarly, a replacement text of `[\1][\2]` would result in `[123][topic]`.
   #
@@ -53,7 +53,7 @@ def new_template
       @new_template ||=
         begin
           curr_branch = Overcommit::GitRepo.current_branch
-          curr_branch.gsub(branch_pattern, replacement_text).strip
+          curr_branch.gsub(branch_pattern, replacement_text)
         end
     end
 
@@ -69,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
@@ -85,7 +85,7 @@ def skipped_commit_types
     end
 
     def skip?
-      skipped_commit_types.include?(commit_message_source)
+      super || skipped_commit_types.include?(commit_message_source)
     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_context.rb b/lib/overcommit/hook_context.rb
index acdff1bb..5863ed94 100644
--- a/lib/overcommit/hook_context.rb
+++ b/lib/overcommit/hook_context.rb
@@ -2,13 +2,13 @@
 
 # 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)
+    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.
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<String>]
     # @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/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/utils/messages_utils.rb b/lib/overcommit/utils/messages_utils.rb
index c92a6010..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)
diff --git a/lib/overcommit/version.rb b/lib/overcommit/version.rb
index e8f6bb39..872c5327 100644
--- a/lib/overcommit/version.rb
+++ b/lib/overcommit/version.rb
@@ -2,5 +2,5 @@
 
 # Defines the gem version.
 module Overcommit
-  VERSION = '0.59.1'
+  VERSION = '0.67.1'
 end
diff --git a/overcommit.gemspec b/overcommit.gemspec
index ef9f24fc..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,9 +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', '< 5'
+  s.add_dependency          'childprocess', '>= 0.6.3', '< 6'
   s.add_dependency          'iniparse', '~> 1.4'
-  s.add_dependency          'rexml', '~> 3.2'
+  s.add_dependency          'rexml', '>= 3.3.9'
 end
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 7a0175da..a6a6a4ba 100644
--- a/spec/integration/gemfile_option_spec.rb
+++ b/spec/integration/gemfile_option_spec.rb
@@ -3,99 +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.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
+  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_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`
 
+    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/resolving_cherry_pick_conflict_spec.rb b/spec/integration/resolving_cherry_pick_conflict_spec.rb
index 58e22e65..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:
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/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 128737c3..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
 
@@ -282,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
@@ -327,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
@@ -343,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 }
@@ -369,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`
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/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/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 `<top (required)>'
+            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 `<main>'
+        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 <top (required)>'
+
+            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 <top (required)>'
+
+          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/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_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/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/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 3844f77a..e355517f 100644
--- a/spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb
+++ b/spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb
@@ -62,6 +62,34 @@ def remove_file(name)
       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] ') }
+
+        it 'prepends the replacement text, including the space' do
+          expect(File.read('COMMIT_EDITMSG')).to eq("[123] \n")
+        end
+      end
+
+      context 'when skip_if exits with a zero status' do
+        let(:config) { new_config('skip_if' => ['bash', '-c', 'exit 0']) }
+
+        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
diff --git a/spec/overcommit/hook_context/commit_msg_spec.rb b/spec/overcommit/hook_context/commit_msg_spec.rb
index 2aa68516..058f1508 100644
--- a/spec/overcommit/hook_context/commit_msg_spec.rb
+++ b/spec/overcommit/hook_context/commit_msg_spec.rb
@@ -298,7 +298,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}`
@@ -474,7 +474,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}`
@@ -500,7 +500,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}`
@@ -532,7 +532,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
@@ -561,7 +561,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/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/template-dir/hooks/commit-msg b/template-dir/hooks/commit-msg
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/commit-msg
+++ b/template-dir/hooks/commit-msg
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/overcommit-hook b/template-dir/hooks/overcommit-hook
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/overcommit-hook
+++ b/template-dir/hooks/overcommit-hook
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/post-checkout b/template-dir/hooks/post-checkout
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/post-checkout
+++ b/template-dir/hooks/post-checkout
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/post-commit b/template-dir/hooks/post-commit
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/post-commit
+++ b/template-dir/hooks/post-commit
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/post-merge b/template-dir/hooks/post-merge
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/post-merge
+++ b/template-dir/hooks/post-merge
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/post-rewrite b/template-dir/hooks/post-rewrite
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/post-rewrite
+++ b/template-dir/hooks/post-rewrite
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/pre-commit b/template-dir/hooks/pre-commit
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/pre-commit
+++ b/template-dir/hooks/pre-commit
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/pre-push b/template-dir/hooks/pre-push
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/pre-push
+++ b/template-dir/hooks/pre-push
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/pre-rebase b/template-dir/hooks/pre-rebase
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/pre-rebase
+++ b/template-dir/hooks/pre-rebase
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile
diff --git a/template-dir/hooks/prepare-commit-msg b/template-dir/hooks/prepare-commit-msg
index 7f8023de..87ffeb58 100755
--- a/template-dir/hooks/prepare-commit-msg
+++ b/template-dir/hooks/prepare-commit-msg
@@ -27,14 +27,8 @@ if hook_type == 'overcommit-hook'
 end
 
 # Check if Overcommit should invoke a Bundler context for loading gems
-require 'yaml'
-# rubocop:disable Style/RescueModifier
-gemfile =
-  begin
-    YAML.load_file('.overcommit.yml', aliases: true)['gemfile']
-  rescue ArgumentError
-    YAML.load_file('.overcommit.yml')['gemfile']
-  end rescue nil
+File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/
+gemfile = Regexp.last_match(1)
 
 if gemfile
   ENV['BUNDLE_GEMFILE'] = gemfile