Repository: sds/overcommit Branch: main Commit: 11838c67483b Files: 458 Total size: 877.0 KB Directory structure: gitextract_l2gjobnw/ ├── .editorconfig ├── .git-hooks/ │ └── pre_commit/ │ └── master_hooks_match.rb ├── .github/ │ └── workflows/ │ ├── lint.yml │ └── tests.yml ├── .gitignore ├── .overcommit.yml ├── .rubocop.yml ├── .rubocop_todo.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gemfile ├── MIT-LICENSE ├── README.md ├── bin/ │ └── overcommit ├── config/ │ ├── default.yml │ └── starter.yml ├── lib/ │ ├── overcommit/ │ │ ├── cli.rb │ │ ├── command_splitter.rb │ │ ├── configuration.rb │ │ ├── configuration_loader.rb │ │ ├── configuration_validator.rb │ │ ├── constants.rb │ │ ├── exceptions.rb │ │ ├── git_config.rb │ │ ├── git_repo.rb │ │ ├── git_version.rb │ │ ├── hook/ │ │ │ ├── base.rb │ │ │ ├── commit_msg/ │ │ │ │ ├── base.rb │ │ │ │ ├── capitalized_subject.rb │ │ │ │ ├── empty_message.rb │ │ │ │ ├── gerrit_change_id.rb │ │ │ │ ├── hard_tabs.rb │ │ │ │ ├── message_format.rb │ │ │ │ ├── russian_novel.rb │ │ │ │ ├── single_line_subject.rb │ │ │ │ ├── spell_check.rb │ │ │ │ ├── text_width.rb │ │ │ │ └── trailing_period.rb │ │ │ ├── post_checkout/ │ │ │ │ ├── base.rb │ │ │ │ ├── bower_install.rb │ │ │ │ ├── bundle_install.rb │ │ │ │ ├── composer_install.rb │ │ │ │ ├── index_tags.rb │ │ │ │ ├── npm_install.rb │ │ │ │ ├── submodule_status.rb │ │ │ │ └── yarn_install.rb │ │ │ ├── post_commit/ │ │ │ │ ├── base.rb │ │ │ │ ├── bower_install.rb │ │ │ │ ├── bundle_install.rb │ │ │ │ ├── commitplease.rb │ │ │ │ ├── composer_install.rb │ │ │ │ ├── git_guilt.rb │ │ │ │ ├── index_tags.rb │ │ │ │ ├── npm_install.rb │ │ │ │ ├── submodule_status.rb │ │ │ │ └── yarn_install.rb │ │ │ ├── post_merge/ │ │ │ │ ├── base.rb │ │ │ │ ├── bower_install.rb │ │ │ │ ├── bundle_install.rb │ │ │ │ ├── composer_install.rb │ │ │ │ ├── index_tags.rb │ │ │ │ ├── npm_install.rb │ │ │ │ ├── submodule_status.rb │ │ │ │ └── yarn_install.rb │ │ │ ├── post_rewrite/ │ │ │ │ ├── base.rb │ │ │ │ ├── bower_install.rb │ │ │ │ ├── bundle_install.rb │ │ │ │ ├── composer_install.rb │ │ │ │ ├── index_tags.rb │ │ │ │ ├── npm_install.rb │ │ │ │ ├── submodule_status.rb │ │ │ │ └── yarn_install.rb │ │ │ ├── pre_commit/ │ │ │ │ ├── author_email.rb │ │ │ │ ├── author_name.rb │ │ │ │ ├── base.rb │ │ │ │ ├── berksfile_check.rb │ │ │ │ ├── broken_symlinks.rb │ │ │ │ ├── bundle_audit.rb │ │ │ │ ├── bundle_check.rb │ │ │ │ ├── bundle_outdated.rb │ │ │ │ ├── case_conflicts.rb │ │ │ │ ├── chamber_compare.rb │ │ │ │ ├── chamber_security.rb │ │ │ │ ├── chamber_verification.rb │ │ │ │ ├── code_spell_check.rb │ │ │ │ ├── coffee_lint.rb │ │ │ │ ├── cook_style.rb │ │ │ │ ├── credo.rb │ │ │ │ ├── css_lint.rb │ │ │ │ ├── dart_analyzer.rb │ │ │ │ ├── dogma.rb │ │ │ │ ├── erb_lint.rb │ │ │ │ ├── es_lint.rb │ │ │ │ ├── execute_permissions.rb │ │ │ │ ├── fasterer.rb │ │ │ │ ├── file_size.rb │ │ │ │ ├── fix_me.rb │ │ │ │ ├── flay.rb │ │ │ │ ├── foodcritic.rb │ │ │ │ ├── forbidden_branches.rb │ │ │ │ ├── ginkgo_focus.rb │ │ │ │ ├── go_fmt.rb │ │ │ │ ├── go_lint.rb │ │ │ │ ├── go_vet.rb │ │ │ │ ├── golangci_lint.rb │ │ │ │ ├── hadolint.rb │ │ │ │ ├── haml_lint.rb │ │ │ │ ├── hard_tabs.rb │ │ │ │ ├── hlint.rb │ │ │ │ ├── html_hint.rb │ │ │ │ ├── html_tidy.rb │ │ │ │ ├── image_optim.rb │ │ │ │ ├── java_checkstyle.rb │ │ │ │ ├── js_hint.rb │ │ │ │ ├── js_lint.rb │ │ │ │ ├── jscs.rb │ │ │ │ ├── jsl.rb │ │ │ │ ├── json_syntax.rb │ │ │ │ ├── kt_lint.rb │ │ │ │ ├── license_finder.rb │ │ │ │ ├── license_header.rb │ │ │ │ ├── line_endings.rb │ │ │ │ ├── local_paths_in_gemfile.rb │ │ │ │ ├── mdl.rb │ │ │ │ ├── merge_conflicts.rb │ │ │ │ ├── mix_format.rb │ │ │ │ ├── nginx_test.rb │ │ │ │ ├── pep257.rb │ │ │ │ ├── pep8.rb │ │ │ │ ├── php_cs.rb │ │ │ │ ├── php_cs_fixer.rb │ │ │ │ ├── php_lint.rb │ │ │ │ ├── php_stan.rb │ │ │ │ ├── pronto.rb │ │ │ │ ├── puppet_lint.rb │ │ │ │ ├── puppet_metadata_json_lint.rb │ │ │ │ ├── pycodestyle.rb │ │ │ │ ├── pydocstyle.rb │ │ │ │ ├── pyflakes.rb │ │ │ │ ├── pylint.rb │ │ │ │ ├── python_flake8.rb │ │ │ │ ├── r_spec.rb │ │ │ │ ├── rails_best_practices.rb │ │ │ │ ├── rails_schema_up_to_date.rb │ │ │ │ ├── rake_target.rb │ │ │ │ ├── reek.rb │ │ │ │ ├── rst_lint.rb │ │ │ │ ├── rubo_cop.rb │ │ │ │ ├── ruby_lint.rb │ │ │ │ ├── ruby_syntax.rb │ │ │ │ ├── scalariform.rb │ │ │ │ ├── scalastyle.rb │ │ │ │ ├── scss_lint.rb │ │ │ │ ├── semi_standard.rb │ │ │ │ ├── shell_check.rb │ │ │ │ ├── slim_lint.rb │ │ │ │ ├── solargraph.rb │ │ │ │ ├── sorbet.rb │ │ │ │ ├── sqlint.rb │ │ │ │ ├── standard.rb │ │ │ │ ├── stylelint.rb │ │ │ │ ├── swift_lint.rb │ │ │ │ ├── terraform_format.rb │ │ │ │ ├── trailing_whitespace.rb │ │ │ │ ├── travis_lint.rb │ │ │ │ ├── ts_lint.rb │ │ │ │ ├── vint.rb │ │ │ │ ├── w3c_css.rb │ │ │ │ ├── w3c_html.rb │ │ │ │ ├── xml_lint.rb │ │ │ │ ├── xml_syntax.rb │ │ │ │ ├── yaml_lint.rb │ │ │ │ ├── yaml_syntax.rb │ │ │ │ ├── yard_coverage.rb │ │ │ │ └── yarn_check.rb │ │ │ ├── pre_push/ │ │ │ │ ├── base.rb │ │ │ │ ├── brakeman.rb │ │ │ │ ├── cargo_test.rb │ │ │ │ ├── flutter_test.rb │ │ │ │ ├── go_test.rb │ │ │ │ ├── golangci_lint.rb │ │ │ │ ├── minitest.rb │ │ │ │ ├── mix_test.rb │ │ │ │ ├── php_unit.rb │ │ │ │ ├── pronto.rb │ │ │ │ ├── protected_branches.rb │ │ │ │ ├── pub_test.rb │ │ │ │ ├── pytest.rb │ │ │ │ ├── python_nose.rb │ │ │ │ ├── r_spec.rb │ │ │ │ ├── rake_target.rb │ │ │ │ └── test_unit.rb │ │ │ ├── pre_rebase/ │ │ │ │ ├── base.rb │ │ │ │ └── merged_commits.rb │ │ │ ├── prepare_commit_msg/ │ │ │ │ ├── base.rb │ │ │ │ └── replace_branch.rb │ │ │ └── shared/ │ │ │ ├── bower_install.rb │ │ │ ├── bundle_install.rb │ │ │ ├── composer_install.rb │ │ │ ├── index_tags.rb │ │ │ ├── npm_install.rb │ │ │ ├── pronto.rb │ │ │ ├── r_spec.rb │ │ │ ├── rake_target.rb │ │ │ ├── submodule_status.rb │ │ │ └── yarn_install.rb │ │ ├── hook_context/ │ │ │ ├── base.rb │ │ │ ├── commit_msg.rb │ │ │ ├── diff.rb │ │ │ ├── helpers/ │ │ │ │ ├── file_modifications.rb │ │ │ │ └── stash_unstaged_changes.rb │ │ │ ├── post_checkout.rb │ │ │ ├── post_commit.rb │ │ │ ├── post_merge.rb │ │ │ ├── post_rewrite.rb │ │ │ ├── pre_commit.rb │ │ │ ├── pre_push.rb │ │ │ ├── pre_rebase.rb │ │ │ ├── prepare_commit_msg.rb │ │ │ └── run_all.rb │ │ ├── hook_context.rb │ │ ├── hook_loader/ │ │ │ ├── base.rb │ │ │ ├── built_in_hook_loader.rb │ │ │ └── plugin_hook_loader.rb │ │ ├── hook_runner.rb │ │ ├── hook_signer.rb │ │ ├── installer.rb │ │ ├── interrupt_handler.rb │ │ ├── logger.rb │ │ ├── message_processor.rb │ │ ├── os.rb │ │ ├── printer.rb │ │ ├── subprocess.rb │ │ ├── utils/ │ │ │ ├── file_utils.rb │ │ │ └── messages_utils.rb │ │ ├── utils.rb │ │ └── version.rb │ └── overcommit.rb ├── libexec/ │ ├── gerrit-change-id │ └── index-tags ├── overcommit.gemspec ├── spec/ │ ├── integration/ │ │ ├── committing_spec.rb │ │ ├── configuration_signing_spec.rb │ │ ├── diff_flag_spec.rb │ │ ├── disable_overcommit_spec.rb │ │ ├── gemfile_option_spec.rb │ │ ├── hook_signing_spec.rb │ │ ├── installing_overcommit_spec.rb │ │ ├── parallelize_spec.rb │ │ ├── protected_branches_spec.rb │ │ ├── resolving_cherry_pick_conflict_spec.rb │ │ ├── resolving_merge_conflict_spec.rb │ │ ├── run_flag_spec.rb │ │ └── template_dir_spec.rb │ ├── overcommit/ │ │ ├── cli_spec.rb │ │ ├── command_splitter_spec.rb │ │ ├── configuration_loader_spec.rb │ │ ├── configuration_spec.rb │ │ ├── configuration_validator_spec.rb │ │ ├── default_configuration_spec.rb │ │ ├── git_config_spec.rb │ │ ├── git_repo_spec.rb │ │ ├── hook/ │ │ │ ├── base_spec.rb │ │ │ ├── commit_msg/ │ │ │ │ ├── capitalized_subject_spec.rb │ │ │ │ ├── empty_message_spec.rb │ │ │ │ ├── gerrit_change_id_spec.rb │ │ │ │ ├── hard_tabs_spec.rb │ │ │ │ ├── message_format_spec.rb │ │ │ │ ├── russian_novel_spec.rb │ │ │ │ ├── single_line_subject_spec.rb │ │ │ │ ├── spell_check_spec.rb │ │ │ │ ├── text_width_spec.rb │ │ │ │ └── trailing_period_spec.rb │ │ │ ├── post_checkout/ │ │ │ │ ├── base_spec.rb │ │ │ │ ├── bower_install_spec.rb │ │ │ │ ├── bundle_install_spec.rb │ │ │ │ ├── composer_install_spec.rb │ │ │ │ ├── index_tags_spec.rb │ │ │ │ ├── npm_install_spec.rb │ │ │ │ ├── submodule_status_spec.rb │ │ │ │ └── yarn_install_spec.rb │ │ │ ├── post_commit/ │ │ │ │ ├── bower_install_spec.rb │ │ │ │ ├── bundle_install_spec.rb │ │ │ │ ├── commitplease_spec.rb │ │ │ │ ├── composer_install_spec.rb │ │ │ │ ├── git_guilt_spec.rb │ │ │ │ ├── index_tags_spec.rb │ │ │ │ ├── npm_install_spec.rb │ │ │ │ ├── submodule_status_spec.rb │ │ │ │ └── yarn_install_spec.rb │ │ │ ├── post_merge/ │ │ │ │ ├── bower_install_spec.rb │ │ │ │ ├── bundle_install_spec.rb │ │ │ │ ├── composer_install_spec.rb │ │ │ │ ├── index_tags_spec.rb │ │ │ │ ├── npm_install_spec.rb │ │ │ │ ├── submodule_status_spec.rb │ │ │ │ └── yarn_install_spec.rb │ │ │ ├── post_rewrite/ │ │ │ │ ├── bower_install_spec.rb │ │ │ │ ├── bundle_install_spec.rb │ │ │ │ ├── composer_install_spec.rb │ │ │ │ ├── index_tags_spec.rb │ │ │ │ ├── npm_install_spec.rb │ │ │ │ ├── submodule_status_spec.rb │ │ │ │ └── yarn_install_spec.rb │ │ │ ├── pre_commit/ │ │ │ │ ├── author_email_spec.rb │ │ │ │ ├── author_name_spec.rb │ │ │ │ ├── berksfile_check_spec.rb │ │ │ │ ├── broken_symlinks_spec.rb │ │ │ │ ├── bundle_audit_spec.rb │ │ │ │ ├── bundle_check_spec.rb │ │ │ │ ├── bundle_outdated_spec.rb │ │ │ │ ├── case_conflicts_spec.rb │ │ │ │ ├── chamber_compare_spec.rb │ │ │ │ ├── chamber_security_spec.rb │ │ │ │ ├── chamber_verification_spec.rb │ │ │ │ ├── code_spell_check_spec.rb │ │ │ │ ├── coffee_lint_spec.rb │ │ │ │ ├── cook_style_spec.rb │ │ │ │ ├── credo_spec.rb │ │ │ │ ├── css_lint_spec.rb │ │ │ │ ├── dart_analyzer_spec.rb │ │ │ │ ├── dogma_spec.rb │ │ │ │ ├── erb_lint_spec.rb │ │ │ │ ├── es_lint_spec.rb │ │ │ │ ├── execute_permissions_spec.rb │ │ │ │ ├── fasterer_spec.rb │ │ │ │ ├── file_size_spec.rb │ │ │ │ ├── fix_me_spec.rb │ │ │ │ ├── flay_spec.rb │ │ │ │ ├── foodcritic_spec.rb │ │ │ │ ├── forbidden_branches_spec.rb │ │ │ │ ├── go_fmt_spec.rb │ │ │ │ ├── go_lint_spec.rb │ │ │ │ ├── go_vet_spec.rb │ │ │ │ ├── golangci_lint_spec.rb │ │ │ │ ├── hadolint_spec.rb │ │ │ │ ├── haml_lint_spec.rb │ │ │ │ ├── hard_tabs_spec.rb │ │ │ │ ├── hlint_spec.rb │ │ │ │ ├── html_hint_spec.rb │ │ │ │ ├── html_tidy_spec.rb │ │ │ │ ├── image_optim_spec.rb │ │ │ │ ├── java_checkstyle_spec.rb │ │ │ │ ├── js_hint_spec.rb │ │ │ │ ├── js_lint_spec.rb │ │ │ │ ├── jscs_spec.rb │ │ │ │ ├── jsl_spec.rb │ │ │ │ ├── json_syntax_spec.rb │ │ │ │ ├── kt_lint_spec.rb │ │ │ │ ├── license_finder_spec.rb │ │ │ │ ├── license_header_spec.rb │ │ │ │ ├── line_endings_spec.rb │ │ │ │ ├── local_paths_in_gemfile_spec.rb │ │ │ │ ├── mdl_spec.rb │ │ │ │ ├── merge_conflicts_spec.rb │ │ │ │ ├── mix_format_spec.rb │ │ │ │ ├── nginx_test_spec.rb │ │ │ │ ├── pep257_spec.rb │ │ │ │ ├── pep8_spec.rb │ │ │ │ ├── php_lint_spec.rb │ │ │ │ ├── php_stan_spec.rb │ │ │ │ ├── phpcs_fixer_spec.rb │ │ │ │ ├── phpcs_spec.rb │ │ │ │ ├── pronto_spec.rb │ │ │ │ ├── puppet_lint_spec.rb │ │ │ │ ├── puppet_metadata_json_lint_spec.rb │ │ │ │ ├── pycodestyle_spec.rb │ │ │ │ ├── pydocstyle_spec.rb │ │ │ │ ├── pyflakes_spec.rb │ │ │ │ ├── pylint_spec.rb │ │ │ │ ├── python_flake8_spec.rb │ │ │ │ ├── r_spec_spec.rb │ │ │ │ ├── rails_best_practices_spec.rb │ │ │ │ ├── rails_schema_up_to_date_spec.rb │ │ │ │ ├── rake_target_spec.rb │ │ │ │ ├── reek_spec.rb │ │ │ │ ├── rst_lint_spec.rb │ │ │ │ ├── rubo_cop_spec.rb │ │ │ │ ├── ruby_lint_spec.rb │ │ │ │ ├── ruby_syntax_spec.rb │ │ │ │ ├── scalariform_spec.rb │ │ │ │ ├── scalastyle_spec.rb │ │ │ │ ├── scss_lint_spec.rb │ │ │ │ ├── semi_standard_spec.rb │ │ │ │ ├── shell_check_spec.rb │ │ │ │ ├── slim_lint_spec.rb │ │ │ │ ├── solargraph_spec.rb │ │ │ │ ├── sorbet_spec.rb │ │ │ │ ├── sqlint_spec.rb │ │ │ │ ├── standard_spec.rb │ │ │ │ ├── stylelint_spec.rb │ │ │ │ ├── swift_lint_spec.rb │ │ │ │ ├── terraform_format_spec.rb │ │ │ │ ├── trailing_whitespace_spec.rb │ │ │ │ ├── travis_lint_spec.rb │ │ │ │ ├── ts_lint_spec.rb │ │ │ │ ├── vint_spec.rb │ │ │ │ ├── w3c_css_spec.rb │ │ │ │ ├── w3c_html_spec.rb │ │ │ │ ├── xml_lint_spec.rb │ │ │ │ ├── xml_syntax_spec.rb │ │ │ │ ├── yaml_lint_spec.rb │ │ │ │ ├── yaml_syntax_spec.rb │ │ │ │ ├── yard_coverage_spec.rb │ │ │ │ └── yarn_check_spec.rb │ │ │ ├── pre_push/ │ │ │ │ ├── base_spec.rb │ │ │ │ ├── brakeman_spec.rb │ │ │ │ ├── cargo_test_spec.rb │ │ │ │ ├── flutter_test_spec.rb │ │ │ │ ├── go_test_spec.rb │ │ │ │ ├── golangci_lint_spec.rb │ │ │ │ ├── minitest_spec.rb │ │ │ │ ├── mix_test_spec.rb │ │ │ │ ├── php_unit_spec.rb │ │ │ │ ├── pronto_spec.rb │ │ │ │ ├── protected_branches_spec.rb │ │ │ │ ├── pub_test_spec.rb │ │ │ │ ├── pytest_spec.rb │ │ │ │ ├── python_nose_spec.rb │ │ │ │ ├── r_spec_spec.rb │ │ │ │ ├── rake_target_spec.rb │ │ │ │ └── test_unit_spec.rb │ │ │ ├── pre_rebase/ │ │ │ │ └── merged_commits_spec.rb │ │ │ └── prepare_commit_msg/ │ │ │ ├── base_spec.rb │ │ │ └── replace_branch_spec.rb │ │ ├── hook_context/ │ │ │ ├── base_spec.rb │ │ │ ├── commit_msg_spec.rb │ │ │ ├── diff_spec.rb │ │ │ ├── post_checkout_spec.rb │ │ │ ├── post_commit_spec.rb │ │ │ ├── post_merge_spec.rb │ │ │ ├── post_rewrite_spec.rb │ │ │ ├── pre_commit_spec.rb │ │ │ ├── pre_push_spec.rb │ │ │ ├── pre_rebase_spec.rb │ │ │ ├── prepare_commit_msg_spec.rb │ │ │ └── run_all_spec.rb │ │ ├── hook_signer_spec.rb │ │ ├── installer_spec.rb │ │ ├── logger_spec.rb │ │ ├── message_processor_spec.rb │ │ └── utils_spec.rb │ ├── spec_helper.rb │ └── support/ │ ├── git_spec_helpers.rb │ ├── matchers/ │ │ └── hook.rb │ ├── normalize_indent.rb │ ├── output_helpers.rb │ └── shell_helpers.rb └── template-dir/ └── hooks/ ├── commit-msg ├── overcommit-hook ├── post-checkout ├── post-commit ├── post-merge ├── post-rewrite ├── pre-commit ├── pre-push ├── pre-rebase └── prepare-commit-msg ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # Defines the coding style for different editors and IDEs. # http://editorconfig.org root = true [*] charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true indent_style = space indent_size = 2 ================================================ FILE: .git-hooks/pre_commit/master_hooks_match.rb ================================================ # frozen_string_literal: true require 'fileutils' module Overcommit::Hook::PreCommit # Ensures all master hooks have the same content. # # This is necessary because we can't use symlinks to link all the hooks in the # template directory to the master `overcommit-hook` file, since symlinks are # not supported on Windows. class MasterHooksMatch < Base def run hooks_dir = File.join('template-dir', 'hooks') master_hook = File.join(hooks_dir, 'overcommit-hook') Dir.glob(File.join(hooks_dir, '*')).each do |hook_path| unless FileUtils.compare_file(master_hook, hook_path) return [ :fail, "Template directory hook '#{hook_path}' does not match '#{master_hook}'!\n" \ "Run `cp #{master_hook} #{hook_path}`" ] end end :pass end end end ================================================ FILE: .github/workflows/lint.yml ================================================ name: Lint on: push: branches: [main] pull_request: branches: [main] jobs: overcommit: timeout-minutes: 10 runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: 3.3 bundler-cache: true - name: Prepare environment run: | git config --global user.email "gh-actions@example.com" git config --global user.name "GitHub Actions" bundle exec overcommit --sign bundle exec overcommit --sign pre-commit - name: Run pre-commit checks run: bundle exec overcommit --run ================================================ FILE: .github/workflows/tests.yml ================================================ name: Tests on: push: branches: [main] pull_request: branches: [main] jobs: rspec: timeout-minutes: 15 runs-on: ${{ matrix.os }}-latest strategy: fail-fast: false matrix: ruby-version: - "2.6" - "2.7" - "3.0" - "3.1" - "3.2" - "3.3" os: - ubuntu # At the moment of this commit various specs fail on Windows. # Any contributor is welcome to fix them and enable the Windows build. # Please see Issue #836 for more details. # - windows steps: - uses: actions/checkout@v4 - name: Set up Ruby ${{ matrix.ruby-version }} uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby-version }} bundler-cache: true - name: Run tests run: | git config --global user.email "gh-actions@example.com" git config --global user.name "GitHub Actions" bundle exec rspec - name: Code coverage reporting uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} flag-name: ruby${{ matrix.ruby-version }}-${{ matrix.os }} parallel: true finish: needs: rspec runs-on: ubuntu-latest steps: - name: Finalize code coverage report uses: coverallsapp/github-action@v2 with: github-token: ${{ secrets.github_token }} parallel-finished: true ================================================ FILE: .gitignore ================================================ Gemfile.lock coverage/ pkg/ .bundle .idea .history/ .vscode/ ================================================ FILE: .overcommit.yml ================================================ gemfile: Gemfile PreCommit: # Disabled since this causes spurious failures on AppVeyor builds BrokenSymlinks: enabled: false BundleCheck: enabled: true ExecutePermissions: enabled: true exclude: - 'bin/overcommit' - 'libexec/**/*' - 'template-dir/hooks/**/*' HardTabs: enabled: true MasterHooksMatch: enabled: true quiet: true RuboCop: enabled: true include: - '**/*.gemspec' - '**/*.rb' - '**/Gemfile' - template-dir/hooks/overcommit-hook TrailingWhitespace: enabled: true YamlSyntax: enabled: true ================================================ FILE: .rubocop.yml ================================================ inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.6 NewCops: disable SuggestExtensions: false Layout/ClosingParenthesisIndentation: Enabled: false Layout/DotPosition: EnforcedStyle: trailing # Fails on AppVeyor builds Layout/EndOfLine: Enabled: false Layout/FirstParameterIndentation: Enabled: false Layout/FirstArrayElementIndentation: Enabled: false Layout/HeredocIndentation: Enabled: false Layout/LineLength: Max: 100 Layout/MultilineMethodCallIndentation: Enabled: false Layout/MultilineOperationIndentation: Enabled: false Layout/SpaceBeforeFirstArg: Exclude: - '*.gemspec' Lint/AmbiguousBlockAssociation: Enabled: false Lint/AmbiguousRegexpLiteral: Enabled: false Lint/AssignmentInCondition: Enabled: false Lint/Void: Enabled: false Metrics/AbcSize: Enabled: false Metrics/BlockLength: Enabled: false Metrics/MethodLength: Max: 20 Metrics/ModuleLength: Enabled: false Naming/FileName: Exclude: - 'template-dir/hooks/*' - 'Gemfile' - 'Rakefile' - '*.gemspec' # Renaming `has_something?` to `something?` obfuscates whether it is a "is-a" or # a "has-a" relationship. Naming/PredicatePrefix: Enabled: false # commit_sha1 is indeed how we want to write such a variable, so ignore this cop Naming/VariableNumber: Enabled: false # Enforcing this results in a lot of unnecessary indentation. Style/ClassAndModuleChildren: Enabled: false Style/Documentation: Exclude: - 'spec/overcommit/**/*' Style/FormatString: Enabled: false # There are a number of situations where this makes code less readable or # disrupts the visual flow of code. Style/GuardClause: Enabled: false Style/IfUnlessModifier: Enabled: false # We want to allow multi-line lambdas using the `->` syntax which Rubocop # doesn't allow. We're also not too worried about people using `lambda` for # single-line lambdas either. Style/Lambda: Enabled: false Style/Next: Enabled: false # Calling .zero? instead of comparing `== 0` seems unnecessarily verbose Style/NumericPredicate: Enabled: false Style/ParallelAssignment: Enabled: false # Prefer curly braces except for %i/%w/%W, since those return arrays. Style/PercentLiteralDelimiters: PreferredDelimiters: '%': '{}' '%i': '[]' '%q': '{}' '%Q': '{}' '%r': '{}' '%s': '()' '%w': '[]' '%W': '[]' '%x': '{}' Style/RescueModifier: Exclude: - 'bin/overcommit' Style/SignalException: Enabled: false # Forcing a particular name (e.g. |a, e|) for inject methods prevents you from # choosing intention-revealing names. Style/SingleLineBlockParams: Enabled: false Style/SpecialGlobalVars: Enabled: false Style/SymbolArray: Enabled: false Style/TrailingCommaInArguments: Enabled: false Style/TrailingCommaInArrayLiteral: Enabled: false Style/TrailingCommaInHashLiteral: Enabled: false ================================================ FILE: .rubocop_todo.yml ================================================ # 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' ================================================ FILE: CHANGELOG.md ================================================ # Overcommit Changelog ## 0.68.0 * Add `Solargraph` pre-commit hook ## 0.67.1 * Fix `set` gem dependency error when running with `--diff` flag ## 0.67.0 * Fix bug introduced in 0.65.0 that prevented `gemfile: false` from working correctly ## 0.66.0 * Add `--diff` CLI option for running pre-commit hooks against only changed files ## 0.65.0 * Load bundled gems on expected version ## 0.64.1 * Update minimum version of rexml to address [CVE-2024-49761](https://www.ruby-lang.org/en/news/2024/10/28/redos-rexml-cve-2024-49761/) ## 0.64.0 * Add support for `stylelint` 16+ * Add `changelog_uri` to gemspec ## 0.63.0 * Add `Sorbet` pre-commit hook * Add `RSpec` pre-commit hook ## 0.62.0 * Allow version 5 of `childprocess` gem dependency ## 0.61.0 * Allow `ReplaceBranch` to use `skip_if` * Fix local Overcommit file merges with existing `.overcommit.yml` ## 0.60.0 * Allow overriding `Gemfile.lock` location for `BundleCheck` pre-commit hook * Fix `ReplaceBranch` prepare-commit-msg hook to allow trailing spaces * Add `MixFormat` pre-commit hook * Add `MixTest` pre-push hook * Allow loading custom local configuration from `.local-overcommit.yml` * Handle `Psych::DisallowedClass` when running `YamlSyntax` pre-commit hook * Add support for specifying custom `encoding` in `RailsSchemaUpToDate` pre-commit hook ## 0.59.1 * Remove `--disable-pending-cops` as default flag to `RuboCop` pre-commit hook. * Remove special handling of process output on Windows since it broke on Linux. ## 0.59.0 * Add `--disable-pending-cops` as default flag to `RuboCop` pre-commit hook to ignore non-existent cops. Requires RuboCop `0.82.0` or newer. * Fix deprecation warning for `Bundler.with_clean_env`. * Fix handling of some kinds of pronto errors in the `Pronto` hook. * Fix encoding of process output on Windows. * Add support for specifying hook type to `--run` flag. * Fix message regex parser for Stylelint. * Fix configuration loading on Ruby 3.1. * Fix `YamlSyntax` to support aliases when parsing. * Fix run output to explicitly flush partial logs. ## 0.58.0 * Add `rexml` dependency explicitly to support Ruby 3.0. * Add `DartAnalyzer` pre-commit hook to analyze Dart files. * Add `PubTest` and `FlutterTest` pre-push hooks to run `pub test` and `flutter test` for Dart projects, respectively. * Update `index-tags` script to support scanning only files tracked by Git. * Fix `EsLint` pre-commit hook to not report certain false positives. * Update `YamlLint` to `fail` the run instead of `warn` when errors are detected. * Update `YamlLint` parse the line number of output so it is line aware. * Gracefully handle breaking behavior in upstream Psych gem to support YAML aliases. * Fix case where `git` would delete all tracked files when popping stash. ## 0.57.0 * Fix `CommitMsg` hooks to be able to call `modified_lines_in_file`. * Add `ErbLint` pre-commit hook to lint ERB files. ## 0.56.0 * Update `ReplaceBranch` prepare-commit-msg hook to avoid running on `--amend` by default. * Add support for `modified_files` and `modified_lines_in_file` in `CommitMsg` hooks. ## 0.55.0 * Fix `GoFmt` to not be enabled by default. This was enabled by mistake when introduced in Overcommit `0.52.0`. ## 0.54.1 * Fix `Overcommit::GitRepo.list_files` helper to work with arbitrarily large lists of files. * Fix `AuthorName` to allow mononyms to be more inclusive of names. ## 0.54.0 * Fix `YamlLint` pre-commit hook * Relax `childprocess` gem version constraint to allow version 4.x ## 0.53.0 * Improve performance in `PhpCs` pre-commit hook * Add `Pronto` pre-push hook * Remove erroneous extra newline in replacement string for `ReplaceBranch` prepare-commit-msg hook * Add note about potentially checking your stash when hook is interrupted * Add support for skipping hooks based on command result using the `skip_if` option ## 0.52.1 * Fix case where no standard input is provided to `pre-push` hooks ## 0.52.0 * Fix `Mdl` to properly parse JSON output from `mdl` * Add `GolangciLint` pre-commit and pre-push hooks * Add `GoTest` pre-push hook * Add `GoFmt` pre-commit hook * Add `exclude_branches` hook option to disable hooks running on specific branches * Add `exclude_remotes` pre-push hook option to disable pre-push hooks running against specific remotes * Change default behavior of pre-push hooks to **not** run against deleted remote refs * Add `include_remote_ref_deletions` pre-push hook option to allow running for a remote branch deletion * Rename `remote_branch_deletion?` pre-push hook helper to `remote_ref_deletion?` * Add per-branch `destructive_only` setting to `ProtectedBranches` pre-push hook ## 0.51.0 * Stop stashing in pre-commit hooks when all changes are already staged, avoiding unnecessary file modification * Improve instructions for recovering commit message when a `commit-msg` hook fails ## 0.50.0 * Fix Overcommit to display helpful error message when a hook does not inherit from the base class * Relax `childprocess` gem constraint to allow up to version 3.x * Display a helpful message if hooks do not inherit from the correct base class * Fix Overcommit to work with emacs/magit by [disabling literal pathspecs](https://magit.vc/manual/magit/My-Git-hooks-work-on-the-command_002dline-but-not-inside-Magit.html) ## 0.49.1 * Fix Overcommit to run when executed with no parent process * Fix `Stylelint` pre-commit hook `required_executable` ## 0.49.0 ### New Features * Add `skipped_commit_types` option to `ReplaceBranch` prepare-commit-msg hook * Add `RubySyntax` pre-commit hook * Add `CodeSpellCheck` pre-commit hook ### Changes * Relax `childprocess` dependency to allow version 1.x ### Bug Fixes * Fix deadlock which was more likely to occur when setting `parallelize` on a hook to `false` * Fix `Mdl` hook to use JSON output and not fail on unexpected output ## 0.48.1 * Fix `Stylelint` hook regex to extract line numbers with more than one digit * Fix `CaseConflicts` hook to work with file paths containing double quotes ## 0.48.0 * Drop support for Ruby 2.3 or older * Support multi-line matches in `MessageFormat` `commit-msg` hook * Add `FileSize` pre-commit hook ## 0.47.0 ### New Features * Add support for `prepare-commit-message` hooks * Add [`SwiftLint`](https://github.com/realm/SwiftLint) pre-commit hook * Add [`KtLint`](https://github.com/shyiko/ktlint) pre-commit hook * Add `TerraformFormat` pre-commit hook * Add [`CookStyle`](https://docs.chef.io/cookstyle.html) pre-commit hook ### Changes * Update `validator_uri` for `W3cHtml` pre-commit hook * Update `TsLint` pre-commit hook to support new output format * Update `BundleCheck` error message with additional instructions ### Bug Fixes * Add `--force-exclusion` flag to `Reek` pre-commit hook configuration to ensure excluded files are excluded ## 0.46.0 * Fix `Credo` pre-commit hook to lint applicable files only rather than all files * Add `PhpCsFixer` pre-commit hook * Add `YardCoverage` pre-commit hook * Add `Flay` pre-commit hook * Add `Stylelint` pre-commit hook * Fix `TsLint` default flags to work with `tslint` 5.11+ ## 0.45.0 ### New Features * Add `CargoTest` pre-push hook for running `cargo test` * Add `min_subject_width` option to `TextWidth` `commit-msg` hook ### Changes * Drop support for Ruby versions 2.1 and older ### Bug Fixes * Fix detection of `.git` directory location on Git versions before 2.5 ## 0.44.0 ### New Features * Add support for [worktrees](https://git-scm.com/docs/git-worktree) ### Bug Fixes * Fix installer to not attempt to remove old hooks directory if non-empty * Fix erroneous `fatal` error message from a pre-commit hook run when adding the first submodule to a repo ## 0.43.0 ### Changes * Add [`GitLfs`](https://git-lfs.github.com/) `post-checkout`, `post-commit` and `post-merge` hooks * Display commit message when `commit-msg` hooks fail * Drop support for JRuby * Enhance `pre-push` hooks to expose `modified_lines_in_file`, similar to `pre-commit` hooks * Add `YarnCheck` pre-commit hook which checks if `yarn.lock` matches `package.json` * Add [`PhpUnit`](https://phpunit.de/) `pre-push` hook ## 0.42.0 ### New Features * Add `YarnInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks * Add [`metadata-json-lint`](https://voxpupuli.org/blog/2014/11/06/linting-metadata-json/) pre-commit hook * Add [`RstLint`](https://github.com/twolfson/restructuredtext-lint) pre-commit hook * Add `YarnInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks * Add additional file patterns for `ChamberSecurity` pre-commit hook * Add `ChamberCompare` and `ChamberVerification` pre-commit hooks * Add `ComposerInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks * Add ability to `pre-push` hooks to inspect modified files for pushed refs * Add [`PhpStan`](https://github.com/phpstan/phpstan) pre-commit hook ### Changes * Run `GoLint` pre-commit hook against each file individually * Improve performance of `BundleAudit` checking of `Gemfile.lock` file * Allow ad hoc hooks to run executables not tracked by Git * Drop support for Ruby 2.0 ### Bug Fixes * Fix `LineEndings` pre-commit hook handling of file paths with spaces * Fix `Mdl` pre-commit hook message parsing regex * Fix `RailsBestPractices` hook to only run against changed files * Fix Overcommit installation in submodules * Don't print backtrace of signature change for `overcommit --run` ## 0.41.0 * Add [`PhpCs`](http://pear.php.net/package/PHP_CodeSniffer) pre-commit hook * Add [`PhpLint`](http://php.net/manual/en/features.commandline.options.php) pre-commit hook * Allow toggling colorize output via `OVERCOMMIT_COLOR` environment variable ## 0.40.0 * Add [`Pronto`](https://github.com/mmozuras/pronto) pre-commit hook * Add [`hadolint`](https://github.com/lukasmartinelli/hadolint) pre-commit hook * Add [`license_finder`](https://github.com/pivotal/LicenseFinder) pre-commit hook * Use the `core.hooksPath` Git configuration option when installing hooks * Gracefully handle binary files in `LineEndings` pre-commit hook * Relax `childprocess` dependency to allow 0.x * Gracefully handle gem loading errors when invoking Overcommit in a repo where the `gemfile` specified by the local `.overcommit.yml` references a gem version incompatible with the already-loaded Overcommit * Ignore `Makefile` and `*.go` files in `HardTabs` pre-commit hook by default ## 0.39.1 ### Bug Fixes * Update `childprocess` to 0.6.3 ## 0.39.0 ### New Features * Add [`GitLfs`](https://git-lfs.github.com/) pre-push hook ### Changes * Update `childprocess` dependency to 0.6.x series * Auto-sign configuration file when installing hooks for the first time ### Bug Fixes * Fix `forwarding to private method` warning on Ruby 2.4.x * Fix potential hang when a hook's `parallelize` option was set to `false` * Fix `empty strings as pathspecs` warning introduced in Git 2.11 ## 0.38.0 ### New Features * Add `Pytest` pre-push hook * Add `RakeTarget` pre-commit and pre-push hook * Moved `CommitPlease` from `CommitMsg` to `PostCommit` hook * Add `skip_file_checkout` hook setting for `PostCheckout` hooks ### Bug Fixes * Fix `install_command` for scss_lint gem ## 0.37.0 ### New Features * Add `FixMe` pre-commit hook, to ensure that no "token" words slips through. These strings are things you should fix now, not later * Add [`YAMLLint`](https://github.com/adrienverge/yamllint) pre-commit hook * Add `LicenseHeader` pre-commit enforcement to ensure open source projects contain proper license comments * Add [`Foodcritic`](http://www.foodcritic.io/) pre-commit hook * Add `LineEndings` pre-commit hook that allows you to enforcing UNIX- or Windows-style line endings ### Bug Fixes * Fix `CapitalizedSubject` to not fail when commit message starts with one or more empty lines ## 0.36.0 * Add [`Fasterer`](https://github.com/DamirSvrtan/fasterer) pre-commit hook * Add [`Brakeman`](http://brakemanscanner.org/) pre-push hook * Add [`TSLint`](http://palantir.github.io/tslint/) pre-commit hook * Validate that hook `env` environment configurations have valid names/values * Fix a false negative reported by RailsSchemaUpToDate for newly-created Rails projects that don't yet have any migrations ## 0.35.0 * Drop support for Ruby 1.9.3 * Fix `JavaCheckstyle` pre-commit hook to properly categorize `INFO` and `WARN` messages * Add `TestUnit` pre-push hook to run tests with `Test::Unit` * Add `BundleAudit` pre-commit hook to scan gems for vulnerabilities with [`bundle-audit`](https://github.com/rubysec/bundler-audit) * Copy hook files instead of symlinking * Add `Credo` pre-commit hook to check Elixir files * Remove `Brakeman` pre-commit hook as it could erroneously report clean runs depending on which files were committed to your repository. You should run this tool in a separate job/task in your CI runs as it doesn't make for a good pre-commit hook. * Add `Commitplease` pre-commit hook which checks commit messages with [`commitplease`](https://www.npmjs.com/package/commitplease) ## 0.34.2 * Add `--no-color` flag to all `git diff`/`git show` calls to override local configuration * Ignore `commit.gpgsign` configuration option when creating stash commits in pre-commit hooks ## 0.34.1 * Switch template directory hooks from symlinks to regular files so gem can be installed on Windows ## 0.34.0 * Fix `Scalastyle` pre-commit hook to capture messages with no line number * Fix `CoffeeLint` pre-commit hook detection of modified lines * Fix `Jscs` pre-commit hook to work with `jscs` 3.0.0+ * Fix `CapitalizedSubject` pre-commit hook to ignore commit message subjects starting with `fixup!` or `squash!` special prefixes * Add `BundleOutdated` pre-commit hook to report gems in the `Gemfile.lock` that have newer versions available * Add `destructive_only` option to `ProtectedBranches` pre-push hook * Include `.ru` files in `RuboCop` pre-commit hook * Fix `TextWidth` to ignore special `fixup!`/`squash!` prefixes in commit message subjects when determining width of line ## 0.33.0 ### New Features * Add global `quiet` option which silences all hook output except in the case of warning or error ### Changes * Official support for Rubinius has been dropped. It will probably still work for most use cases, but parallelized hook runs may be problematic. If someone from the community is willing to step up to support it, we'll gladly add it back * Change `overcommit` CLI to automatically run within a Bundler context if the `gemfile` option is specified. This mainly saves you from needing `bundle exec` when running `overcommit --run` ### Bug Fixes * Fix `AuthorName`/`AuthorEmail` pre-commit hooks to respect `GIT_AUTHOR_NAME`/`GIT_AUTHOR_EMAIL` environment variables, respectively * Fix `JavaCheckstyle` pre-commit hook to ignore `[ERROR]` prefix when parsing output messages ## 0.32.0 ### New Features * Hooks are now run in parallel by default * Add `concurrency` global option allowing you to specify the number of threads to use when running hooks concurrently * Add `parallelize` hook option which specifies whether or not this hook should be run in parallel (default is `true`) * Add `processors` hook option allowing you to specify how many processing units a hook should require * Add `ForbiddenBranches` pre-commit hook which prevents creating a commit on any blacklisted branch by name/pattern * Add `MessageFormat` commit-msg hook to validate commit messages against a regex pattern ### Changes * Improve error message output when there is a problem processing messages via `extract_messages` pre-commit hook helper * Switch `ScssLint` pre-commit hook to use the JSON output formatter instead of the default formatter * Change tense of hook descriptions from progressive indicative form ("Running") to indicative present form ("Run") so output reads better in parallel hook runs ### Bug Fixes * Fix bug where amending a commit with command line arguments containing Unicode characters could cause a crash due to invalid byte sequences * Fix `Minitest` pre-push hook to include all test files ## 0.32.0.rc1 * Add `concurrency` global option allowing you to specify the number of threads to use when running hooks concurrently * Add `parallelize` hook option which specifies whether or not this hook should be run in parallel (default is `true`) * Add `processors` hook option allowing you to specify how many processing units a hook should require ## 0.31.0 * Add support for glob patterns to `ProtectedBranches` pre-push hook * Add `Mdl` pre-commit hook to run [`mdl`](https://github.com/mivok/markdownlint) on Markdown files * Add `--without-color` flag to `RailsBestPractices` pre-commit hook to fix parsing issues due to color escape sequences * Improve error message when `gemfile` has not had a dependency installed * Fix `RuboCop` pre-commit hook to not swallow cop messages when `parser` gem warnings are output to STDERR ## 0.30.0 ### New Features * Add `Dogma` pre-commit hook to lint Elixir files with [dogma](http://elixir-lang.org/) files * Add `Minitest` pre-push hook to run Minitest tests * Add `RailsBestPractices` pre-commit hook which lints code with [`rails_best_practices`](https://github.com/railsbp/rails_best_practices) ### Bug Fixes * Fix `--run` flag to not block reading STDIN when using existing hook scripts * Fix `RuboCop` pre-commit hook to fail when RuboCop version specified by Bundler context is not available * Fix `TextWidth` commit-msg hook to not include newline characters in calculated width ## 0.29.1 * Raise error when hooks are defined with invalid names (i.e. non-alphanumeric characters) * Fix hook signing when specifying hook name * Fix `BundleCheck` pre-commit hook to not report false negatives when running via `overcommit --run` with local changes ## 0.29.0 ### Important Security Fix * Fix vulnerability where disabling signature verification would not be caught by signature verification, allowing an attacker to bypass the check. If you disable signature verification in your configuration, you must rename the option to `verify_signatures` and should audit your hooks. ### New Features * Allow nested arrays in `include` and `exclude` options so lists of file glob patterns can be shared across hook configurations via YAML references * Add `NginxTest` pre-commit hook that checks nginx configuration files with [`nginx -t`](https://www.nginx.com/resources/wiki/start/topics/tutorials/commandline/) * Respect `core.commentchar` configuration when reading commit messages ### Changes * Rename `verify_plugin_signatures` to `verify_signatures` ### Bug Fixes * Fix `Jscs` pre-commit hook to handle the new `jscs` [exit codes](https://github.com/jscs-dev/node-jscs/wiki/Exit-codes) introduced as of 2.2.0 * Fix `Scalastyle` pre-commit hook to fail with non-zero exit statuses ## 0.28.0 * Ensure `applicable_files` hook helper returns files in lexicographic order * Add `NpmInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks * Add `PuppetLint` pre-commit hook that checks Puppet code with [puppet-lint](http://puppet-lint.com/) * Add `BowerInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks * Add `BundleInstall` post-checkout, post-commit, post-merge, and post-rewrite hooks * Add `Sqlint` pre-commit hook that checks SQL code with [sqlint](https://github.com/purcell/sqlint) * Add Windows support * Add `Hlint` pre-commit hook that checks Haskell files with [hlint](https://github.com/ndmitchell/hlint) * Add `ExecutePermissions` pre-commit hook that checks file mode for unnecessary execute permissions ## 0.27.0 ### New Features * Add `HtmlHint` pre-commit hook that checks HTML files with [HTMLHint](http://htmlhint.com/) * Add support to the hook `execute` helper for accepting an optional list of splittable command arguments for transparently dealing with really long file lists and the operating system command length limit * Add `modified_files` helper to `PostCheckout` and `PostRewrite` hooks * Add `rewritten_commits` helper to `PostRewrite` hooks * Add `gemfile` option to configuration file which allows a `Gemfile` to be loaded by Bundler to enforce particular gem versions during hook runs * Add support for `OVERCOMMIT_DEBUG` environment variable which toggles the display of additional verbose output from executed commands * Add support for defining [hooks based on your existing git hooks](README.md#adding-existing-git-hooks) within your `.overcommit.yml` (no Ruby code required) * Add support for filtering all hooks except a small list via the `ONLY` environment variable (similar to `SKIP` except a whitelist instead of blacklist) ### Changes * Don't display "No applicable _hook-type_ hooks to run" message unless debug mode is enabled ### Bug Fixes * Fix pre-commit hook bug where amending a commit which breaks a symlink would result in that symlink not being included in the list of modified files * Fix `CaseConflicts` pre-commit hook handling of large sets of files * Fix `SemiStandard`/`Standard` hooks to read from `STDOUT` instead of `STDERR` and handle new output format * Fix `commit-msg` hooks to handle large commit messages auto-generated by the `--verbose` flag for `git commit` ## 0.26.0 ### New Features * Add `EmptyMessage` commit-msg hook that reports commits messages that are empty or contain only whitespace * Add `env` hook configuration option that allows you to set values for environment variables during the course of a particular hook's run ### Bug Fixes * Fix handling of paths with spaces in the name * Fix `CaseConflicts` pre-commit hook to not fail on initial commit * Fix handling of files removed or renamed in a commit amendment ## 0.25.0 ### New Features * Add `Vint` pre-commit hook that checks Vim script with [Vint](https://github.com/Kuniwak/vint) * Add `Scalariform` pre-commit hook that checks formatting of Scala code with [Scalariform](https://mdr.github.io/scalariform/) * Add `SlimLint` pre-commit hook that analyzes Slim templates with [Slim-Lint](https://github.com/sds/slim-lint) ### Changes * Include SVG files in `ImageOptim`, `XmlLint`, and `XmlSyntax` pre-commit hooks by default * Make `IndexTags` hooks quiet by default * Rename `Rubocop` pre-commit hook to `RuboCop` to match the project's proper name ### Bug Fixes * Fix `HardTabs` and `TrailingWhitespace` pre-commit hooks to include line information in errors, making it work as expected when `problem_on_unmodified_line` is set to something other than `report` * Fix handling of changing a symlink to a directory on commit amendment so it is not included in the list of modified files for pre-commit hooks * Handle empty commit messages in `CapitalizedSubject`, `SingleLineSubject`, `HardTabs`, `TextWidth`, and `TrailingPeriod` commit-msg hooks ## 0.24.0 ### New Features * Add `required_library`/`required_libraries` hook option which specifies a list of paths a hook should load with `Kernel.require` before running * Add `JsLint` pre-commit hook that checks the style of JavaScript files with [JSLint](http://www.jslint.com/) * Add `RubyLint` pre-commit hook that statically analyzes Ruby files with [ruby-lint](https://github.com/YorickPeterse/ruby-lint) * Add `Jsl` pre-commit hook that checks the style of JavaScript files with [JavaScript Lint](http://www.javascriptlint.com/) * Add `CapitalizedSubject` commit message hook * Add `GoVet` pre-commit hook that examines Go source files with [vet](https://godoc.org/golang.org/x/tools/cmd/vet) * Add `XmlSyntax` pre-commit hook to check that XML files are valid * Add `CaseConflicts` pre-commit hook which checks for file names in the same directory which differ by letter casing * Preserve existing git hooks in a repository when installing Overcommit hooks, and restore them on uninstall * Add `RSpec` pre-push hook that runs [RSpec](http://rspec.info/) tests before pushing to remote * Add `ProtectedBranches` pre-push hook that prevents destructive pushes (deletions or force pushes) to specified branches * Add `SpellCheck` commit-msg hook to check commit messages for misspelled words * Add support for `pre-rebase` hooks * Add `SubmoduleStatus` `post-checkout`, `post-commit`, `post-merge`, and `post-rewrite` hooks that warn when submodules are uninitialized, out of date with the current index, or contain merge conflicts ### Changes * Disable `ShellCheck` pre-commit hook by default * Switch `ImageOptim` hook to use executable instead of Ruby API * Improve `CoffeeLint` pre-commit hook to differentiate between errors and warnings * Improve `GoLint` pre-commit hook to extract file and line information * Change configuration loading behavior to prefer user-defined `ALL` hook configuration over default `ALL` configuration, and user-defined hook configuration over default `ALL` configuration * Change hook summary message to mention warnings if there were any * Disable almost all hooks by default. You will now need to explicitly enable almost all hooks yourself in your `.overcommit.yml`. If you are migrating from `overcommit` 0.23.0 and want to use the default configuration that shipped with that version, copy the [default configuration from 0.23.0](https://github.com/sds/overcommit/blob/9f03e9c82b385d375a836ca7146b117dbde5c822/config/default.yml) * Update `ScssLint` pre-commit hook to properly handle special exit code that signals all files were filtered by exclusions (new as of `scss-lint` 0.36.0) * Update `childprocess` dependency to minimum 0.5.6 * Change default value for `problem_on_unmodified_line` from `warn` to `report` * Update `Rubocop` pre-commit hook to pass `--display-cop-names` flag so cop names appear in output * Drop support for returning `:good`/`:bad` results from hooks (was deprecated in 0.15.0) * Remove `PryBinding` pre-commit hook since its functionality is provided by the `Rubocop` pre-commit hook ### Bug Fixes * Fix `LocalPathsInGemfile` to not report lints for commented paths * Fix `CssLint` pre-commit hook to ignore blank lines in `csslint` output * Fix error instructions typo in `BundleCheck` pre-commit hook * Fix bug where stashed changes were not restored when plugin signature validation failed * Don't clear working tree after pre-commit hook when only submodule changes are present * Restore file modification times of unstaged files in addition to staged files in pre-commit hook runs ## 0.23.0 ### New Features * Add pre-commit [ESLint](http://eslint.org/) hook * Add pre-commit hooks for [standard](https://github.com/feross/standard) and [semistandard](https://github.com/Flet/semistandard) JavaScript linters * Add support for `post-commit`, `post-merge`, and `post-rewrite` hooks * Add `GitGuilt` `post-commit` hook to display changes in blame ownership for modified files * Add `execute_in_background` helper to provide a standardized way to start long-running processes without blocking the hook run * Add `IndexTags` hook for `post-commit`, `post-merge`, and `post-rewrite` hook types so tags index can always be kept up to date via `ctags` * Add `W3cCss` and `W3cHtml` pre-commit hooks which integrate with the `w3c_validator` gem * Add `Scalastyle` pre-commit hook that runs [scalastyle](http://www.scalastyle.org/) against Scala code * Add `XmlLint` pre-commit hook to check XML files with [xmllint](http://xmlsoft.org/xmllint.html) * Add `JavaCheckstyle` pre-commit hook to check style of Java files with [checkstyle](http://checkstyle.sourceforge.net/) * Add `Pep8` pre-commit hook to check Python files with [pep8](https://pypi.python.org/pypi/pep8) * Add `Pyflakes` pre-commit hook to check Python files with [pyflakes](https://pypi.python.org/pypi/pyflakes) * Add `Pep257` pre-commit hook to check Python files with [pep257](https://pypi.python.org/pypi/pep257) * Add `HtmlTidy` pre-commit hook to check HTML files with [tidy](http://www.html-tidy.org/) * Add `Pylint` pre-commit hook to check Python files with [pylint](http://www.pylint.org/) ### Changes * Parse JSHint errors more precisely * Remove `JsxHint` and `Jsxcs` pre-commit hooks in favor of using the `required_executable` option on the JsHint and Jscs pre-commit hooks * Change behavior of configuration options containing array values to always replace the old value instead of appending to it * Change `ImageOptim` hook to fail instead of warn if the `image_optim` gem cannot be found * Remove `ctags_arguments` option from `IndexTags` hooks * Improve `PythonFlake8` pre-commit hook to differentiate between errors and warnings * Improve `CssLint` pre-commit hook to differentiate between errors and warnings ### Bug Fixes * Fix `--run` flag to consider all lines in all files as modified rather than none * Fix `--run` flag to exclude submodule directories from the list of modified files * Fix handling of files with spaces in their name when calculating modified lines in a file ## 0.22.0 * Disable `Reek` pre-commit hook by default * Allow `required_executable` to include paths that are in the repo root * Add `command` hook option allowing the actual command that is executed to be configured (useful to invoke command via `bundle exec` or similar) * Add `flags` hook option allowing the flags passed on the command line to be configured ## 0.21.0 * Change `HardTabs`, `MergeConflicts`, and `PryBinding` pre-commit hooks to be `quiet` by default * Switch `TravisLint` pre-commit hook from deprecated `travis-lint` gem to `travis` gem * Add .projections.json configuration file * Add pre-commit static analysis and linting for sh/bash scripts with [ShellCheck](http://www.shellcheck.net/) * Use `--verbose` flag when running JSCS to include name of offending rule ## 0.20.0 * Add `--run` flag which runs all configured pre-commit hooks against the entire repository * Fix installer to work with Overcommit hooks created via `GIT_TEMPLATE_DIR` * Fix hook runner to not display skip message unless hook would have actually run * Change `ImageOptim` hook to use `skip_missing_workers` option and update dependency to 0.18.0 * Remove interactive prompt support from overcommit hooks * Change hook signing from interactive flow to be done via `overcommit --sign ` command ## 0.19.0 * Add `--no-pngout` flag for `image_optim` command on `:fail` message * Fix `Brakeman` pre-commit hook when multiple files have been staged * Reset modification times more frequently when cleaning up the environment after running pre-commit hooks. This should help overcommit work with file watchers a little more nicely. * Add pre-commit JavaScript style checking with [JSXCS](https://github.com/orktes/node-jsxcs) * Add pre-commit Ruby code smell checking with [Reek](https://github.com/troessner/reek) * Gracefully handle `.git` files that point to an external git directory ## 0.18.0 * Update minimum version of `image_optim` gem to 0.15.0 (breaking change in name of exception classes) * Add `--list-hooks` flag which displays all hooks for a repository and whether they are enabled/disabled * Add `required_executable` and `install_command` options that allow a hook to define an executable that must be in the `PATH` in order for it to work, and a command the user can use to install the executable if it doesn't exist * All built-in hooks will now fail if the required executable is not present * Fix bug where pre-commit hook would crash if user attempted to commit a broken symlink * Add `BrokenSymlinks` pre-commit hook which checks for broken symlinks * Fix Chamber integration * Fix 'include' path for ChamberSecurity * Fix bug where commit message from cherry-picked commit would be lost if there were conflicts ## 0.17.0 * Change commit hook header text to bold instead of bold white so that it displays on terminals with a white background * Add support for `OVERCOMMIT_DISABLE` environment variable, which when set prevents Overcommit hooks from running * Fix bug that prevented RailsSchemaUpToDate from working in directories that contained decimals * Warn when trying to pipe commands using the `execute` helper, as this is not supported * Include both standard out/error streams in exception messages in pre-commit hook context ## 0.16.0 * Fix edge case where hitting Ctrl-C twice rapidly could result in work tree being lost * Fix edge case where hitting Ctrl-C after all pre-commit hooks had run but before the cleanup had finished would result in a lost working tree * Handle edge case where if a file was created in the working directory by a separate process in between the working tree being reset and the stash being applied, the hook runner would silently fail * Prevent stack traces from appearing during early interrupt before Overcommit has loaded its code * Remove `BundleCheck` post-checkout hook as it was a bit overzealous ## 0.15.0 * Fix bug where incorrect "hook run interrupted" message displayed when hook run failed * Gracefully handle `git stash` failures in pre-commit hook runs * Fix `overcommit-hook` auto-updating not passing original arguments to updated hook * Display message when `overcommit-hook` file is automatically updated * Deprecate `:bad` status in favor of `:fail` * Deprecate `:good` status in favor of `:pass` * Allow hook statuses to be transformed via `on_fail` and `on_warn` configuration options * Add `config` attribute as the preferred method to access hook configurations in hook implementations * Generate starter configuration on install with instructions on how to configure overcommit if an `.overcommit.yml` file does not yet exist * Include name of hook in output (to make it easier to find out which name to use when skipping) ## 0.14.1 * Fix hook skipping regression ## 0.14.0 * Ignore `db/structure.sql` in `TrailingWhitespace` pre-commit hook * Drop stashed changes after restoring them (now that #55 is fixed) * Change `JSCS` pre-commit hook to check status code instead of using regex to determine type of error * Fix performance regression where running Overcommit in a repository with a lot of files would be very slow * Wildcards in include/exclude globs now match files beginning with `.` * Drop support for Ruby 1.8.7 ## 0.13.0 * Prevent `JsonSyntax` pre-commit hook from failing if `json_class` key is present in JSON * Prevent `HardTabs` pre-commit hook from warning on tabs in Makefiles * Fix bug where `overcommit` hooks would fail for initial commit to repo * Add support for gracefully exiting from Ctrl-C interruptions * Add `.gitmodules` to the list of ignored files in `HardTabs` pre-commit hook ## 0.12.0 * Skip `HardTabs` pre-commit hook for Golang source files by default * Disable `IndexTags` post-checkout hook by default * Add `GoLint` pre-commit hook which runs `golint` on Golang source files ## 0.11.1 * Fix bug where `CHERRY_PICK_HEAD` would be lost when a pre-commit hook failed after attempting to cherry pick a commit with a conflict * Drop support for Ruby 1.9.2 ## 0.11.0 * Allow custom arguments to be passed to `ctags` via `IndexTags` post-checkout hook ## 0.10.0 * Change format of `include`/`exclude` file globs to match that of standard shell globbing (e.g. `**` matches zero or more directories rather than 1 or more) * Don't drop stashed changes after restoring them * Fix bug where `MERGE_HEAD` would be lost when attempting to commit a resolution to a merge conflict ## 0.9.0 * Include `--force-exclusion` flag in Rubocop hook so files excluded via `.rubocop.yml` are actually excluded * Add pre-commit `JsxHint` hook which uses the [JSXHint](https://github.com/STRML/JSXHint) project * Add pre-commit `BerksfileCheck` hook which warns you when your `Berksfile.lock` is out of sync with your `Berksfile` * Fix `BundleCheck` to use `git ls-files` instead of `git check-ignore`, as the latter is only available as of git 1.8 * Fix bug where skipping a hook via the `SKIP` environment variable would incorrectly warn about the hook's configuration having changed * Add `MergeConflicts` pre-commit hook which checks for unresolved merge conflicts in files * Add `RailsSchemaUpToDate` pre-commit hook which checks for `schema.rb`/`structure.sql` that aren't up-to-date with the latest migration * Add `PryBinding` pre-commit hook which checks for `binding.pry` calls that have been left behind in code * Add `LocalPathsInGemfile` pre-commit hook which checks for gem dependencies pointing to local paths in a `Gemfile` * Add `JsonSyntax` pre-commit hook which checks the syntax of all `.json` files * Add `Brakeman` pre-commit hook which runs security checks against code (disabled by default as it is slow) * Add `ChamberSecurity` pre-commit hook which ensures that `chamber secure` has been run before committing your changes (see the [Chamber](https://github.com/thekompanee/chamber) gem for more information) ## 0.8.0 * Add pre-commit `TravisLint` hook which uses the [travis-lint](https://github.com/travis-ci/travis-lint) gem * Display actual warning message when dependencies aren't satisfied in post-checkout `BundleCheck` hook * Add support for hook plugin signature verification so that you don't automatically execute repo-specific hooks that changed since you last ran them. See [Security](README.md#security) for more information * Automatically update `overcommit-hook` master hook and any other symlinks before hook run. Run `overcommit --install` if you're upgrading to save you from having to run `overcommit --install` in the future ## 0.7.0 * Change `command` hook helper signature to accept an array of arguments instead of a shell string * Rename `command` hook helper to `execute` * Add support for JRuby 1.7.9 in Ruby 1.9 mode * Display more helpful error message when installing Overcommit into a repo that already has non-Overcommit hooks * Add `--force` flag allowing Overcommit to be installed in repositories that already contain non-Overcommit hooks (overwriting them in the process) ## 0.6.3 * `TextWidth` pre-commit hook now supports customized maximum subject line and commit message body widths * Fix bug where committing a change with only file deletions would result in those changes not being committed * Warn instead of failing when gem dependencies are out of date in `BundleCheck` post-checkout hook ## 0.6.2 * Fix bug where hook run would crash if hook was unsuccessful but returned no output ## 0.6.1 * Fix bug where a plugin would fail to load if it had a custom configuration defined ## 0.6.0 * Breaking changes: plugin framework has been overhauled. You must now subclass `Overcommit::Hook::` and implement the method `run` instead of `run_check`. Also, the old hook runner no longer works, so you'll need to remove the hooks installed in `.git/hooks` and install new ones with `overcommit --install` * Configuration for repository can be specified via `.overcommit.yml` file * Can now skip hooks using just `SKIP` instead of `SKIP_CHECKS` environment variable * Add `--template-dir` flag which provides a convenient way to auto-install overcommit via Git template directories * Converted all script-based hook scripts to Ruby-based ones * `AuthorEmail` check can be customized so emails match a regex * `Whitespace` check was split into `HardTabs` and `TrailingWhitespace` * Add pre-commit JavaScript style checking with [JSCS](https://github.com/mdevils/node-jscs) * Add `BundleCheck` pre-commit hook which checks if `Gemfile.lock` matches `Gemfile` ## 0.5.0 * Use per-file `.scss-lint.yml` configuration for staged files ## 0.4.1 * Remove `RestrictedPaths` pre-commit check ## 0.4.0 * Added pre-commit check that optimizes images with `image_optim` * Don't include submodules in the list of modified files ## 0.3.2 * Fix bug where `.rubocop.yml` would not be found in present working directory ## 0.3.1 * Use per-file `.rubocop.yml` configuration for staged files ## 0.3.0 * Added Gemfile.lock/bundler checking * Added `--no-ext-diff` option to git diff * Exposed StagedFile#original_path ## 0.2.6 * Added check for linting HAML files with [haml-lint](https://github.com/sds/haml-lint) ## 0.2.5 * Don't use `--silent` flag with `rubocop` for Ruby style check (allows upgrade to Rubocop 0.12.0) ## 0.2.4 * Teach scss-lint check to downgrade lints on untouched lines as warnings ## 0.2.3 * Fix "Too many open files" error for very large commits * Make `StagedFile` tempfile creation lazy - should speed up some checks * Address rare cross-platform compatibility issue by replacing a `which` call with a pure Ruby equivalent * Fix CoffeeScript linter path processing ## 0.2.2 * Allow specifying multiple file types for checks and syntax check rake files * Fix bug where checks which returned lists of lines would output incorrectly * Indent check output lines to nest under check name for better readability ## 0.2.1 * Fix bug where checks that didn't return strings for output would error ## 0.2.0 * Teach `StagedFile`s how to calculate which lines were actually added/modified * Checks no longer need to filter temporary staged file paths themselves * Condense Ruby style check output * Teach Ruby style check to downgrade style lints on untouched lines as warnings ## 0.1.11 * Added Ruby code style linting via RuboCop ## 0.1.10 * Fixed bug where `output` was expected to be a string but was an array in js_syntax ## 0.1.9 * Fixed bug where `staged` helper in `HookSpecificCheck` wasn't returning `StagedFile`s ## 0.1.8 * Resurrect StagedFile for reading index contents rather than disk contents ## 0.1.7 * Sort plugins alphabetically * Omit periods from warning messages for consistency * Enforce single-line commit message subjects * Only colorize output when logging to a TTY * Add check to detect hard tabs in commit messages * Fix crashing --list-templates flag ## 0.1.6 * Strip out blank release note in addition to warning the committer * Add Python linting via [flake8](http://flake8.readthedocs.org/en/latest/) * Add CoffeeScript linting via [coffeelint](http://www.coffeelint.org/) ## 0.1.5 * Improve spec coverage * Use installed `jshint` if available instead of Rhino * Update readme with dependencies, uninstall instructions ## 0.1.4 * Fix SKIP_CHECKS for namespaced hooks * Make hooks work when repo-specific configuration file is missing * Improve error handling when loading custom hooks ## 0.1.3 * Add un-skippable checks (not skipped via SKIP_CHECKS) * Improve spec coverage ## 0.1.2 * Add uninstall (-u) option ## 0.1.1 * Make installer more robust * Improve readme documentation * Add template listing (-l) to CLI * Add rspec and super-basic spec coverage * Improve command-line messaging ## 0.1.0 * First public release ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing to Overcommit ## Bug Reports * Ensure that your issue [has not already been reported][1]. It may already be fixed! * Include the steps you carried out to produce the problem. * Include the behavior you observed along with the behavior you expected, and why you expected it. * Try setting the `OVERCOMMIT_DEBUG` environment variable to enable the display of additional verbose output from executed commands. * Include the stack trace and any debugging output reported by Overcommit. ## Feature Requests We welcome feedback with or without pull requests. If you have an idea for how to improve the tool, great! All we ask is that you take the time to write a clear and concise explanation of what need you are trying to solve. If you have thoughts on _how_ it can be solved, include those too! The best way to see a feature added, however, is to submit a pull request. ## Pull Requests * Before creating your pull request, it's usually worth asking if the code you're planning on writing will actually be considered for merging. You can do this by [opening an issue][1] and asking. It may also help give the maintainers context for when the time comes to review your code. * Ensure your [commit messages are well-written][2]. This can double as your pull request message, so it pays to take the time to write a clear message. * Add tests for your feature. You should be able to look at other tests for examples, especially if you're contributing a pre-commit hook. Speaking of tests, we use `rspec`, which can be run like so: ```bash bundle exec rspec ``` * Submit your pull request! All pull requests will be tested against [Travis CI][3], where the following commands are run against multiple versions of Ruby: ```bash bundle exec rspec bundle exec overcommit --run ``` Ensuring your changes pass for the above commands before submitting your pull request will save you time having to fix those changes. Better yet, if you [install Overcommit](README.md#installation) hooks into your forked repo, a lot of these checks will be done automatically for you! ### Naming Hooks Hooks should be named in camel case format (e.g. `RuboCop`) with acronyms only capitalizing the first letter in the series (e.g. SCSS Lint becomes `ScssLint`). If a tool has a specific capitalization that is odd, follow that capitalization. For example, `Scalastyle` is written with a lowercase "s" rather than camel-cased as `ScalaStyle`, so the `Scalastyle` hook follows that convention. Exceptions to this rule are tools that begin with a lowercase letter—these should be capitalized. Lastly, unless a tool has a particularly unique or descriptive name, include an additional prefix to help categorize it (e.g. `Java` in `JavaCheckstyle`), so it is easier for others to find hooks in the [README](README.md). The reasoning for this perhaps odd naming scheme is to strike a balance between consistency, familiarity for those who already know the tool, and Overcommit's ability to deduce the name of a hook from its filename and vice versa. [1]: https://github.com/sds/overcommit/issues [2]: https://medium.com/brigade-engineering/the-secrets-to-great-commit-messages-106fc0a92a25 [3]: https://travis-ci.org/ ## Code of conduct This project adheres to the [Open Code of Conduct][code-of-conduct]. By participating, you are expected to honor this code. [code-of-conduct]: https://github.com/civiccc/code-of-conduct ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source 'https://rubygems.org' gemspec # Development dependencies are listed below gem 'rspec', '~> 3.0' gem 'simplecov', '~> 0.21.0' gem 'simplecov-lcov', '~> 0.8.0' # Pin RuboCop for CI builds if RUBY_VERSION < '2.7.0' gem 'rubocop', '1.50.0' else gem 'rubocop', '1.77.0' end gem 'ffi' if Gem.win_platform? ================================================ FILE: MIT-LICENSE ================================================ Copyright (c) 2013-2019 Shane da Silva, Aiden Scandella Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ [![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=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=main)](http://inch-ci.org/github/sds/overcommit)

Overcommit Logo

`overcommit` is a tool to manage and configure [Git hooks](http://git-scm.com/book/en/Customizing-Git-Git-Hooks). In addition to supporting a wide variety of hooks that can be used across multiple repositories, you can also define hooks specific to a repository which are stored in source control. You can also easily [add your existing hook scripts](#adding-existing-git-hooks) without writing any Ruby code. - [Requirements](#requirements) - [Windows](#windows) - [Dependencies](#dependencies) - [Installation](#installation) - [Automatically Install Overcommit Hooks](#automatically-install-overcommit-hooks) - [Usage](#usage) - [Skipping Hooks](#skipping-hooks) - [Disabling Overcommit](#disabling-overcommit) - [Disabling Colorized Output](#disabling-colorized-output) - [Continuous Integration](#continuous-integration) - [Configuration](#configuration) - [Hook Options](#hook-options) - [Hook Categories](#hook-categories) - [The `ALL` Hook](#the-all-hook) - [Gemfile](#gemfile) - [Plugin Directory](#plugin-directory) - [Quiet Hook Runs](#quiet-hook-runs) - [Concurrency](#concurrency) - [Signature Verification](#signature-verification) - [Built-In Hooks](#built-in-hooks) - [CommitMsg](#commitmsg) - [PostCheckout](#postcheckout) - [PostCommit](#postcommit) - [PostMerge](#postmerge) - [PostRewrite](#postrewrite) - [PreCommit](#precommit) - [WARNING: pre-commit hooks cannot have side effects](#warning-pre-commit-hooks-cannot-have-side-effects) - [PrePush](#prepush) - [PreRebase](#prerebase) - [Repo-Specific hooks](#repo-specific-hooks) - [Adding Existing Git Hooks](#adding-existing-git-hooks) - [Security](#security) - [Disabling Signature Checking](#disabling-signature-checking) - [Contributing](#contributing) - [Community](#community) - [Changelog](#changelog) - [License](#license) ## Requirements This project aims to support the following Ruby runtimes on \*nix (and best effort on Windows): * Ruby 2.6+ ### Dependencies Some hooks have third-party dependencies. For example, to lint your [SCSS](http://sass-lang.com/) files, you're going to need the [scss_lint gem](https://github.com/sds/scss-lint). Depending on the hooks you enable/disable for your repository, you'll need to ensure your development environment already has those dependencies installed. Most hooks will display a warning if a required executable isn't available. If you are using Bundler to manage your Ruby gem dependencies, you'll likely want to use the [`gemfile`](#gemfile) option to control which gem versions are available during your hook runs. ## Installation `overcommit` is installed via [RubyGems](https://rubygems.org/). It is strongly recommended that your environment support running `gem install` without requiring root user privileges via `sudo` or otherwise. Using a Ruby version manager like [`rbenv`](https://github.com/rbenv/rbenv/) or [`rvm`](https://rvm.io/) is recommended. Once you have an environment that allows you to install gems without `sudo`, run: ```bash gem install overcommit ``` You can then run the `overcommit` command to install hooks into repositories. ```bash mkdir important-project cd important-project git init overcommit --install ``` After running `overcommit --install`, any existing hooks for your repository which Overcommit will replace will be backed up. You can restore everything to the way it was by running `overcommit --uninstall`. ### Automatically Install Overcommit Hooks If you want to use `overcommit` for all repositories you create/clone going forward, add the following to automatically run in your shell environment: ```bash export GIT_TEMPLATE_DIR="$(overcommit --template-dir)" ``` The `GIT_TEMPLATE_DIR` provides a directory for Git to use as a template for automatically populating the `.git` directory. If you have your own template directory, you might just want to copy the contents of `overcommit --template-dir` to that directory. ## Usage Once you've installed the hooks via `overcommit --install`, they will automatically run when the appropriate hook is triggered. The `overcommit` executable supports the following command-line flags: Command Line Flag | Description --------------------------|---------------------------------------------------- `-i`/`--install` | Install Overcommit hooks in a repository `-u`/`--uninstall` | Remove Overcommit hooks from a repository `-f`/`--force` | Don't bail on install if other hooks already exist--overwrite them `-l`/`--list-hooks` | Display all available hooks in the current repository `-r`/`--run` | Run pre-commit hook against all tracked files in repository `--diff ` | Run pre-commit hook against all changed files relative to `` `-t`/`--template-dir` | Print location of template directory `-h`/`--help` | Show command-line flag documentation `-v`/`--version` | Show version ### Skipping Hooks Sometimes a hook will report an error that for one reason or another you'll want to ignore. To prevent these errors from blocking your commit, you can include the name of the relevant hook in the `SKIP` environment variable, e.g. ```bash SKIP=RuboCop git commit ``` If you would prefer to specify a whitelist of hooks rather than a blacklist, use the `ONLY` environment variable instead. ```bash ONLY=RuboCop git commit ``` Use this feature sparingly, as there is no point to having the hook in the first place if you're just going to ignore it. If you want to ensure a hook is never skipped, set the `required` option to `true` in its configuration. If you attempt to skip it, you'll see a warning telling you that the hook is required, and the hook will still run. ### Disabling Overcommit If you have scripts that execute `git` commands where you don't want Overcommit hooks to run, you can disable Overcommit entirely by setting the `OVERCOMMIT_DISABLE` environment variable. ```bash OVERCOMMIT_DISABLE=1 ./my-custom-script ``` ### Disabling Colorized Output Overcommit automatically colorizes its output based on whether it is outputting to a TTY. However, you can manually enable/disable color by setting the `OVERCOMMIT_COLOR` environment variable. ```bash OVERCOMMIT_COLOR=0 git commit ``` ## Continuous Integration You can run the same set of hooks that would be executed in a pre-commit hook against your entire repository by running `overcommit --run`. This makes it easy to have the checks verified by a CI service such as [Travis CI](https://travis-ci.com/), including custom hooks you've written yourself. The `--run` flag works by creating a pre-commit context that assumes _all_ the files in your repository have changed, and follows the same rules as a normal pre-commit check. If any hook fails with an error, it will return a non-zero exit code. ## Configuration Overcommit provides a flexible configuration system that allows you to tailor the built-in hooks to suit your workflow. All configuration specific to a repository is stored in `.overcommit.yml` in the top-level directory of the repository. When writing your own configuration, it will automatically extend the [default configuration](config/default.yml), so you only need to specify your configuration with respect to the default. In order to enable/disable hooks, you can add the following to your repo-specific configuration file: ```yaml PreCommit: RuboCop: enabled: true 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 own custom options unique to each hook. The following table lists all built-in configuration options: Option | Description ----------------------------------------|-------------------------------------- `enabled` | If `false`, this hook will never be run `required` | If `true`, this hook cannot be skipped via the `SKIP` environment variable `quiet` | If `true`, this hook does not display any output unless it warns/fails `description` | Message displayed while hook is running. `requires_files` | If `true`, this hook runs only if files that are applicable to it have been modified. See `include` and `exclude` for how to specify applicable files. `include` | File paths or glob patterns of files that apply to this hook. The hook will only run on the applicable files when they have been modified. Note that the concept of modified varies for different types of hooks. By default, `include` matches every file until you specify a list of patterns. `exclude` | File paths or glob patterns of files that do not apply to this hook. This is used to exclude any files that would have been matched by `include`. `exclude_branches` | List of branch names or glob patterns of branches that this hook should not run against. `exclude_remotes` | *`PrePush` hooks only.* List of remote names that the hook should not run against. `include_remote_ref_deletions` | *`PrePush` hooks only.* By default, `PrePush` hooks will **not** run for pushes that delete a remote ref (i.e. branches or tags). Set to `true` to have the hook run even for deleted remote ref. `problem_on_unmodified_line` | How to treat errors reported on lines that weren't modified during the action captured by this hook (e.g. for pre-commit hooks, warnings/errors reported on lines that were not staged with `git add` may not be warnings/errors you care about). Valid values are `report`: report errors/warnings as-is regardless of line location (default); `warn`: report errors as warnings if they are on lines you didn't modify; and `ignore`: don't display errors/warnings at all if they are on lines you didn't modify (`ignore` is _not_ recommended). `on_fail` | Change the status of a failed hook to `warn` or `pass`. This allows you to treat failures as warnings or potentially ignore them entirely, but you should use caution when doing so as you might be hiding important information. `on_warn` | Similar to `on_fail`, change the status of a hook that returns a warning status to either `pass` (you wish to silence warnings entirely) or `fail` (you wish to treat all warnings as errors). `required_executable` | Name of an executable that must exist in order for the hook to run. If this is a path (e.g. `./bin/ruby`), ensures that the executable file exists at the given location relative to the repository root. Otherwise, if it just the name of an executable (e.g. `ruby`) checks if the executable can be found in one of the directories in the `PATH` environment variable. Set this to a specific path if you want to always use an executable that is stored in your repository. (e.g. RubyGems bin stubs, Node.js binaries, etc.) `required_library`/`required_libraries` | List of Ruby libraries to load with `Kernel.require` before the hook runs. This is specifically for hooks that integrate with external Ruby libraries. `command` | Array of arguments to use as the command. How each hook uses this is different, but it allows hooks to change the context with which they run. For example, you can change the command to be `['bundle', 'exec', 'rubocop']` instead of just `rubocop` so that you can use the gem versions specified in your local `Gemfile.lock`. This defaults to the name of the `required_executable`. `flags` | Array of arguments to append to the `command`. This is useful for customizing the behavior of a tool. It's also useful when a newer version of a tool removes/renames existing flags, so you can update the flags via your `.overcommit.yml` instead of waiting for an upstream fix in Overcommit. `env` | Hash of environment variables the hook should be run with. This is intended to be used as a last resort when an executable a hook runs is configured only via an environment variable. Any pre-existing environment variables with the same names as ones defined in `env` will have their original values restored after the hook runs. **NOTE:** Currently, only strings are accepted values. Boolean values will raise an error. **WARNING**: If you set the same environment variable for multiple hooks and you've enabled parallel hook runs, since the environment is shared across all threads you could accidentally have these separate hooks trample on each other. In this case, you should disable parallelization for the hook using the `parallelize` option. `parallelize` | Whether to allow this hook to be run concurrently with other hooks. Disable this if the hook requires access to a shared resource that other hooks may also access and modify (e.g. files, the git index, process environment variables, etc). `processors` | The number of processing units to reserve for this hook. This does not reserve CPUs, but indicates that out of the total number of possible concurrent hooks allowed by the global `concurrency` option, this hook requires the specified number. Thus in the typical case where `concurrency` is set to the number of available cores (default), and you have a hook that executes an application which itself creates 2 threads (or is otherwise scheduled on 2 cores), you can indicate that Overcommit should allocate 2 `processors` to the hook. Ideally this means your hooks won't put undue load on your available cores. `install_command` | Command the user can run to install the `required_executable` (or alternately the specified `required_libraries`). This is intended for documentation purposes, as Overcommit does not install software on your behalf since there are too many edge cases where such behavior would result in incorrectly configured installations (e.g. installing a Python package in the global package space instead of in a virtual environment). `skip_file_checkout` | Whether to skip this hook for file checkouts (e.g. `git checkout some-ref -- file`). Only applicable to `PostCheckout` hooks. `skip_if` | Array of arguments to be executed to determine whether or not the hook should run. For example, setting this to a value of `['bash', '-c', '! which my-executable']` would allow you to skip running this hook if `my-executable` was not in the bin path. In addition to the built-in configuration options, each hook can expose its own unique configuration options. The `AuthorEmail` hook, for example, allows you to customize the regex used to check commit author emails via the `pattern` option—useful if you want to enforce that developers use a company email address for their commits. This provides incredible flexibility for hook authors as you can make your hooks sufficiently generic and then customize them on a per-project basis. ### Hook Categories Hook configurations are organized into categories based on the type of hook. So `pre-commit` hooks are located under the `PreCommit` option, and `post-commit` hooks are located under `PostCommit`. See the [default configuration](config/default.yml) for a thorough example. #### The `ALL` Hook Within a hook category, there is a special type of hook configuration that applies to _all_ hooks in the category. This configuration looks like a normal hook configuration, except it has the name `ALL`: ```yaml PreCommit: ALL: problem_on_unmodified_line: warn requires_files: true required: false quiet: false SomeHook: enabled: true ... ``` The `ALL` configuration is useful for when you want to [DRY](http://en.wikipedia.org/wiki/Don%27t_repeat_yourself) up your configuration, or when you want to apply changes across an entire category of hooks. Note that array configuration options (like `include`/`exclude`) in the special `ALL` hook section are not merged with individual hook configurations if custom ones are defined for the hook. Any custom configuration option for `include`/`exclude` will replace the `ALL` hook's configuration. If you want to have a global list of default exclusions and extend them with a custom list, you can use YAML references, e.g. ```yaml PreCommit: ALL: exclude: &default_excludes - 'node_modules/**/*' - 'vendor/**/*' MyHook: exclude: - *default_excludes - 'another/directory/in/addition/to/default/excludes/**/*' ``` Again, you can consult the [default configuration](config/default.yml) for detailed examples of how the `ALL` hook can be used. ### Gemfile You may want to enforce the version of Overcommit or other gems that you use in your git hooks. This can be done by specifying the `gemfile` option in your `.overcommit.yml`. The `gemfile` option tells Overcommit to load the specified file with [Bundler](http://bundler.io/), the standard gem dependency manager for Ruby. This is useful if you would like to: - Enforce a specific version of Overcommit to use for all hook runs (or to use a version from the master branch that has not been released yet) - Enforce a specific version or unreleased branch is used for a gem you want to use in your git hooks Loading a Bundler context necessarily adds a startup delay to your hook runs as Bundler parses the specified `Gemfile` and checks that the dependencies are satisfied. Thus for projects with many gems this can introduce a noticeable delay. The recommended workaround is to create a separate `Gemfile` in the root of your repository (call it `.overcommit_gems.rb`), and include only the gems that your Overcommit hooks need in order to run. Generate the associated lock file by running: ```bash bundle install --gemfile=.overcommit_gems.rb ``` ...and commit `.overcommit_gems.rb` and the resulting `.overcommit_gems.rb.lock` file to your repository. Set your `gemfile` option to `.overcommit_gems.rb`, and you're all set. Using a smaller Gemfile containing only the gems used by your Overcommit hooks significantly reduces the startup delay in your hook runs. It is thus the recommended approach unless your project has a relatively small number of gems in your `Gemfile`. ### Plugin Directory You can change the directory that project-specific hooks are loaded from via the `plugin_directory` option. The default directory is `.git-hooks`. ### Quiet Hook Runs If you prefer to have your hooks be completely silent unless there is a problem, you can set the top-level `quiet` option to `true`. Note that if you have many hooks or slow hooks this may not be desirable, as you don't get visual feedback indicating the general progress of the hook run. ### Concurrency Overcommit runs hooks in parallel by default, with a number of concurrent workers equal to the number of logical cores on your machine. If you know your particular set of hooks would benefit from higher/lower number of workers, you can adjust the global `concurrency` option. You can define single-operator mathematical expressions, e.g. `%{processors} * 2`, or `%{processors} / 2`. ```yaml concurrency: '%{processors} / 4' ``` Note that individual hooks can specify the number of processors they require with the `processors` hook option. See the [hook options](#hook-options) section for more details. ### Signature Verification You can disable manual verification of signatures by setting `verify_signatures` to `false`. See the [Security](#security) section for more information on this option and what exactly it controls. ## Built-In Hooks Currently, Overcommit supports the following hooks out of the box—simply enable them in your `.overcommit.yml`. **Note**: Hooks with a `*` are enabled by default. **Warning**: This list represents the list of hooks available on the `master` branch. Please consult the [change log](CHANGELOG.md) to view which hooks have not been released yet. ### CommitMsg `commit-msg` hooks are run against every commit message you write before a commit is created. A failed hook prevents a commit from being created. These hooks are useful for enforcing policies on your commit messages, e.g. ensuring a task ID is included for tracking purposes, or ensuring your commit messages follow [proper formatting guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). * [`*`CapitalizedSubject](lib/overcommit/hook/commit_msg/capitalized_subject.rb) * [`*`EmptyMessage](lib/overcommit/hook/commit_msg/empty_message.rb) * [GerritChangeId](lib/overcommit/hook/commit_msg/gerrit_change_id.rb) * [HardTabs](lib/overcommit/hook/commit_msg/hard_tabs.rb) * [MessageFormat](lib/overcommit/hook/commit_msg/message_format.rb) * [RussianNovel](lib/overcommit/hook/commit_msg/russian_novel.rb) * [`*`SingleLineSubject](lib/overcommit/hook/commit_msg/single_line_subject.rb) * [SpellCheck](lib/overcommit/hook/commit_msg/spell_check.rb) * [`*`TextWidth](lib/overcommit/hook/commit_msg/text_width.rb) * [`*`TrailingPeriod](lib/overcommit/hook/commit_msg/trailing_period.rb) ### PostCheckout `post-checkout` hooks run after a successful `git checkout`, or in other words any time your `HEAD` changes or a file is explicitly checked out. * [BowerInstall](lib/overcommit/hook/post_checkout/bower_install.rb) * [BundleInstall](lib/overcommit/hook/post_checkout/bundle_install.rb) * [ComposerInstall](lib/overcommit/hook/post_checkout/composer_install.rb) * [IndexTags](lib/overcommit/hook/post_checkout/index_tags.rb) * [NpmInstall](lib/overcommit/hook/post_checkout/npm_install.rb) * [SubmoduleStatus](lib/overcommit/hook/post_checkout/submodule_status.rb) * [YarnInstall](lib/overcommit/hook/post_checkout/yarn_install.rb) ### PostCommit `post-commit` hooks run after a commit is successfully created. A hook failing in this case does not prevent the commit since it has already occurred; however, it can be used to alert the user to some issue. * [BowerInstall](lib/overcommit/hook/post_commit/bower_install.rb) * [BundleInstall](lib/overcommit/hook/post_commit/bundle_install.rb) * [Commitplease](lib/overcommit/hook/post_commit/commitplease.rb) * [ComposerInstall](lib/overcommit/hook/post_commit/composer_install.rb) * [GitGuilt](lib/overcommit/hook/post_commit/git_guilt.rb) * [IndexTags](lib/overcommit/hook/post_commit/index_tags.rb) * [NpmInstall](lib/overcommit/hook/post_commit/npm_install.rb) * [SubmoduleStatus](lib/overcommit/hook/post_commit/submodule_status.rb) * [YarnInstall](lib/overcommit/hook/post_commit/yarn_install.rb) ### PostMerge `post-merge` hooks run after a `git merge` executes successfully with no merge conflicts. A hook failing in this case does not prevent the merge since it has already occurred; however, it can be used to alert the user to some issue. * [BowerInstall](lib/overcommit/hook/post_merge/bower_install.rb) * [BundleInstall](lib/overcommit/hook/post_merge/bundle_install.rb) * [ComposerInstall](lib/overcommit/hook/post_merge/composer_install.rb) * [IndexTags](lib/overcommit/hook/post_merge/index_tags.rb) * [NpmInstall](lib/overcommit/hook/post_merge/npm_install.rb) * [SubmoduleStatus](lib/overcommit/hook/post_merge/submodule_status.rb) * [YarnInstall](lib/overcommit/hook/post_merge/yarn_install.rb) ### PostRewrite `post-rewrite` hooks run after a commit is modified by a `git commit --amend` or `git rebase`. A hook failing in this case does not prevent the rewrite since it has already occurred; however, it can be used to alert the user to some issue. * [BowerInstall](lib/overcommit/hook/post_rewrite/bower_install.rb) * [BundleInstall](lib/overcommit/hook/post_rewrite/bundle_install.rb) * [ComposerInstall](lib/overcommit/hook/post_rewrite/composer_install.rb) * [IndexTags](lib/overcommit/hook/post_rewrite/index_tags.rb) * [NpmInstall](lib/overcommit/hook/post_rewrite/npm_install.rb) * [SubmoduleStatus](lib/overcommit/hook/post_rewrite/submodule_status.rb) * [YarnInstall](lib/overcommit/hook/post_rewrite/yarn_install.rb) ### PreCommit `pre-commit` hooks are run after `git commit` is executed, but before the commit message editor is displayed. If a hook fails, the commit will not be created. These hooks are ideal for syntax checkers, linters, and other checks that you want to run before you allow a commit to even be created. #### WARNING: pre-commit hooks cannot have side effects `pre-commit` hooks currently do not support hooks with side effects (such as modifying files and adding them to the index with `git add`). This is a consequence of Overcommit's pre-commit hook stashing behavior to ensure hooks are run against _only the changes you are about to commit_. Without Overcommit, the proper way to write a `pre-commit` hook would be to extract the staged changes into temporary files and lint those files instead of whatever contents are in your working tree (as you don't want unstaged changes to taint your results). Overcommit takes care of this for you, but to do it in a generalized way introduces this limitation. See the [thread tracking this issue](https://github.com/sds/overcommit/issues/238) for more details. * [`*`AuthorEmail](lib/overcommit/hook/pre_commit/author_email.rb) * [`*`AuthorName](lib/overcommit/hook/pre_commit/author_name.rb) * [BerksfileCheck](lib/overcommit/hook/pre_commit/berksfile_check.rb) * [`*`BrokenSymlinks](lib/overcommit/hook/pre_commit/broken_symlinks.rb) * [BundleAudit](lib/overcommit/hook/pre_commit/bundle_audit.rb) * [BundleCheck](lib/overcommit/hook/pre_commit/bundle_check.rb) * [BundleOutdated](lib/overcommit/hook/pre_commit/bundle_outdated.rb) * [`*`CaseConflicts](lib/overcommit/hook/pre_commit/case_conflicts.rb) * [ChamberSecurity](lib/overcommit/hook/pre_commit/chamber_security.rb) * [CodeSpellCheck](lib/overcommit/hook/pre_commit/code_spell_check.rb) * [CoffeeLint](lib/overcommit/hook/pre_commit/coffee_lint.rb) * [Credo](lib/overcommit/hook/pre_commit/credo.rb) * [CssLint](lib/overcommit/hook/pre_commit/css_lint.rb) * [DartAnalyzer](lib/overcommit/hook/pre_commit/dart_analyzer.rb) * [Dogma](lib/overcommit/hook/pre_commit/dogma.rb) * [ErbLint](lib/overcommit/hook/pre_commit/erb_lint.rb) * [EsLint](lib/overcommit/hook/pre_commit/es_lint.rb) * [ExecutePermissions](lib/overcommit/hook/pre_commit/execute_permissions.rb) * [Fasterer](lib/overcommit/hook/pre_commit/fasterer.rb) * [FileSize](lib/overcommit/hook/pre_commit/file_size.rb) * [FixMe](lib/overcommit/hook/pre_commit/fix_me.rb) * [Flay](lib/overcommit/hook/pre_commit/flay.rb) * [Foodcritic](lib/overcommit/hook/pre_commit/foodcritic.rb) * [ForbiddenBranches](lib/overcommit/hook/pre_commit/forbidden_branches.rb) * [GoLint](lib/overcommit/hook/pre_commit/go_lint.rb) * [GoVet](lib/overcommit/hook/pre_commit/go_vet.rb) * [Hadolint](lib/overcommit/hook/pre_commit/hadolint.rb) * [LicenseFinder](lib/overcommit/hook/pre_commit/license_finder.rb) * [HamlLint](lib/overcommit/hook/pre_commit/haml_lint.rb) * [HardTabs](lib/overcommit/hook/pre_commit/hard_tabs.rb) * [Hlint](lib/overcommit/hook/pre_commit/hlint.rb) * [HtmlHint](lib/overcommit/hook/pre_commit/html_hint.rb) * [HtmlTidy](lib/overcommit/hook/pre_commit/html_tidy.rb) * [ImageOptim](lib/overcommit/hook/pre_commit/image_optim.rb) * [JavaCheckstyle](lib/overcommit/hook/pre_commit/java_checkstyle.rb) * [Jscs](lib/overcommit/hook/pre_commit/jscs.rb) * [JsHint](lib/overcommit/hook/pre_commit/js_hint.rb) * [JsLint](lib/overcommit/hook/pre_commit/js_lint.rb) * [Jsl](lib/overcommit/hook/pre_commit/jsl.rb) * [JsonSyntax](lib/overcommit/hook/pre_commit/json_syntax.rb) * [KtLint](lib/overcommit/hook/pre_commit/kt_lint.rb) * [LicenseHeader](lib/overcommit/hook/pre_commit/license_header.rb) * [LineEndings](lib/overcommit/hook/pre_commit/line_endings.rb) * [LocalPathsInGemfile](lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb) * [Mdl](lib/overcommit/hook/pre_commit/mdl.rb) * [`*`MergeConflicts](lib/overcommit/hook/pre_commit/merge_conflicts.rb) * [NginxTest](lib/overcommit/hook/pre_commit/nginx_test.rb) * [PhpCs](lib/overcommit/hook/pre_commit/php_cs.rb) * [PhpCsFixer](lib/overcommit/hook/pre_commit/php_cs_fixer.rb) * [PhpLint](lib/overcommit/hook/pre_commit/php_lint.rb) * [PhpStan](lib/overcommit/hook/pre_commit/php_stan.rb) * [Pronto](lib/overcommit/hook/pre_commit/pronto.rb) * [PuppetLint](lib/overcommit/hook/pre_commit/puppet_lint.rb) * [PuppetMetadataJsonLint](lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb) * [Pycodestyle](lib/overcommit/hook/pre_commit/pycodestyle.rb) * [Pydocstyle](lib/overcommit/hook/pre_commit/pydocstyle.rb) * [Pyflakes](lib/overcommit/hook/pre_commit/pyflakes.rb) * [Pylint](lib/overcommit/hook/pre_commit/pylint.rb) * [PythonFlake8](lib/overcommit/hook/pre_commit/python_flake8.rb) * [RakeTarget](lib/overcommit/hook/pre_commit/rake_target.rb) * [RailsBestPractices](lib/overcommit/hook/pre_commit/rails_best_practices.rb) * [RailsSchemaUpToDate](lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb) * [Reek](lib/overcommit/hook/pre_commit/reek.rb) * [RuboCop](lib/overcommit/hook/pre_commit/rubo_cop.rb) * [RubyLint](lib/overcommit/hook/pre_commit/ruby_lint.rb) * [RubySyntax](lib/overcommit/hook/pre_commit/ruby_syntax.rb) * [SwiftLint](lib/overcommit/hook/pre_commit/swift_lint.rb) * [Scalariform](lib/overcommit/hook/pre_commit/scalariform.rb) * [Scalastyle](lib/overcommit/hook/pre_commit/scalastyle.rb) * [ScssLint](lib/overcommit/hook/pre_commit/scss_lint.rb) * [SemiStandard](lib/overcommit/hook/pre_commit/semi_standard.rb) * [ShellCheck](lib/overcommit/hook/pre_commit/shell_check.rb) * [SlimLint](lib/overcommit/hook/pre_commit/slim_lint.rb) * [Sorbet](lib/overcommit/hook/pre_commit/sorbet.rb) * [Sqlint](lib/overcommit/hook/pre_commit/sqlint.rb) * [Standard](lib/overcommit/hook/pre_commit/standard.rb) * [Stylelint](lib/overcommit/hook/pre_commit/stylelint.rb) * [TrailingWhitespace](lib/overcommit/hook/pre_commit/trailing_whitespace.rb) * [TravisLint](lib/overcommit/hook/pre_commit/travis_lint.rb) * [TsLint](lib/overcommit/hook/pre_commit/ts_lint.rb) * [Vint](lib/overcommit/hook/pre_commit/vint.rb) * [W3cCss](lib/overcommit/hook/pre_commit/w3c_css.rb) * [W3cHtml](lib/overcommit/hook/pre_commit/w3c_html.rb) * [XmlLint](lib/overcommit/hook/pre_commit/xml_lint.rb) * [XmlSyntax](lib/overcommit/hook/pre_commit/xml_syntax.rb) * [YamlLint](lib/overcommit/hook/pre_commit/yaml_lint.rb) * [YamlSyntax](lib/overcommit/hook/pre_commit/yaml_syntax.rb) * [YardCoverage](lib/overcommit/hook/pre_commit/yard_coverage.rb) * [YarnCheck](lib/overcommit/hook/pre_commit/yarn_check.rb) ### PrePush `pre-push` hooks are run during `git push`, after remote refs have been updated but before any objects have been transferred. If a hook fails, the push is aborted. * [Brakeman](lib/overcommit/hook/pre_push/brakeman.rb) * [FlutterTest](lib/overcommit/hook/pre_push/flutter_test.rb) * [Minitest](lib/overcommit/hook/pre_push/minitest.rb) * [PhpUnit](lib/overcommit/hook/pre_push/php_unit.rb) * [Pronto](lib/overcommit/hook/pre_push/pronto.rb) * [ProtectedBranches](lib/overcommit/hook/pre_push/protected_branches.rb) * [PubTest](lib/overcommit/hook/pre_push/pub_test.rb) * [Pytest](lib/overcommit/hook/pre_push/pytest.rb) * [PythonNose](lib/overcommit/hook/pre_push/python_nose.rb) * [RakeTarget](lib/overcommit/hook/pre_push/rake_target.rb) * [RSpec](lib/overcommit/hook/pre_push/r_spec.rb) * [TestUnit](lib/overcommit/hook/pre_push/test_unit.rb) ### PreRebase `pre-rebase` hooks are run during `git rebase`, before any commits are rebased. If a hook fails, the rebase is aborted. * [MergedCommits](lib/overcommit/hook/pre_rebase/merged_commits.rb) ## Repo-Specific hooks Out of the box, `overcommit` comes with a set of hooks that enforce a variety of styles and lints. However, some hooks only make sense in the context of a specific repository. For example, you can have a number of simple checks that run against your code to catch common errors. For example, if you use [RSpec](http://rspec.info/), you can make sure all spec files contain the line `require 'spec_helper'`. Inside our repository, we can add the file `.git-hooks/pre_commit/ensure_spec_helper.rb` in order to automatically check our spec files: ```ruby module Overcommit::Hook::PreCommit class EnsureSpecHelper < Base def run errors = [] applicable_files.each do |file| if File.read(file) !~ /^require 'spec_helper'/ errors << "#{file}: missing `require 'spec_helper'`" end end return :fail, errors.join("\n") if errors.any? :pass end end end ``` The corresponding configuration for this hook would look like: ```yaml PreCommit: EnsureSpecHelper: enabled: true description: 'Checking for missing inclusion of spec_helper' include: '**/*_spec.rb' ``` ### Adding Existing Git Hooks You might already have hook scripts written which you'd like to integrate with Overcommit right away. To make this easy, Overcommit allows you to include your hook script in your configuration without writing any Ruby code. For example: ```yaml PostCheckout: CustomScript: enabled: true required_executable: './bin/custom-script' ``` So long as a command is given (either by specifying the `command` option directly or specifying `required_executable`) a special hook is created that executes the command and appends any arguments and standard input stream that would have been passed to the regular hook. The hook passes or fails based on the exit status of the command. The script is executed as if Git were calling the hook directly. If you want to understand which arguments are passed to the script depending on the type of hook, see the [git-hooks documentation][GHD]. [GHD]: https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks ## Security While Overcommit can make managing Git hooks easier and more convenient, this convenience can come at a cost of being less secure. Since installing Overcommit hooks will allow arbitrary plugin code in your repository to be executed, you expose yourself to an attack where checking out code from a third party can result in malicious code being executed on your system. As an example, consider the situation where you have an open source project. An attacker could submit a pull request which adds a `post-checkout` hook that executes some malicious code. When you fetch and checkout this pull request, the `post-checkout` hook will be run on your machine, along with the malicious code that you just checked out. Overcommit attempts to address this problem by storing a signature of your configuration and all hook plugin code since the last time it ran. When the signature changes, a warning is displayed alerting you to which plugins have changed. It is then up to you to manually verify that the changes are not malicious, and then continue running the hooks. The signature is derived from the contents of the plugin's source code itself and any configuration for the plugin. Thus a change to the plugin's source code or your local repo's `.overcommit.yml` file could result in a signature change. ### Disabling Signature Checking In typical usage, your plugins usually don't change too often, so this warning shouldn't become a nuisance. However, users who work within proprietary repositories where all developers who can push changes to the repository already have a minimum security clearance may wish to disable this check. While not recommended, you can disable signature verification by setting `verify_signatures` to `false` in your `.overcommit.yml` file. **Regardless of whether you have `verify_signatures` disabled for your project, if you are running Overcommit for the first time you will need to sign your configuration with `overcommit --sign`**. This needs to happen once so Overcommit can record in your local git repo's configuration (outside of source control) that you intend to enable/disable verification. This way if someone else changes `verify_signatures` you'll be asked to confirm the change. ## Contributing We love contributions to Overcommit, be they bug reports, feature ideas, or pull requests. See our [guidelines for contributing](CONTRIBUTING.md) to best ensure your thoughts, ideas, or code get merged. ## Community All major discussion surrounding Overcommit happens on the [GitHub issues list](https://github.com/sds/overcommit/issues). ## Changelog If you're interested in seeing the changes and bug fixes between each version of `overcommit`, read the [Overcommit Changelog](CHANGELOG.md). ## License This project is released under the [MIT license](MIT-LICENSE). The Overcommit logo is adapted from the [Git Logo by Jason Long][GL], and is licensed under the [Creative Commons Attribution 3.0 Unported License][CC3]. [GL]: https://git-scm.com/downloads/logos [CC3]: http://creativecommons.org/licenses/by/3.0/ ================================================ FILE: bin/overcommit ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Check if Overcommit should invoke a Bundler context for loading gems require 'yaml' if gemfile = YAML.load_file('.overcommit.yml')['gemfile'] rescue nil ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin # We need to temporarily silence STDERR to remove annoying Gem specification # warnings that ultimately don't matter, e.g. # https://github.com/rubygems/rubygems/issues/1070 old_stderr = $stderr begin $stderr = File.new(File::NULL, 'w') Bundler.setup ensure $stderr = old_stderr end rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG rescue Gem::LoadError => e # Handle case where user is executing overcommit without `bundle exec` and # whose local Gemfile has a gem requirement that does not match a gem # requirement of the installed version of Overcommit. raise unless e.message =~ /already activated/i exec('bundle', 'exec', $0, *ARGV) end end begin require 'overcommit/cli' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else raise end exit 64 # EX_USAGE end logger = Overcommit::Logger.new(STDOUT) Overcommit::CLI.new(ARGV, STDIN, logger).run ================================================ FILE: config/default.yml ================================================ # Default configuration that all Overcommit configurations inherit from. # # This is an opinionated list of which hooks are valuable to run and what their # out-of-the-box settings should be. #------------------------------------------------------------------------------- # Loads Bundler context from a Gemfile. If false, does nothing (default). # # Specifying a Gemfile for Bundler to load allows you to control which gems are # available in the load path (i.e. loadable via `require`) within your hook # runs. Note that having a Gemfile requires you to include `overcommit` itself # in your Gemfile (otherwise Overcommit can't load itself!). # # This is useful if you want to: # # - Enforce a specific version of Overcommit to use for all hook runs # (or to use a version from the master branch that has not been released yet) # - Enforce a specific version or unreleased branch is used for a gem you want # to use in your git hooks # # WARNING: This makes your hook runs slower, but you can work around this! # # Loading a Bundler context necessarily adds a startup delay to your hook runs # as Bundler parses the Gemfile and checks that the dependencies are satisfied. # Thus for projects with many gems this can introduce a noticeable delay. # # The recommended workaround is to create a separate Gemfile in the root of your # repository (call it `.overcommit_gems.rb`), and include only the gems that # your Overcommit hooks need in order to run. This significantly reduces the # startup delay in your hook runs. Make sure to commit both # `.overcommit_gems.rb` and the resulting `.overcommit_gems.rb.lock` file to # 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 # addition to the default hooks Overcommit comes with. The location is relative # to the root of the repository. plugin_directory: '.git-hooks' # Whether to hide hook output by default. This results in completely silent hook # runs except in the case of warning or failure. quiet: false # Number of hooks that can be run concurrently. Typically this won't need to be # adjusted, but if you know that some of your hooks themselves use multiple # processors you can lower this value accordingly. You can define # single-operator mathematical expressions, e.g. '%{processors} * 2', or # '%{processors} / 2'. concurrency: '%{processors}' # Whether to check if a hook plugin has changed since Overcommit last ran it. # This is a defense mechanism when working with repositories which can contain # untrusted code (e.g. when you fetch a pull request from a third party). # See https://github.com/brigade/overcommit#security for more information. verify_signatures: true # Hooks that are run against every commit message after a user has written it. # These hooks are useful for enforcing policies on commit messages written for a # project. CommitMsg: ALL: requires_files: false quiet: false CapitalizedSubject: enabled: true description: 'Check subject capitalization' EmptyMessage: enabled: true description: 'Check for empty commit message' quiet: true GerritChangeId: enabled: false description: 'Ensure Gerrit Change-Id is present' required: true HardTabs: enabled: false description: 'Check for hard tabs' MessageFormat: enabled: false description: 'Check commit message matches expected pattern' pattern: '(.+)[|](.+)[|](.+)' expected_pattern_message: ' | | ' sample_message: 'DEFECT-1234 | Refactored Onboarding flow | John Doe' RussianNovel: enabled: false description: 'Check length of commit message' quiet: true SingleLineSubject: enabled: true description: 'Check subject line' SpellCheck: enabled: false description: 'Check for misspelled words' required_executable: 'hunspell' flags: ['-a'] TextWidth: enabled: true description: 'Check text width' max_subject_width: 60 min_subject_width: 0 max_body_width: 72 TrailingPeriod: enabled: true description: 'Check for trailing periods in subject' # Hooks that are run after `git commit` is executed, before the commit message # editor is displayed. These hooks are ideal for syntax checkers, linters, and # other checks that you want to run before you allow a commit object to be # created. PreCommit: ALL: problem_on_unmodified_line: report requires_files: true required: false quiet: false AuthorEmail: enabled: true description: 'Check author email' requires_files: false required: true quiet: true pattern: '^[^@]+@.*$' AuthorName: enabled: true description: 'Check for author name' requires_files: false required: true quiet: true BerksfileCheck: enabled: false description: 'Check Berksfile lock' required_executable: 'berks' flags: ['list', '--quiet'] install_command: 'gem install berks' include: - 'Berksfile' - 'Berksfile.lock' BrokenSymlinks: enabled: true description: 'Check for broken symlinks' quiet: true BundleAudit: enabled: false description: 'Check for vulnerable versions of gems' required_executable: 'bundle-audit' install_command: 'gem install bundler-audit' BundleCheck: enabled: false description: 'Check Gemfile dependencies' required_executable: 'bundle' flags: ['check'] install_command: 'gem install bundler' include: - 'Gemfile' - 'Gemfile.lock' - '*.gemspec' BundleOutdated: enabled: false description: 'List installed gems with newer versions available' required_executable: 'bundle' flags: ['outdated', '--strict', '--parseable'] install_command: 'gem install bundler' CaseConflicts: enabled: true description: 'Check for case-insensitivity conflicts' quiet: true ChamberCompare: enabled: false description: 'Check that settings are equivalent between namespaces' required_executable: 'chamber' flags: ['compare'] install_command: 'gem install chamber' namespaces: - ['development'] - ['test'] - ['production'] exclusions: [] include: &chamber_settings_files - 'config/settings*.yml' - 'config/settings*.yml.erb' - 'config/settings/**/*.yml' - 'config/settings/**/*.yml.erb' - 'settings*.yml' - 'settings*.yml.erb' - 'settings/**/*.yml' - 'settings/**/*.yml.erb' ChamberSecurity: enabled: false description: 'Check that settings have been secured with Chamber' required_executable: 'chamber' flags: ['secure', '--files'] install_command: 'gem install chamber' include: *chamber_settings_files ChamberVerification: enabled: false description: 'Verify that all settings changes have been approved' required_executable: 'chamber' flags: ['sign', '--verify'] install_command: 'gem install chamber' include: *chamber_settings_files CodeSpellCheck: enabled: false description: 'Check if all your code is spell-checked correctly' command: 'alfonsox' install_command: 'gem install alfonsox' include: - '**/*.rb' - '**/*.erb' CoffeeLint: enabled: false description: 'Analyze with coffeelint' required_executable: 'coffeelint' flags: ['--reporter=csv'] install_command: 'npm install -g coffeelint' include: '**/*.coffee' CookStyle: enabled: false description: 'Analyze with CookStyle' required_executable: 'cookstyle' flags: ['--format=emacs', '--force-exclusion', '--display-cop-names'] install_command: 'gem install cookstyle' include: - '**/*.rb' - '**/*.erb' Credo: enabled: false description: 'Analyze with credo' required_executable: 'mix' flags: ['credo', '--all', '--strict', '--format', 'flycheck'] include: - '**/*.ex' - '**/*.exs' CssLint: enabled: false description: 'Analyze with csslint' required_executable: 'csslint' flags: ['--quiet', '--format=compact'] install_command: 'npm install -g csslint' include: '**/*.css' DartAnalyzer: enabled: false description: 'Analyze with dartanalyzer' required_executable: 'dartanalyzer' flags: [] include: - '**/*.dart' Dogma: enabled: false description: 'Analyze with dogma' required_executable: 'mix' flags: ['dogma'] include: - '**/*.ex' - '**/*.exs' ErbLint: enabled: false description: 'Analyze with ERB Lint' required_executable: 'erblint' install_command: 'bundle install erb_lint' include: '**/*.html.erb' EsLint: enabled: false description: 'Analyze with ESLint' required_executable: 'eslint' flags: ['--format=compact'] install_command: 'npm install -g eslint' include: '**/*.js' ExecutePermissions: enabled: false description: 'Check for file execute permissions' quiet: true Fasterer: enabled: false description: 'Analyzing for potential speed improvements' required_executable: 'fasterer' install_command: 'gem install fasterer' include: '**/*.rb' FixMe: enabled: false description: 'Check for "token" strings' required_executable: 'grep' flags: ['-IEHnw'] keywords: ['BROKEN', 'BUG', 'ERROR', 'FIXME', 'HACK', 'NOTE', 'OPTIMIZE', 'REVIEW', 'TODO', 'WTF', 'XXX'] FileSize: enabled: false description: 'Check for oversized files' size_limit_bytes: 1_000_000 Flay: enabled: false description: 'Analyze ruby code for structural similarities with Flay' required_executable: 'flay' install_command: 'gem install flay' mass_threshold: 16 fuzzy: 1 liberal: false include: '**/*.rb' Foodcritic: enabled: false description: 'Analyze with Foodcritic' required_executable: 'foodcritic' flags: ['--epic-fail=any'] install_command: 'gem install foodcritic' ForbiddenBranches: enabled: false description: 'Check for commit to forbidden branch' quiet: true branch_patterns: ['master'] GinkgoFocus: enabled: false description: 'Check for "focused" tests' required_executable: 'grep' flags: ['-IEHnw'] keywords: ['FContext','FDescribe','FIt','FMeasure','FSpecify','FWhen'] GoFmt: enabled: false description: 'Fix with go fmt' required_executable: 'go' command: ['go', 'fmt'] parallelize: false include: '**/*.go' GolangciLint: enabled: false description: 'Analyze with golangci-lint' required_executable: 'golangci-lint' install_command: 'go get github.com/golangci/golangci-lint/cmd/golangci-lint' flags: ['--out-format=line-number', '--print-issued-lines=false'] command: ['golangci-lint', 'run'] include: '**/*.go' GoLint: enabled: false description: 'Analyze with golint' required_executable: 'golint' install_command: 'go get github.com/golang/lint/golint' include: '**/*.go' GoVet: enabled: false description: 'Analyze with go vet' required_executable: 'go' flags: ['tool', 'vet'] install_command: 'go get golang.org/x/tools/cmd/vet' include: '**/*.go' Hadolint: enabled: false description: 'Analyze with hadolint' required_executable: 'hadolint' include: - '**/Dockerfile*' HamlLint: enabled: false description: 'Analyze with haml-lint' required_executable: 'haml-lint' install_command: 'gem install haml-lint' flags: ['--no-summary'] include: '**/*.haml' HardTabs: enabled: false description: 'Check for hard tabs' quiet: true required_executable: 'grep' flags: ['-IHn', "\t"] exclude: - '**/Makefile' - '**/*.go' Hlint: enabled: false description: 'Analyze with hlint' required_executable: 'hlint' install_command: 'cabal install hlint' include: '**/*.hs' HtmlHint: enabled: false description: 'Analyze with HTMLHint' required_executable: 'htmlhint' install_command: 'npm install -g htmlhint' include: '**/*.html' HtmlTidy: enabled: false description: 'Analyze HTML with tidy' required_executable: 'tidy' flags: ['-errors', '-quiet', '-utf8'] include: '**/*.html' ImageOptim: enabled: false description: 'Check for optimizable images' required_executable: 'image_optim' install_command: 'gem install image_optim' include: - '**/*.gif' - '**/*.jpeg' - '**/*.jpg' - '**/*.png' - '**/*.svg' JavaCheckstyle: enabled: false description: 'Analyze with checkstyle' required_executable: 'checkstyle' flags: ['-c', '/sun_checks.xml'] include: '**/*.java' Jscs: enabled: false description: 'Analyze with JSCS' required_executable: 'jscs' flags: ['--reporter=inline'] install_command: 'npm install -g jscs' include: '**/*.js' JsHint: enabled: false description: 'Analyze with JSHint' required_executable: 'jshint' flags: ['--verbose'] install_command: 'npm install -g jshint' include: '**/*.js' JsLint: enabled: false description: 'Analyze with JSLint' required_executable: 'jslint' flags: ['--terse'] install_command: 'npm install -g jslint' include: '**/*.js' Jsl: enabled: false description: 'Analyze with JSL' required_executable: 'jsl' flags: ['-nologo', '-nofilelisting', '-nocontext', '-nosummary'] include: '**/*.js' JsonSyntax: enabled: false description: 'Validate JSON syntax' required_library: 'json' install_command: 'gem install json' include: '**/*.json' KtLint: enabled: false description: 'Analyze with KtLint' required_executable: 'ktlint' flags: [] include: '**/*.kt' LicenseFinder: enabled: false description: 'Analyze with LicenseFinder' required_executable: 'license_finder' install_command: 'gem install license_finder' include: - 'Gemfile' - 'requirements.txt' - 'package.json' - 'pom.xml' - 'build.gradle' - 'bower.json' - 'Podfile' - 'rebar.config' LicenseHeader: enabled: false license_file: 'LICENSE.txt' description: 'Check source files for license headers' LocalPathsInGemfile: enabled: false description: 'Check for local paths in Gemfile' required_executable: 'grep' flags: ['-IHnE', "^[^#]*((\\bpath:)|(:path[ \t]*=>))"] include: '**/Gemfile' Mdl: enabled: false description: 'Analyze markdown files with mdl' required_executable: 'mdl' flags: ['--json'] install_command: 'gem install mdl' include: '**/*.md' MergeConflicts: enabled: true description: 'Check for merge conflicts' quiet: true required_executable: 'grep' flags: ['-IHn', "^<<<<<<<[ \t]"] MixFormat: enabled: false description: 'Check formatting with mix format' required_executable: 'mix' flags: ['format', '--check-formatted'] include: - '**/*.ex' - '**/*.heex' - '**/*.exs' PuppetMetadataJsonLint: enabled: false description: 'Checking module metadata' flags: ['--strict-license', '--strict-dependencies', '--fail-on-warning'] include: 'metadata.json' required_executable: 'metadata-json-lint' install_command: 'gem install metadata-json-lint' NginxTest: enabled: false description: 'Test nginx configs' required_executable: 'nginx' flags: ['-t'] include: '**/nginx.conf' Pep257: # Deprecated – use Pydocstyle instead. enabled: false description: 'Analyze docstrings with pep257' required_executable: 'pep257' install_command: 'pip install pep257' include: '**/*.py' Pep8: # Deprecated – use Pycodestyle instead. enabled: false description: 'Analyze with pep8' required_executable: 'pep8' install_command: 'pip install pep8' include: '**/*.py' PhpLint: enabled: false description: 'Testing with PHP lint' required_executable: 'php' command: 'php' flags: ['-l'] include: '**/*.php' PhpCs: enabled: false description: 'Analyze with PHP_CodeSniffer' command: 'vendor/bin/phpcs' flags: ['--standard=PSR2', '--report=csv'] include: '**/*.php' PhpCsFixer: enabled: false description: 'Fix non compliant PHP files' required_executable: 'php-cs-fixer' command: 'vendor/bin/php-cs-fixer' flags: ['fix', '-v', '--path-mode=intersection'] install_command: 'composer global require friendsofphp/php-cs-fixer' include: '**/*.php' PhpStan: description: 'Analyze with phpstan' enabled: false command: 'phpstan' flags: ['analyze', '--errorFormat=raw'] include: - '**/*.php' Pronto: enabled: false description: 'Analyzing with pronto' required_executable: 'pronto' install_command: 'gem install pronto' flags: ['run', '--staged', '--exit-code'] PuppetLint: enabled: false description: 'Analyze with puppet-lint' required_executable: 'puppet-lint' install_command: 'gem install puppet-lint' flags: - '--log-format="%{fullpath}:%{line}:%{column}:%{KIND}: %{message} (%{check})"' - '--fail-on-warnings' - '--error-level=all' include: '**/*.pp' Pycodestyle: enabled: false description: 'Analyze with pycodestyle' required_executable: 'pycodestyle' install_command: 'pip install pycodestyle' include: '**/*.py' Pydocstyle: enabled: false description: 'Analyze docstrings with pydocstyle' required_executable: 'pydocstyle' install_command: 'pip install pydocstyle' include: '**/*.py' Pyflakes: enabled: false description: 'Analyze with pyflakes' required_executable: 'pyflakes' install_command: 'pip install pyflakes' include: '**/*.py' Pylint: enabled: false description: 'Analyze with Pylint' required_executable: 'pylint' install_command: 'pip install pylint' flags: - '--msg-template="{path}:{line}:{C}: {msg} ({symbol})"' - '--reports=n' - '--persistent=n' include: '**/*.py' PythonFlake8: enabled: false description: 'Analyze with flake8' required_executable: 'flake8' install_command: 'pip install flake8' include: '**/*.py' RakeTarget: enabled: false description: 'Run rake targets' # targets: # - 'lint' # - 'validate' # - '...' required_executable: 'rake' install_command: 'gem install rake' RailsBestPractices: enabled: false description: 'Analyze with RailsBestPractices' required_executable: 'rails_best_practices' flags: ['--without-color'] install_command: 'gem install rails_best_practices' RailsSchemaUpToDate: enabled: false description: 'Check if database schema is up to date' include: - 'db/migrate/*.rb' - 'db/schema.rb' - 'db/structure.sql' Reek: enabled: false description: 'Analyze with Reek' required_executable: 'reek' flags: ['--single-line', '--no-color', '--force-exclusion'] install_command: 'gem install reek' include: - '**/*.gemspec' - '**/*.rake' - '**/*.rb' - '**/Gemfile' - '**/Rakefile' RstLint: enabled: false description: 'Analyze reStructuredText files with rst-lint' required_executable: 'rst-lint' install_command: 'pip install restructuredtext_lint' include: '**/*.rst' RSpec: enabled: false description: 'Run tests with Rspec' required_executable: 'rspec' RuboCop: enabled: false description: 'Analyze with RuboCop' required_executable: 'rubocop' flags: ['--format=emacs', '--force-exclusion', '--display-cop-names'] install_command: 'gem install rubocop' include: - '**/*.gemspec' - '**/*.rake' - '**/*.rb' - '**/*.ru' - '**/Gemfile' - '**/Rakefile' RubyLint: enabled: false description: 'Analyze with ruby-lint' required_executable: 'ruby-lint' flags: ['--presenter=syntastic', '--levels=error,warning'] install_command: 'gem install ruby-lint' include: - '**/*.gemspec' - '**/*.rb' RubySyntax: enabled: false description: 'Check ruby syntax' required_executable: 'ruby' command: [ 'ruby', '-e', 'ARGV.each { |applicable_file| ruby_c_output = `ruby -c #{applicable_file}`; puts ruby_c_output unless $?.success? }' ] include: - '**/*.gemspec' - '**/*.rb' Scalariform: enabled: false description: 'Check formatting with Scalariform' required_executable: 'scalariform' flags: ['--test'] include: '**/*.scala' Scalastyle: enabled: false description: 'Analyze with Scalastyle' required_executable: 'scalastyle' include: '**/*.scala' ScssLint: enabled: false description: 'Analyze with scss-lint' required_library: 'json' required_executable: 'scss-lint' flags: ['--format', 'JSON'] install_command: 'gem install scss_lint' include: '**/*.scss' SemiStandard: enabled: false description: 'Analyze with semistandard' required_executable: 'semistandard' flags: ['--verbose'] install_command: 'npm install -g semistandard' include: '**/*.js' ShellCheck: enabled: false description: 'Analyze with ShellCheck' required_executable: 'shellcheck' flags: ['--format=gcc'] include: '**/*.sh' SlimLint: enabled: false description: 'Analyze with slim-lint' required_executable: 'slim-lint' install_command: 'gem install slim_lint' include: '**/*.slim' Solargraph: enabled: false description: 'Typecheck with Solargraph' requires_files: true required_executable: 'solargraph' install_command: 'gem install solargraph' flags: ['typecheck', '--level', 'strong'] include: '**/*.rb' exclude: - 'spec/**/*.rb' - 'test/**/*.rb' - 'vendor/**/*.rb' - '.bundle/**/*.rb' 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' required_executable: 'sqlint' install_command: 'gem install sqlint' include: '**/*.sql' Standard: enabled: false description: 'Analyze with standard' required_executable: 'standard' flags: ['--verbose'] install_command: 'npm install -g standard' include: '**/*.js' Stylelint: enabled: false description: 'Check styles with Stylelint' required_executable: 'stylelint' flags: ['-f', 'compact'] install_command: 'npm install -g stylelint' include: - '**/*.scss' - '**/*.css' - '**/*.less' SwiftLint: enabled: false description: 'Analyze with SwiftLint' required_executable: 'swiftlint' flags: ['lint', '--strict'] install_command: 'brew install swiftlint' include: '**/*.swift' TerraformFormat: enabled: false description: 'Analyze with Terraform' required_executable: 'terraform' flags: ['fmt', '-check=true', '-diff=false'] include: '**/*.tf' TsLint: enabled: false description: 'Analyze with TSLint' required_executable: 'tslint' install_command: 'npm install -g tslint typescript' include: '**/*.ts' TrailingWhitespace: enabled: false description: 'Check for trailing whitespace' required_executable: 'grep' flags: ['-IHn', "[ \t]$"] TravisLint: enabled: false description: 'Check Travis CI configuration' required_executable: 'travis' flags: ['lint'] install_command: 'gem install travis' include: '.travis.yml' Vint: enabled: false description: 'Analyze with Vint' required_executable: 'vint' install_command: 'pip install vim-vint' include: - '**/*.vim' - '**/*.vimrc' W3cCss: enabled: false description: 'Analyze with W3C CSS validation service' required_library: 'w3c_validators' install_command: 'gem install w3c_validators' validator_uri: 'http://jigsaw.w3.org/css-validator/validator' language: 'en' profile: 'css3' warn_level: 2 include: - '**/*.css' W3cHtml: enabled: false description: 'Analyze with W3C HTML validation service' required_library: 'w3c_validators' install_command: 'gem install w3c_validators' validator_uri: 'https://validator.w3.org/nu' charset: 'utf-8' doctype: 'HTML5' include: - '**/*.html' LineEndings: description: 'Check line endings' enabled: false eol: "\n" # or "\r\n" for Windows-style newlines XmlLint: enabled: false description: 'Analyze with xmllint' required_executable: 'xmllint' flags: ['--noout'] include: - '**/*.xml' - '**/*.svg' XmlSyntax: enabled: false description: 'Check XML syntax' required_library: 'rexml/document' include: - '**/*.xml' - '**/*.svg' YamlLint: enabled: false description: 'Analyze with YAMLlint' required_executable: 'yamllint' flags: ['--format=parsable', '--strict'] install_command: 'pip install yamllint' include: - '**/*.yaml' - '**/*.yml' YamlSyntax: enabled: false description: 'Check YAML syntax' required_library: 'yaml' include: - '**/*.yaml' - '**/*.yml' YardCoverage: enabled: false description: 'Checking for yard coverage' command: ['yard', 'stats', '--list-undoc', '--compact'] flags: ['--private', '--protected'] required_executable: 'yard' install_command: 'gem install yard' min_coverage_percentage: 100 include: - '/**/*.rb' YarnCheck: enabled: false description: 'Check yarn.lock dependencies' required_executable: 'yarn' flags: ['check', '--silent', '--no-progress', '--non-interactive'] install_command: 'npm install --global yarn' include: - 'package.json' - 'yarn.lock' # Hooks that run after HEAD changes or a file is explicitly checked out. PostCheckout: ALL: required: false quiet: false skip_file_checkout: true BowerInstall: enabled: false description: 'Install bower dependencies' requires_files: true required_executable: 'bower' install_command: 'npm install -g bower' flags: ['install'] include: 'bower.json' BundleInstall: enabled: false description: 'Install Bundler dependencies' requires_files: true required_executable: 'bundle' install_command: 'gem install bundler' flags: ['install'] include: - 'Gemfile' - 'Gemfile.lock' - '*.gemspec' ComposerInstall: enabled: false description: 'Install composer dependencies' requires_files: true required_executable: 'composer' install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer' flags: ['install'] include: 'composer.json' GitLfs: enabled: false description: 'Check status of lockable files tracked by Git LFS' required_executable: 'git-lfs' install_command: 'brew install git-lfs' IndexTags: enabled: false description: 'Generate tags file from source' quiet: true required_executable: 'ctags' NpmInstall: enabled: false description: 'Install NPM dependencies' requires_files: true required_executable: 'npm' flags: ['install'] include: - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: enabled: false description: 'Check submodule status' quiet: true recursive: false YarnInstall: enabled: false description: 'Install Yarn dependencies' requires_files: true required_executable: 'yarn' flags: ['install'] include: - 'package.json' - 'yarn.lock' # Hooks that run after a commit is created. PostCommit: ALL: requires_files: false required: false quiet: false BowerInstall: enabled: false description: 'Install bower dependencies' requires_files: true required_executable: 'bower' install_command: 'npm install -g bower' flags: ['install'] include: 'bower.json' BundleInstall: enabled: false description: 'Install Bundler dependencies' requires_files: true required_executable: 'bundle' install_command: 'gem install bundler' flags: ['install'] include: - 'Gemfile' - 'Gemfile.lock' - '*.gemspec' Commitplease: enabled: false description: 'Analyze with Commitplease' required_executable: './node_modules/.bin/commitplease' install_command: 'npm install --save-dev commitplease' flags: ['-1'] ComposerInstall: enabled: false description: 'Install composer dependencies' requires_files: true required_executable: 'composer' install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer' flags: ['install'] include: 'composer.json' GitGuilt: enabled: false description: 'Calculate changes in blame since last commit' requires_files: true required_executable: 'git-guilt' flags: ['HEAD~', 'HEAD'] install_command: 'npm install -g git-guilt' GitLfs: enabled: false description: 'Check status of lockable files tracked by Git LFS' required_executable: 'git-lfs' install_command: 'brew install git-lfs' IndexTags: enabled: false description: 'Generate tags file from source' quiet: true required_executable: 'ctags' NpmInstall: enabled: false description: 'Install NPM dependencies' requires_files: true required_executable: 'npm' flags: ['install'] include: - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: enabled: false description: 'Check submodule status' quiet: true recursive: false YarnInstall: enabled: false description: 'Install Yarn dependencies' requires_files: true required_executable: 'yarn' flags: ['install'] include: - 'package.json' - 'yarn.lock' # Hooks that run after `git merge` executes successfully (no merge conflicts). PostMerge: ALL: requires_files: false quiet: false BowerInstall: enabled: false description: 'Install bower dependencies' requires_files: true required_executable: 'bower' install_command: 'npm install -g bower' flags: ['install'] include: 'bower.json' BundleInstall: enabled: false description: 'Install Bundler dependencies' requires_files: true required_executable: 'bundle' install_command: 'gem install bundler' flags: ['install'] include: - 'Gemfile' - 'Gemfile.lock' - '*.gemspec' ComposerInstall: enabled: false description: 'Install composer dependencies' requires_files: true required_executable: 'composer' install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer' flags: ['install'] include: 'composer.json' GitLfs: enabled: false description: 'Check status of lockable files tracked by Git LFS' required_executable: 'git-lfs' install_command: 'brew install git-lfs' IndexTags: enabled: false description: 'Generate tags file from source' quiet: true required_executable: 'ctags' NpmInstall: enabled: false description: 'Install NPM dependencies' requires_files: true required_executable: 'npm' flags: ['install'] include: - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: enabled: false description: 'Check submodule status' quiet: true recursive: false YarnInstall: enabled: false description: 'Install Yarn dependencies' requires_files: true required_executable: 'yarn' flags: ['install'] include: - 'package.json' - 'yarn.lock' # Hooks that run after a commit is modified by an amend or rebase. PostRewrite: ALL: requires_files: false quiet: false BowerInstall: enabled: false description: 'Install bower dependencies' requires_files: true required_executable: 'bower' install_command: 'npm install -g bower' flags: ['install'] include: 'bower.json' BundleInstall: enabled: false description: 'Install Bundler dependencies' requires_files: true required_executable: 'bundle' install_command: 'gem install bundler' flags: ['install'] include: - 'Gemfile' - 'Gemfile.lock' - '*.gemspec' ComposerInstall: enabled: false description: 'Install composer dependencies' requires_files: true required_executable: 'composer' install_command: 'curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer' flags: ['install'] include: 'composer.json' IndexTags: enabled: false description: 'Generate tags file from source' quiet: true required_executable: 'ctags' NpmInstall: enabled: false description: 'Install NPM dependencies' requires_files: true required_executable: 'npm' flags: ['install'] include: - 'package.json' - 'npm-shrinkwrap.json' SubmoduleStatus: enabled: false description: 'Check submodule status' quiet: true recursive: false YarnInstall: enabled: false description: 'Install Yarn dependencies' requires_files: true required_executable: 'yarn' flags: ['install'] include: - 'package.json' - 'yarn.lock' # Hooks that run during the `prepare-commit-msg` hook. PrepareCommitMsg: ALL: requires_files: false required: false quiet: false ReplaceBranch: enabled: false description: 'Prepends the commit message with text based on the branch name' branch_pattern: '\A(\d+)-(\w+).*\z' replacement_text: '[#\1]' skipped_commit_types: - 'message' # if message is given via `-m`, `-F` - 'template' # if `-t` is given or `commit.template` is set - 'commit' # if `-c`, `-C`, or `--amend` is given - 'merge' # if merging - 'squash' # if squashing on_fail: warn # Hooks that run during `git push`, after remote refs have been updated but # before any objects have been transferred. PrePush: ALL: requires_files: false required: false quiet: false Brakeman: enabled: false description: 'Check for security vulnerabilities' required_executable: 'brakeman' flags: ['--exit-on-warn', '--quiet', '--summary'] install_command: 'gem install brakeman' CargoTest: enabled: false description: 'Run tests with cargo' required_executable: 'cargo' flags: ['test'] include: 'src/**/*.rs' FlutterTest: enabled: false description: 'Run flutter test suite' required_executable: 'flutter' flags: ['test'] GitLfs: enabled: false description: 'Upload files tracked by Git LFS' required_executable: 'git-lfs' install_command: 'brew install git-lfs' GolangciLint: enabled: false description: 'Analyze with golangci-lint' required_executable: 'golangci-lint' install_command: 'go get github.com/golangci/golangci-lint/cmd/golangci-lint' flags: ['--out-format=line-number', '--print-issued-lines=false'] command: ['golangci-lint', 'run'] GoTest: enabled: false description: 'Run go test suite' required_executable: 'go' command: ['go', 'test', './...'] Minitest: enabled: false description: 'Run Minitest test suite' command: ['ruby', '-Ilib:test', '-rminitest', "-e 'exit! Minitest.run'"] include: 'test/**/*_test.rb' MixTest: enabled: false description: 'Run mix test suite' required_executable: 'mix' flags: ['test'] PhpUnit: enabled: false description: 'Run PhpUnit test suite' command: 'vendor/bin/phpunit' flags: ['--bootstrap', 'vendor/autoload.php', 'tests'] install_command: 'composer require --dev phpunit/phpunit' Pronto: enabled: false description: 'Analyzing with pronto' required_executable: 'pronto' install_command: 'gem install pronto' flags: ['run', '--exit-code'] ProtectedBranches: enabled: false description: 'Check for illegal pushes to protected branches' destructive_only: true branches: ['master'] PubTest: enabled: false description: 'Run pub test suite' required_executable: 'pub' flags: ['run', 'test'] Pytest: enabled: false description: 'Run pytest test suite' required_executable: 'pytest' install_command: 'pip install -U pytest' PythonNose: enabled: false description: 'Run nose test suite' required_executable: 'nosetests' install_command: 'pip install -U nose' RSpec: enabled: false description: 'Run RSpec test suite' required_executable: 'rspec' RakeTarget: enabled: false description: 'Run rake targets' # targets: # - 'lint' # - 'validate' # - '...' required_executable: 'rake' install_command: 'gem install rake' TestUnit: enabled: false description: 'Run Test::Unit test suite' command: ['ruby', '-Ilib:test', '-rtest/unit', "-e 'exit! Test::Unit::AutoRunner.run'"] # Hooks that run during `git rebase`, before any commits are rebased. # If a hook fails, the rebase is aborted. PreRebase: ALL: requires_files: false required: false quiet: false MergedCommits: enabled: false description: 'Check for commits that have already been merged' branches: ['master'] ================================================ FILE: config/starter.yml ================================================ # Use this file to configure the Overcommit hooks you wish to use. This will # extend the default configuration defined in: # https://github.com/sds/overcommit/blob/master/config/default.yml # # At the topmost level of this YAML file is a key representing type of hook # being run (e.g. pre-commit, commit-msg, etc.). Within each type you can # customize each hook, such as whether to only run it on certain files (via # `include`), whether to only display output if it fails (via `quiet`), etc. # # For a complete list of hooks, see: # https://github.com/sds/overcommit/tree/master/lib/overcommit/hook # # For a complete list of options that you can use to customize hooks, see: # https://github.com/sds/overcommit#configuration # # Uncomment the following lines to make the configuration take effect. #PreCommit: # RuboCop: # enabled: true # on_warn: fail # Treat all warnings as failures # # TrailingWhitespace: # enabled: true # exclude: # - '**/db/structure.sql' # Ignore trailing whitespace in generated files # #PostCheckout: # ALL: # Special hook name that customizes all hooks of this type # quiet: true # Change all post-checkout hooks to only display output on failure # # IndexTags: # enabled: true # Generate a tags file with `ctags` each time HEAD changes ================================================ FILE: lib/overcommit/cli.rb ================================================ # frozen_string_literal: true require 'overcommit' require 'optparse' module Overcommit # Responsible for parsing command-line options and executing appropriate # application logic based on those options. class CLI # rubocop:disable Metrics/ClassLength def initialize(arguments, input, logger) @arguments = arguments @cli_options = {} @input = input @log = logger @options = {} Overcommit::Utils.log = logger end def run parse_arguments case @options[:action] when :install, :uninstall install_or_uninstall when :template_dir print_template_directory_path when :sign sign when :run_all run_all when :diff diff end rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e exit 64 # EX_USAGE end private attr_reader :log def parse_arguments @parser = create_option_parser begin @parser.parse!(@arguments, into: @cli_options) # Default action is to install @options[:action] ||= :install # Unconsumed arguments are our targets @options[:targets] = @arguments rescue OptionParser::InvalidOption => e print_help @parser.help, e end end def create_option_parser OptionParser.new do |opts| opts.banner = "Usage: #{opts.program_name} [options] [target-repo]" add_information_options(opts) add_installation_options(opts) add_other_options(opts) end end def add_information_options(opts) opts.on_tail('-h', '--help', 'Show this message') do print_help opts.help end opts.on_tail('-v', '--version', 'Show version') do print_version(opts.program_name) end opts.on_tail('-l', '--list-hooks', 'List installed hooks') do print_installed_hooks end end def add_installation_options(opts) opts.on('-u', '--uninstall', 'Remove Overcommit hooks from a repository') do @options[:action] = :uninstall end opts.on('-i', '--install', 'Install Overcommit hooks in a repository') do @options[:action] = :install end opts.on('-f', '--force', 'Overwrite any previously installed hooks') do @options[:force] = true end opts.on('-r [hook]', '--run [hook]', 'Run specified hook against all git tracked files. Defaults to `pre_commit`.') do |arg| # rubocop:disable Layout/LineLength @options[:action] = :run_all @options[:hook_to_run] = arg ? arg.to_s : 'run-all' end opts.on('--diff [ref]', 'Run pre_commit hooks against the diff between a given ref. Defaults to `main`.') do |arg| # rubocop:disable Layout/LineLength @options[:action] = :diff arg end end def add_other_options(opts) opts.on('-s', '--sign [hook]', 'Update hook signatures', String) do |hook_to_sign| @options[:hook_to_sign] = hook_to_sign if hook_to_sign.is_a?(String) @options[:action] = :sign end opts.on('-t', '--template-dir', 'Print location of template directory') do @options[:action] = :template_dir end end def install_or_uninstall if Array(@options[:targets]).empty? @options[:targets] = [Overcommit::Utils.repo_root].compact end if @options[:targets].empty? log.warning 'You are not in a git repository.' log.log 'You must either specify the path to a repository or ' \ 'change your current directory to a repository.' halt 64 # EX_USAGE end @options[:targets].each do |target| 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 def print_template_directory_path puts File.join(Overcommit::HOME, 'template-dir') halt end def print_help(message, error = nil) log.error "#{error}\n" if error log.log message halt(error ? 64 : 0) # 64 = EX_USAGE end def print_version(program_name) log.log "#{program_name} #{Overcommit::VERSION}" halt end # Prints the hooks available in the current repo and whether they're # enabled/disabled. def print_installed_hooks config.all_hook_configs.each do |hook_type, hook_configs| print_hooks_for_hook_type(config, hook_configs, hook_type) end halt end def print_hooks_for_hook_type(repo_config, hook_configs, hook_type) log.log "#{hook_type}:" hook_configs.each do |hook_name, config| log.partial " #{hook_name}: " if config['enabled'] log.success('enabled', true) else log.error('disabled', true) end if repo_config.plugin_hook?(hook_type, hook_name) log.warning(' (plugin)') else log.newline end end end def sign if @options[:hook_to_sign] context = Overcommit::HookContext.create(@options[:hook_to_sign], config, @arguments, @input) Overcommit::HookLoader::PluginHookLoader.new(config, context, log).update_signatures else log.log 'Updating signature for configuration file...' config(verify: false).update_signature! end halt end def run_all empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input context = Overcommit::HookContext.create(@options[:hook_to_run], config, @arguments, empty_stdin) # rubocop:disable Layout/LineLength config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, log, context) runner = Overcommit::HookRunner.new(config, log, context, printer) status = runner.run halt(status ? 0 : 65) end def diff empty_stdin = File.open(File::NULL) # pre-commit hooks don't take input context = Overcommit::HookContext.create('diff', config, @arguments, empty_stdin, **@cli_options) # rubocop:disable Layout/LineLength config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, log, context) 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 end # Returns the configuration for this repository. def config(options = {}) @config ||= Overcommit::ConfigurationLoader.new(log, options).load_repo_config end end end ================================================ FILE: lib/overcommit/command_splitter.rb ================================================ # frozen_string_literal: true module Overcommit # Distributes a list of arguments over multiple invocations of a command. # # This accomplishes the same functionality provided by `xargs` but in a # cross-platform way that does not require any pre-existing tools. # # One of the tradeoffs with this approach is that we no longer deal with a # single exit status from a command, but multiple (one for each invocation). # # This will return a struct similar to `Subprocess::Result` but with # additional `statuses`, `stdouts`, and `stderrs` fields so hook authors can # actually see the results of each invocation. If they don't care, the # standard `status`, `stdout`, and `stderr` will still work but be a # aggregation/concatenation of all statuses/outputs. class CommandSplitter # Encapsulates the result of a split argument run. # # @attr_reader statuses [Array] status codes for invocations # @attr_reader stdouts [Array] standard outputs from invocations # @attr_reader stderrs [Array] standard error outputs from invocations Result = Struct.new(:statuses, :stdouts, :stderrs) do # Returns whether all invocations were successful. # # @return [true,false] def success? status == 0 end # Returns `0` if all invocations returned `0`; `1` otherwise. # # @return [true,false] def status statuses.all? { |code| code == 0 } ? 0 : 1 end # Returns concatenated standard output streams of all invocations in the # order they were executed. # # @return [String] def stdout stdouts.join end # Returns concatenated standard error streams of all invocations in the # order they were executed. # # @return [String] def stderr stderrs.join end end class << self def execute(initial_args, options) options = options.dup if (splittable_args = (options.delete(:args) { [] })).empty? raise Overcommit::Exceptions::InvalidCommandArgs, 'Must specify list of arguments to split on' end # Execute each chunk of arguments in serial. We don't parallelize (yet) # since in theory we want to support parallelization at the hook level # and not within individual hooks. results = extract_argument_lists(initial_args, splittable_args).map do |arg_list| Overcommit::Subprocess.spawn(arg_list, options) end Result.new(results.map(&:status), results.map(&:stdout), results.map(&:stderr)) end private # Given a list of prefix arguments and suffix arguments that can be split, # returns a list of argument lists that are executable on the current OS # without exceeding command line limitations. def extract_argument_lists(args, splittable_args) # Total number of bytes needed to contain the prefix command # (including byte separators between each argument) prefix_bytes = (args.size - 1) + args.reduce(0) { |sum, arg| sum + arg.bytesize } if prefix_bytes >= max_command_length raise Overcommit::Exceptions::InvalidCommandArgs, "Command `#{args.take(5).join(' ')} ...` is longer than the " \ 'maximum number of bytes allowed by the operating system ' \ "(#{max_command_length})" end arg_lists = [] index = 0 while index <= splittable_args.length - 1 arg_list, index = arguments_under_limit(splittable_args, index, max_command_length - prefix_bytes) arg_lists << args + arg_list end arg_lists end # @return [Array, Integer>] tuple of arguments and new index def arguments_under_limit(splittable_args, start_index, byte_limit) index = start_index total_bytes = 0 loop do break if index > splittable_args.length - 1 total_bytes += splittable_args[index].bytesize break if total_bytes > byte_limit # Not enough room index += 1 end if index == start_index # No argument was consumed; perhaps a really long argument? raise Overcommit::Exceptions::InvalidCommandArgs, "Argument `#{splittable_args[index][0..5]}...` exceeds the " \ 'maximum command length when appended to command prefix and ' \ "can't be split further" end [splittable_args[start_index...index], index] end # Returns the maximum number of arguments allowed in a single command on # this system. # # @return [Integer] def max_command_length @max_command_length ||= if Gem.win_platform? # Windows is limited to 2048 since that is a worst-case scenario. # http://blogs.msdn.com/b/oldnewthing/archive/2003/12/10/56028.aspx 2048 else # We fudge factor this by halving the buffer size since *nix systems # usually have pretty large limits, and the actual limit changes # depending on how much of your stack is environment variables. # Definitely erring on the side of overly cautious. `getconf ARG_MAX`.to_i / 2 end end end end end ================================================ FILE: lib/overcommit/configuration.rb ================================================ # frozen_string_literal: true require 'digest' require 'json' module Overcommit # Stores configuration for Overcommit and the hooks it runs. class Configuration # rubocop:disable Metrics/ClassLength # Creates a configuration from the given hash. # # @param hash [Hash] loaded YAML config file as a hash # @param options [Hash] # @option default [Boolean] whether this is the default built-in configuration # @option logger [Overcommit::Logger] def initialize(hash, options = {}) @options = options.dup @options[:logger] ||= Overcommit::Logger.silent @hash = hash # Assign so validator can read original values unless options[:validate] == false @hash = Overcommit::ConfigurationValidator.new.validate(self, hash, options) end end def ==(other) super || @hash == other.hash end # Access the configuration as if it were a hash. # # @param key [String] # @return [Array,Hash,Number,String] def [](key) @hash[key] end # Returns absolute path to the directory that external hook plugins should # be loaded from. def plugin_directory File.join(Overcommit::Utils.repo_root, @hash['plugin_directory'] || '.git-hooks') end def concurrency @concurrency ||= begin cores = Overcommit::Utils.processor_count content = @hash.fetch('concurrency') { '%d' } if content.is_a?(String) concurrency_expr = content % { processors: cores } a, op, b = concurrency_expr.scan(%r{(\d+)\s*([+\-*\/])\s*(\d+)})[0] if a a.to_i.send(op, b.to_i) else concurrency_expr.to_i end else content.to_i end end end # Returns configuration for all hooks in each hook type. # # @return [Hash] def all_hook_configs smart_merge(all_builtin_hook_configs, all_plugin_hook_configs) end # Returns configuration for all built-in hooks in each hook type. # # @return [Hash] def all_builtin_hook_configs hook_configs = {} Overcommit::Utils.supported_hook_type_classes.each do |hook_type| hook_names = @hash[hook_type].keys.reject { |name| name == 'ALL' } hook_configs[hook_type] = Hash[ hook_names.map do |hook_name| [hook_name, for_hook(hook_name, hook_type)] end ] end hook_configs end # Returns configuration for all plugin hooks in each hook type. # # @return [Hash] def all_plugin_hook_configs hook_configs = {} Overcommit::Utils.supported_hook_types.each do |hook_type| hook_type_class_name = Overcommit::Utils.camel_case(hook_type) directory = File.join(plugin_directory, hook_type.tr('-', '_')) plugin_paths = Dir[File.join(directory, '*.rb')].sort hook_names = plugin_paths.map do |path| Overcommit::Utils.camel_case(File.basename(path, '.rb')) end hook_configs[hook_type_class_name] = Hash[ hook_names.map do |hook_name| [hook_name, for_hook(hook_name, Overcommit::Utils.camel_case(hook_type))] end ] end hook_configs end # Returns the built-in hooks that have been enabled for a hook type. def enabled_builtin_hooks(hook_context) @hash[hook_context.hook_class_name].keys. reject { |hook_name| hook_name == 'ALL' }. select { |hook_name| built_in_hook?(hook_context, hook_name) }. select { |hook_name| hook_enabled?(hook_context, hook_name) } end # Returns the ad hoc hooks that have been enabled for a hook type. def enabled_ad_hoc_hooks(hook_context) @hash[hook_context.hook_class_name].keys. reject { |hook_name| hook_name == 'ALL' }. select { |hook_name| ad_hoc_hook?(hook_context, hook_name) }. select { |hook_name| hook_enabled?(hook_context, hook_name) } end # Returns a non-modifiable configuration for a hook. def for_hook(hook, hook_type = nil) unless hook_type components = hook.class.name.split('::') hook = components.last hook_type = components[-2] end # Merge hook configuration with special 'ALL' config hook_config = smart_merge(@hash[hook_type]['ALL'], @hash[hook_type][hook] || {}) # Need to specially handle `enabled` option since not setting it does not # necessarily mean the hook is disabled hook_config['enabled'] = hook_enabled?(hook_type, hook) hook_config.freeze end # Merges the given configuration with this one, returning a new # {Configuration}. The provided configuration will either add to or replace # any options defined in this configuration. def merge(config) self.class.new(smart_merge(@hash, config.hash)) end # Applies additional configuration settings based on the provided # environment variables. def apply_environment!(hook_context, env) skipped_hooks = "#{env['SKIP']} #{env['SKIP_CHECKS']} #{env['SKIP_HOOKS']}".split(/[:, ]/) only_hooks = env.fetch('ONLY') { '' }.split(/[:, ]/) hook_type = hook_context.hook_class_name if only_hooks.any? || skipped_hooks.include?('all') || skipped_hooks.include?('ALL') @hash[hook_type]['ALL']['skip'] = true end only_hooks.select { |hook_name| hook_exists?(hook_context, hook_name) }. map { |hook_name| Overcommit::Utils.camel_case(hook_name) }. each do |hook_name| @hash[hook_type][hook_name] ||= {} @hash[hook_type][hook_name]['skip'] = false end skipped_hooks.select { |hook_name| hook_exists?(hook_context, hook_name) }. map { |hook_name| Overcommit::Utils.camel_case(hook_name) }. each do |hook_name| @hash[hook_type][hook_name] ||= {} @hash[hook_type][hook_name]['skip'] = true end end def plugin_hook?(hook_context_or_type, hook_name) hook_type_name = if hook_context_or_type.is_a?(String) Overcommit::Utils.snake_case(hook_context_or_type) else hook_context_or_type.hook_type_name end hook_name = Overcommit::Utils.snake_case(hook_name) File.exist?(File.join(plugin_directory, hook_type_name, "#{hook_name}.rb")) end # Return whether the signature for this configuration has changed since it # was last calculated. # # @return [true,false] def signature_changed? signature != stored_signature end # Return whether a previous signature has been recorded for this # configuration. # # @return [true,false] def previous_signature? !stored_signature.empty? end # Returns whether this configuration should verify itself by checking the # stored configuration for the repo. # # @return [true,false] def verify_signatures? return false if ENV['OVERCOMMIT_NO_VERIFY'] return true if @hash['verify_signatures'] != false result = Overcommit::Utils.execute( %W[git config --local --get #{verify_signature_config_key}] ) if result.status == 1 # Key doesn't exist return true elsif result.status != 0 raise Overcommit::Exceptions::GitConfigError, "Unable to read from local repo git config: #{result.stderr}" end # We don't cast since we want to allow anything to count as "true" except # a literal zero result.stdout.strip != '0' end # Update the currently stored signature for this hook. def update_signature! result = Overcommit::Utils.execute( %w[git config --local] + [signature_config_key, signature] ) verify_signature_value = @hash['verify_signatures'] ? 1 : 0 result &&= Overcommit::Utils.execute( %W[git config --local #{verify_signature_config_key} #{verify_signature_value}] ) unless result.success? raise Overcommit::Exceptions::GitConfigError, "Unable to write to local repo git config: #{result.stderr}" end end protected attr_reader :hash private def ad_hoc_hook?(hook_context, hook_name) ad_hoc_conf = @hash.fetch(hook_context.hook_class_name) { {} }.fetch(hook_name) { {} } # Ad hoc hooks are neither built-in nor have a plugin file written but # still have a `command` specified to be run !built_in_hook?(hook_context, hook_name) && !plugin_hook?(hook_context, hook_name) && (ad_hoc_conf['command'] || ad_hoc_conf['required_executable']) end def built_in_hook?(hook_context, hook_name) hook_name = Overcommit::Utils.snake_case(hook_name) File.exist?(File.join(Overcommit::HOME, 'lib', 'overcommit', 'hook', hook_context.hook_type_name, "#{hook_name}.rb")) end def hook_exists?(hook_context, hook_name) built_in_hook?(hook_context, hook_name) || plugin_hook?(hook_context, hook_name) || ad_hoc_hook?(hook_context, hook_name) end def hook_enabled?(hook_context_or_type, hook_name) hook_type = if hook_context_or_type.is_a?(String) hook_context_or_type else hook_context_or_type.hook_class_name end individual_enabled = @hash[hook_type].fetch(hook_name) { {} }['enabled'] return individual_enabled unless individual_enabled.nil? all_enabled = @hash[hook_type]['ALL']['enabled'] return all_enabled unless all_enabled.nil? false end def smart_merge(parent, child) # Treat the ALL hook specially so that it overrides any configuration # specified by the default configuration. child_all = child['ALL'] unless child_all.nil? parent = Hash[parent.collect { |k, v| [k, smart_merge(v, child_all)] }] end parent.merge(child) do |_key, old, new| case old when Hash smart_merge(old, new) else new end end end # Returns the unique signature of this configuration. # # @return [String] def signature Digest::SHA256.hexdigest(@hash.to_json) end # Returns the stored signature of this repo's Overcommit configuration. # # This is intended to be compared against the current signature of this # configuration object. # # @return [String] def stored_signature result = Overcommit::Utils.execute( %w[git config --local --get] + [signature_config_key] ) if result.status == 1 # Key doesn't exist return '' elsif result.status != 0 raise Overcommit::Exceptions::GitConfigError, "Unable to read from local repo git config: #{result.stderr}" end result.stdout.chomp end def signature_config_key 'overcommit.configuration.signature' end def verify_signature_config_key 'overcommit.configuration.verifysignatures' end end end ================================================ FILE: lib/overcommit/configuration_loader.rb ================================================ # frozen_string_literal: true require 'yaml' module Overcommit # Manages configuration file loading. class ConfigurationLoader DEFAULT_CONFIG_PATH = File.join(Overcommit::HOME, 'config', 'default.yml') class << self # Loads and returns the default configuration. # # @return [Overcommit::Configuration] def default_configuration @default_configuration ||= load_from_file(DEFAULT_CONFIG_PATH, default: true, verify: false) end # Loads configuration from file. # # @param file [String] path to file # @param options [Hash] # @option default [Boolean] whether this is the default built-in configuration # @option verify [Boolean] whether to verify the signature of the configuration # @option logger [Overcommit::Logger] # @return [Overcommit::Configuration] def load_from_file(file, options = {}) # Psych 4 introduced breaking behavior that doesn't support aliases by # default. Explicitly enable aliases if the option is available. yaml = begin YAML.load_file(file, aliases: true) rescue ArgumentError YAML.load_file(file) end hash = yaml ? yaml.to_hash : {} Overcommit::Configuration.new(hash, options) end end # Create a configuration loader which writes warnings/errors to the given # {Overcommit::Logger} instance. # # @param logger [Overcommit::Logger] # @param options [Hash] # @option verify [Boolean] whether to verify signatures def initialize(logger, options = {}) @log = logger @options = options end # Loads and returns the configuration for the repository we're running in. # # @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_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 end end # Loads a configuration, ensuring it extends the default configuration. 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) end config rescue Overcommit::Exceptions::ConfigurationSignatureChanged raise rescue StandardError => e raise Overcommit::Exceptions::ConfigurationError, "Unable to load configuration from '#{file}': #{e}", e.backtrace end private def verify_signatures(config) if !config.previous_signature? raise Overcommit::Exceptions::ConfigurationSignatureChanged, "No previously recorded signature for configuration file.\n" \ 'Run `overcommit --sign` if you trust the hooks in this repository.' elsif config.signature_changed? raise Overcommit::Exceptions::ConfigurationSignatureChanged, "Signature of configuration file has changed!\n" \ "Run `overcommit --sign` once you've verified the configuration changes." end end end end ================================================ FILE: lib/overcommit/configuration_validator.rb ================================================ # frozen_string_literal: true # rubocop:disable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength module Overcommit # Validates and normalizes a configuration. class ConfigurationValidator # Validates hash for any invalid options, normalizing where possible. # # @param config [Overcommit::Configuration] # @param hash [Hash] hash representation of YAML config # @param options[Hash] # @option default [Boolean] whether hash represents the default built-in config # @option logger [Overcommit::Logger] logger to output warnings to # @return [Hash] validated hash (potentially modified) def validate(config, hash, options) @options = options.dup @log = options[:logger] hash = convert_nils_to_empty_hashes(hash) ensure_hook_type_sections_exist(hash) check_hook_name_format(hash) check_hook_env(hash) check_for_missing_enabled_option(hash) unless @options[:default] check_for_too_many_processors(config, hash) check_for_verify_plugin_signatures_option(hash) hash end private # Ensures that keys for all supported hook types exist (PreCommit, # CommitMsg, etc.) def ensure_hook_type_sections_exist(hash) Overcommit::Utils.supported_hook_type_classes.each do |hook_type| hash[hook_type] ||= {} hash[hook_type]['ALL'] ||= {} end end # Normalizes `nil` values to empty hashes. # # This is useful for when we want to merge two configuration hashes # together, since it's easier to merge two hashes than to have to check if # one of the values is nil. def convert_nils_to_empty_hashes(hash) hash.each_with_object({}) do |(key, value), h| h[key] = case value when nil then {} when Hash then convert_nils_to_empty_hashes(value) else value end end end def check_hook_env(hash) errors = [] Overcommit::Utils.supported_hook_type_classes.each do |hook_type| hash.fetch(hook_type) { {} }.each do |hook_name, hook_config| hook_env = hook_config.fetch('env') { {} } unless hook_env.is_a?(Hash) errors << "#{hook_type}::#{hook_name} has an invalid `env` specified: " \ 'must be a hash of environment variable name to string value.' next end hook_env.each do |var_name, var_value| if var_name.include?('=') errors << "#{hook_type}::#{hook_name} has an invalid `env` specified: " \ "variable name `#{var_name}` cannot contain `=`." end unless var_value.nil? || var_value.is_a?(String) errors << "#{hook_type}::#{hook_name} has an invalid `env` specified: " \ "value of `#{var_name}` must be a string or `nil`, but was " \ "#{var_value.inspect} (#{var_value.class})" end end end end if errors.any? if @log @log.error errors.join("\n") @log.newline end raise Overcommit::Exceptions::ConfigurationError, 'One or more hooks had an invalid `env` configuration option' end end # Prints an error message and raises an exception if a hook has an # invalid name, since this can result in strange errors elsewhere. def check_hook_name_format(hash) errors = [] Overcommit::Utils.supported_hook_type_classes.each do |hook_type| hash.fetch(hook_type) { {} }.each_key do |hook_name| next if hook_name == 'ALL' unless hook_name.match?(/\A[A-Za-z0-9]+\z/) errors << "#{hook_type}::#{hook_name} has an invalid name " \ "#{hook_name}. It must contain only alphanumeric " \ 'characters (no underscores or dashes, etc.)' end end end if errors.any? if @log @log.error errors.join("\n") @log.newline end raise Overcommit::Exceptions::ConfigurationError, 'One or more hooks had invalid names' end end # Prints a warning if there are any hooks listed in the configuration # without `enabled` explicitly set. def check_for_missing_enabled_option(hash) return unless @log any_warnings = false Overcommit::Utils.supported_hook_type_classes.each do |hook_type| hash.fetch(hook_type) { {} }.each do |hook_name, hook_config| next if hook_name == 'ALL' if hook_config['enabled'].nil? @log.warning "#{hook_type}::#{hook_name} hook does not explicitly " \ 'set `enabled` option in .overcommit.yml' any_warnings = true end end end @log.newline if any_warnings end # Prints a warning if any hook has a number of processors larger than the # global `concurrency` setting. def check_for_too_many_processors(config, hash) concurrency = config.concurrency errors = [] Overcommit::Utils.supported_hook_type_classes.each do |hook_type| hash.fetch(hook_type) { {} }.each do |hook_name, hook_config| processors = hook_config.fetch('processors') { 1 } if processors > concurrency errors << "#{hook_type}::#{hook_name} `processors` value " \ "(#{processors}) is larger than the global `concurrency` " \ "option (#{concurrency})" end end end if errors.any? if @log @log.error errors.join("\n") @log.newline end raise Overcommit::Exceptions::ConfigurationError, 'One or more hooks had invalid `processor` value configured' end end # Prints a warning if the `verify_plugin_signatures` option is used instead # of the new `verify_signatures` option. def check_for_verify_plugin_signatures_option(hash) return unless @log if hash.key?('verify_plugin_signatures') @log.warning '`verify_plugin_signatures` has been renamed to ' \ '`verify_signatures`. Defaulting to verifying signatures.' @log.warning "See change log at #{REPO_URL}/blob/v0.29.0/CHANGELOG.md for details." @log.newline end end end end # rubocop:enable Metrics/ClassLength, Metrics/CyclomaticComplexity, Metrics/MethodLength ================================================ FILE: lib/overcommit/constants.rb ================================================ # frozen_string_literal: true # Global application constants. 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 REPO_URL = 'https://github.com/sds/overcommit' BUG_REPORT_URL = "#{REPO_URL}/issues" end ================================================ FILE: lib/overcommit/exceptions.rb ================================================ # frozen_string_literal: true module Overcommit::Exceptions # Base error class. class Error < StandardError; end # Raised when a {Configuration} could not be loaded from a file. class ConfigurationError < Error; end # Raised when the Overcommit configuration file signature has changed. class ConfigurationSignatureChanged < Error; end # Raised when trying to read/write to/from the local repo git config fails. class GitConfigError < Error; end # Raised when there was a problem reading submodule information for a repo. class GitSubmoduleError < Error; end # Raised when there was a problem reading git revision information with `rev-list`. class GitRevListError < Error; end # Raised when a {HookContext} is unable to setup the environment before a run. class HookSetupFailed < Error; end # Raised when a {HookContext} is unable to clean the environment after a run. class HookCleanupFailed < Error; end # Raised when a hook run was cancelled by the user. class HookCancelled < Error; end # Raised when a hook could not be loaded by a {HookRunner}. class HookLoadError < Error; end # Raised when a {HookRunner} could not be loaded. class HookContextLoadError < Error; end # Raised when a pipe character is used in the `execute` helper, as this was # likely used in error. class InvalidCommandArgs < Error; end # Raised when an installation target is not a valid git repository. class InvalidGitRepo < Error; end # Raised when a hook was defined incorrectly. class InvalidHookDefinition < Error; end # Raised when one or more hook plugin signatures have changed. class InvalidHookSignature < Error; end # Raised when there is a problem processing output into {Hook::Messages}s. class MessageProcessingError < Error; end # Raised when an installation target already contains non-Overcommit hooks. class PreExistingHooks < Error; end end ================================================ FILE: lib/overcommit/git_config.rb ================================================ # frozen_string_literal: true require 'overcommit/utils' module Overcommit # Get configuration options from git module GitConfig module_function def comment_character char = `git config --get core.commentchar`.chomp char = '#' if char == '' char end def hooks_path path = `git config --get core.hooksPath`.chomp return File.join(Overcommit::Utils.git_dir, 'hooks') if path.empty? File.expand_path(path, Dir.pwd) end end end ================================================ FILE: lib/overcommit/git_repo.rb ================================================ # frozen_string_literal: true require 'iniparse' require 'shellwords' module Overcommit # Provide a set of utilities for certain interactions with `git`. module GitRepo module_function # Regular expression used to extract diff ranges from hunks of diff output. DIFF_HUNK_REGEX = / ^@@\s [^\s]+\s # Ignore old file range \+(\d+)(?:,(\d+))? # Extract range of hunk containing start line and number of lines \s@@.*$ /x.freeze # Regular expression used to extract information from lines of # `git submodule status` output SUBMODULE_STATUS_REGEX = / ^\s*(?[-+U]?)(?\w+) \s(?[^\s]+?) (?:\s\((?.+)\))?$ /x.freeze # Struct encapsulating submodule information extracted from the # output of `git submodule status` SubmoduleStatus = Struct.new(:prefix, :sha1, :path, :describe) do # Returns whether the submodule has not been initialized def uninitialized? prefix == '-' end # Returns whether the submodule is out of date with the current # index, i.e. its checked-out commit differs from that stored in # the index of the parent repo def outdated? prefix == '+' end # Returns whether the submodule reference has a merge conflict def merge_conflict? prefix == 'U' end end # Returns a list of SubmoduleStatus objects, one for each submodule in the # parent repository. # # @option options [Boolean] recursive check submodules recursively # @return [Array] def submodule_statuses(options = {}) flags = '--recursive' if options[:recursive] `git submodule status #{flags}`. scan(SUBMODULE_STATUS_REGEX). map do |prefix, sha1, path, describe| SubmoduleStatus.new(prefix, sha1, path, describe) end end # Extract the set of modified lines from a given file. # # @param file_path [String] # @param options [Hash] # @return [Set] line numbers that have been modified in file def extract_modified_lines(file_path, options) lines = Set.new flags = '--cached' if options[:staged] refs = options[:refs] subcmd = options[:subcmd] || 'diff' `git #{subcmd} --no-color --no-ext-diff -U0 #{flags} #{refs} -- "#{file_path}"`. scan(DIFF_HUNK_REGEX) do |start_line, lines_added| lines_added = (lines_added || 1).to_i # When blank, one line was added cur_line = start_line.to_i lines_added.times do lines.add cur_line cur_line += 1 end end lines end # Returns the names of all files that have been modified compared to HEAD. # # @param options [Hash] # @return [Array] list of absolute file paths def modified_files(options) flags = '--cached' if options[:staged] refs = options[:refs] subcmd = options[:subcmd] || 'diff' `git #{subcmd} --name-only -z --diff-filter=ACMR --ignore-submodules=all #{flags} #{refs}`. split("\0"). map(&:strip). reject(&:empty?). map { |relative_file| File.expand_path(relative_file) } end # Returns the names of files in the given paths that are tracked by git. # # @param paths [Array] list of paths to check # @option options [String] ref ('HEAD') Git ref to check # @return [Array] list of absolute file paths def list_files(paths = [], options = {}) ref = options[:ref] || 'HEAD' result = Overcommit::Utils.execute(%W[git ls-tree --name-only #{ref}], args: paths) unless result.success? raise Overcommit::Exceptions::Error, "Error listing files. EXIT STATUS(es): #{result.statuses}.\n" \ "STDOUT(s): #{result.stdouts}.\n" \ "STDERR(s): #{result.stderrs}." end result.stdout.split(/\n/). map { |relative_file| File.expand_path(relative_file) }. reject { |file| File.directory?(file) } # Exclude submodule directories end # Returns whether the specified file/path is tracked by this repository. # # @param path [String] # @return [true,false] def tracked?(path) Overcommit::Utils.execute(%W[git ls-files #{path} --error-unmatch]).success? end # Returns the names of all files that are tracked by git. # # @return [Array] list of absolute file paths def all_files `git ls-files`. split(/\n/). map { |relative_file| File.expand_path(relative_file) }. reject { |file| File.directory?(file) } # Exclude submodule directories end # Returns whether the current git branch is empty (has no commits). # @return [true,false] def initial_commit? !Overcommit::Utils.execute(%w[git rev-parse HEAD]).success? end # Store any relevant files that are present when repo is in the middle of a # merge. # # Restored via [#restore_merge_state]. def store_merge_state merge_head = `git rev-parse MERGE_HEAD 2> #{File::NULL}`.chomp # Store the merge state if we're in the middle of resolving a merge # conflict. This is necessary since stashing removes the merge state. if merge_head != 'MERGE_HEAD' @merge_head = merge_head end merge_msg_file = File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir) @merge_msg = File.open(merge_msg_file).read if File.exist?(merge_msg_file) end # Store any relevant files that are present when repo is in the middle of a # cherry-pick. # # Restored via [#restore_cherry_pick_state]. def store_cherry_pick_state cherry_head = `git rev-parse CHERRY_PICK_HEAD 2> #{File::NULL}`.chomp # Store the merge state if we're in the middle of resolving a merge # conflict. This is necessary since stashing removes the merge state. if cherry_head != 'CHERRY_PICK_HEAD' @cherry_head = cherry_head end end # Restore any relevant files that were present when repo was in the middle # of a merge. def restore_merge_state if @merge_head FileUtils.touch(File.expand_path('MERGE_MODE', Overcommit::Utils.git_dir)) File.open(File.expand_path('MERGE_HEAD', Overcommit::Utils.git_dir), 'w') do |f| f.write(@merge_head) end @merge_head = nil end if @merge_msg File.open(File.expand_path('MERGE_MSG', Overcommit::Utils.git_dir), 'w') do |f| f.write("#{@merge_msg}\n") end @merge_msg = nil end end # Restore any relevant files that were present when repo was in the middle # of a cherry-pick. def restore_cherry_pick_state if @cherry_head File.open(File.expand_path('CHERRY_PICK_HEAD', Overcommit::Utils.git_dir), 'w') do |f| f.write(@cherry_head) end @cherry_head = nil end end # Contains information about a registered submodule. Submodule = Struct.new(:path, :url) # Returns the submodules that have been staged for removal. # # `git` has an unexpected behavior where removing a submodule without # committing (i.e. such that the submodule directory is removed and the # changes to the index are staged) and then doing a hard reset results in # the index being wiped but the empty directory of the once existent # submodule being restored (but with no content). # # This prevents restoration of the stash of the submodule index changes, # which breaks pre-commit hook restorations of the working index. # # Thus we expose this helper so the restoration code can manually delete the # directory. # # @raise [Overcommit::Exceptions::GitSubmoduleError] when def staged_submodule_removals # There were no submodules before, so none could have been removed return [] if `git ls-files .gitmodules`.empty? previous = submodules(ref: 'HEAD') current = submodules previous - current end # Returns the current set of registered submodules. # # @param options [Hash] # @return [Array] def submodules(options = {}) ref = options[:ref] modules = [] IniParse.parse(`git show #{ref}:.gitmodules 2> #{File::NULL}`).each do |section| # git < 1.8.5 does not update the .gitmodules file with submodule # changes, so when we are looking at the current state of the work tree, # we need to check if the submodule actually exists via another method, # since the .gitmodules file we parsed does not represent reality. if ref.nil? && GIT_VERSION < '1.8.5' result = Overcommit::Utils.execute(%W[ git submodule status #{section['path']} ]) next unless result.success? end modules << Submodule.new(section['path'], section['url']) end modules rescue IniParse::IniParseError => e raise Overcommit::Exceptions::GitSubmoduleError, "Unable to read submodule information from #{ref}:.gitmodules file: #{e.message}" end # Returns the names of all branches containing the given commit. # # @param commit_ref [String] git tree ref that resolves to a commit # @return [Array] list of branches containing the given commit def branches_containing_commit(commit_ref) `git branch --column=dense --contains #{commit_ref}`. sub(/\((HEAD )?detached (from|at) .*?\)/, ''). # ignore detached HEAD split(/\s+/). reject { |s| s.empty? || s == '*' } end # Returns the name of the currently checked out branch. # @return [String] def current_branch `git symbolic-ref --short -q HEAD`.chomp end end end ================================================ FILE: lib/overcommit/git_version.rb ================================================ # frozen_string_literal: true # Returns the version of the available git binary. # # This is intended to be used to conveniently execute code based on a specific # git version. Simply compare to a version string: # # @example # if GIT_VERSION <= '1.8.5' # ... # end module Overcommit GIT_VERSION = begin version = `git --version`.chomp[/\d+(\.\d+)+/, 0] Overcommit::Utils::Version.new(version) end end ================================================ FILE: lib/overcommit/hook/base.rb ================================================ # frozen_string_literal: true require 'forwardable' require 'overcommit/message_processor' # Container for top-level hook-related classes and constants. module Overcommit::Hook # Helper containing metadata about error/warning messages returned by hooks. Message = Struct.new(:type, :file, :line, :content) do def to_s content end end # Possible types of messages. MESSAGE_TYPES = [:error, :warning].freeze # Functionality common to all hooks. class Base # rubocop:disable Metrics/ClassLength extend Forwardable def_delegators :@context, :all_files, :modified_files attr_reader :config # @param config [Overcommit::Configuration] # @param context [Overcommit::HookContext] def initialize(config, context) @config = config.for_hook(self) @context = context end # Runs the hook. def run raise NotImplementedError, 'Hook must define `run`' end # Runs the hook and transforms the status returned based on the hook's # configuration. # # Poorly named because we already have a bunch of hooks in the wild that # implement `#run`, and we needed a wrapper step to transform the status # based on any custom configuration. def run_and_transform if output = check_for_requirements status = :fail else result = Overcommit::Utils.with_environment(@config.fetch('env') { {} }) { run } status, output = process_hook_return_value(result) end [transform_status(status), output] end def name self.class.name.split('::').last end def description @config['description'] || "Run #{name}" end def required? @config['required'] end def parallelize? @config['parallelize'] != false end def processors @config.fetch('processors') { 1 } end def quiet? @config['quiet'] end def enabled? @config['enabled'] != false end def excluded? exclude_branches.any? { |p| File.fnmatch(p, current_branch) } end def skip? @config['skip'] || (@config['skip_if'] ? execute(@config['skip_if']).success? : false) end def run? enabled? && !excluded? && !(@config['requires_files'] && applicable_files.empty?) end def in_path?(cmd) Overcommit::Utils.in_path?(cmd) end # Execute a command in a separate process. # # If `splittable_args` is specified, ensures that those arguments are # concatenated onto the end of the `cmd` arguments, but split up so that the # operating system's maximum command length is not exceeded. This is useful # for splitting up long file lists. # # @param cmd [Array] command arguments # @param options [Hash] # @option options [Array] :args arguments that can be split up over # multiple invocations (usually a list of files) # @option options [String] :input string to pass to process' standard input # stream # @return [#status,#stdout,#stderr] struct containing result of invocation def execute(cmd, options = {}) Overcommit::Utils.execute(cmd, options) end def execute_in_background(cmd) Overcommit::Utils.execute_in_background(cmd) end def required_executable @config['required_executable'] end def required_libraries Array(@config['required_library'] || @config['required_libraries']) end # Return command to execute for this hook. # # This is intended to be configurable so hooks can prefix their commands # with `bundle exec` or similar. It will appends the command line flags # specified by the `flags` option after. # # Note that any files intended to be passed must be handled by the hook # itself. # # @return [Array] def command Array(@config['command'] || required_executable) + flags end # Return command line flags to be passed to the command. # # This excludes the list of files, as that must be handled by the hook # itself. # # The intention here is to provide flexibility for when a tool # removes/renames its flags. Rather than wait for Overcommit to update the # flags it uses, you can update your configuration to use the new flags # right away without being blocked. # # Also note that any flags containing dynamic content must be passed in the # hook's {#run} method. # # @return [Array] def flags Array(@config['flags']) end # Gets a list of staged files that apply to this hook based on its # configured `include` and `exclude` lists. def applicable_files @applicable_files ||= select_applicable(modified_files) end # Gets a list of all files that apply to this hook based on its # configured `include` and `exclude` lists. def included_files @included_files ||= select_applicable(all_files) end private def select_applicable(list) list.select { |file| applicable_file?(file) }.sort end def applicable_file?(file) includes = Array(@config['include']).flatten.map do |glob| Overcommit::Utils.convert_glob_to_absolute(glob) end included = includes.empty? || includes.any? do |glob| Overcommit::Utils.matches_path?(glob, file) end excludes = Array(@config['exclude']).flatten.map do |glob| Overcommit::Utils.convert_glob_to_absolute(glob) end excluded = excludes.any? do |glob| Overcommit::Utils.matches_path?(glob, file) end included && !excluded end # Check for any required executables or libraries. # # Returns output if any requirements are not met. def check_for_requirements check_for_executable || check_for_libraries end # If the hook defines a required executable, check if it's in the path and # display the install command if one exists. def check_for_executable return unless required_executable && !in_path?(required_executable) "'#{required_executable}' is not installed, not in your PATH, " \ "or does not have execute permissions#{install_command_prompt}" end def install_command_prompt if install_command = @config['install_command'] "\nInstall it by running: #{install_command}" else '' end end # If the hook defines required library paths that it wants to load, attempt # to load them. def check_for_libraries output = [] required_libraries.each do |library| 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? output.join("\n") end # Converts the hook's return value into a canonical form of a tuple # containing status (pass/warn/fail) and output. # # This is intended to support various shortcuts for writing hooks so that # hook authors don't need to work with {Overcommit::Hook::Message} objects # for simple pass/fail hooks. It also saves you from needing to manually # encode logic like "if there are errors, fail; if there are warnings, warn, # otherwise pass." by simply returning an array of # {Overcommit::Hook::Message} objects. # # @param hook_return_value [Symbol, Array, Array] # @return [Array] tuple of status and output def process_hook_return_value(hook_return_value) if hook_return_value.is_a?(Array) && (hook_return_value.first.is_a?(Message) || hook_return_value.empty?) # Process messages into a status and output Overcommit::MessageProcessor.new( self, @config['problem_on_unmodified_line'], ).hook_result(hook_return_value) else # Otherwise return as-is hook_return_value end end # Transforms the hook's status based on custom configuration. # # This allows users to change failures into warnings, or vice versa. def transform_status(status) case status when :fail @config.fetch('on_fail') { :fail }.to_sym when :warn @config.fetch('on_warn') { :warn }.to_sym else status end end def exclude_branches @config['exclude_branches'] || [] end def current_branch @current_branch ||= Overcommit::GitRepo.current_branch end end end ================================================ FILE: lib/overcommit/hook/commit_msg/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::CommitMsg # Functionality common to all commit-msg hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :empty_message?, :commit_message, :update_commit_message, :commit_message_lines, :commit_message_file, :modified_lines_in_file end end ================================================ FILE: lib/overcommit/hook/commit_msg/capitalized_subject.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Ensures commit message subject lines start with a capital letter. class CapitalizedSubject < Base def run return :pass if empty_message? # Git treats the first non-empty line as the subject subject = commit_message_lines.find { |line| !line.strip.empty? }.to_s first_letter = subject.match(/^[[:punct:]]*(.)/)[1] unless special_prefix?(subject) || first_letter =~ /[[:upper:]]/ return :warn, 'Subject should start with a capital letter' end :pass end private def special_prefix?(subject) subject =~ /^(fixup|squash)!/ end end end ================================================ FILE: lib/overcommit/hook/commit_msg/empty_message.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Checks that the commit message is not empty class EmptyMessage < Base def run return :pass unless empty_message? [:fail, 'Commit message should not be empty'] end end end ================================================ FILE: lib/overcommit/hook/commit_msg/gerrit_change_id.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Ensures a Gerrit Change-Id line is included in the commit message. # # It may seem odd to do this here instead of in a prepare-commit-msg hook, but # the reality is that if you want to _ensure_ the Change-Id is included then # you need to do it in a commit-msg hook. This is because the user could still # edit the message after a prepare-commit-msg hook was run. # # @see https://code.google.com/p/gerrit/ class GerritChangeId < Base SCRIPT_LOCATION = Overcommit::Utils.script_path('gerrit-change-id') def run result = execute(['sh', SCRIPT_LOCATION, commit_message_file]) return :pass if result.success? [:fail, result.stdout] end end end ================================================ FILE: lib/overcommit/hook/commit_msg/hard_tabs.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Checks for hard tabs in commit messages. class HardTabs < Base def run return :pass if empty_message? # Catches hard tabs entered by the user (not auto-generated) if commit_message.index(/\t/) return :warn, "Don't use hard tabs in commit messages" end :pass end end end ================================================ FILE: lib/overcommit/hook/commit_msg/message_format.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Ensures the commit message follows a specific format. class MessageFormat < Base def run error_msg = validate_pattern(commit_message_lines.join("\n")) return :fail, error_msg if error_msg :pass end private def validate_pattern(message) pattern = config['pattern'] return if pattern.empty? expected_pattern_message = config['expected_pattern_message'] sample_message = config['sample_message'] unless message.match?(/#{pattern}/m) [ 'Commit message pattern mismatch.', "Expected : #{expected_pattern_message}", "Sample : #{sample_message}" ].join("\n") end end end end ================================================ FILE: lib/overcommit/hook/commit_msg/russian_novel.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Checks for long commit messages (not good or bad--just fun to point out) class RussianNovel < Base RUSSIAN_NOVEL_LENGTH = 30 def run if commit_message_lines.length >= RUSSIAN_NOVEL_LENGTH return :warn, 'You seem to have authored a Russian novel; congratulations!' end :pass end end end ================================================ FILE: lib/overcommit/hook/commit_msg/single_line_subject.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Ensures commit message subject lines are followed by a blank line. class SingleLineSubject < Base def run return :pass if empty_message? unless commit_message_lines[1].to_s.strip.empty? return :warn, 'Subject should be one line and followed by a blank line' end :pass end end end ================================================ FILE: lib/overcommit/hook/commit_msg/spell_check.rb ================================================ # frozen_string_literal: true require 'tempfile' module Overcommit::Hook::CommitMsg # Checks the commit message for potential misspellings with `hunspell`. # # @see http://hunspell.sourceforge.net/ class SpellCheck < Base Misspelling = Struct.new(:word, :suggestions) MISSPELLING_REGEX = /^[&#]\s(?\w+)(?:.+?:\s(?.*))?/.freeze def run result = execute(command + [uncommented_commit_msg_file]) return [:fail, "Error running spellcheck: #{result.stderr.chomp}"] unless result.success? misspellings = parse_misspellings(result.stdout) return :pass if misspellings.empty? messages = misspellings.map do |misspelled| msg = "Potential misspelling: #{misspelled.word}." msg += " Suggestions: #{misspelled.suggestions}" unless misspelled.suggestions.nil? msg end [:warn, messages.join("\n")] end private def uncommented_commit_msg_file ::Tempfile.open('commit-msg') do |file| file.write(commit_message) file.path end end def parse_misspellings(output) output.scan(MISSPELLING_REGEX).map do |word, suggestions| Misspelling.new(word, suggestions) end end end end ================================================ FILE: lib/overcommit/hook/commit_msg/text_width.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Ensures the number of columns the subject and commit message lines occupy is # under the preferred limits. class TextWidth < Base def run return :pass if empty_message? @errors = [] find_errors_in_subject(commit_message_lines.first.chomp) find_errors_in_body(commit_message_lines) return :warn, @errors.join("\n") if @errors.any? :pass end private def find_errors_in_subject(subject) max_subject_width = config['max_subject_width'] + special_prefix_length(subject) if subject.length > max_subject_width @errors << "Commit message subject must be <= #{max_subject_width} characters" return end min_subject_width = config['min_subject_width'] if subject.length < min_subject_width @errors << "Commit message subject must be >= #{min_subject_width} characters" nil end end def find_errors_in_body(lines) return unless lines.count > 2 max_body_width = config['max_body_width'] lines[2..].each_with_index do |line, index| if line.chomp.size > max_body_width @errors << "Line #{index + 3} of commit message has > " \ "#{max_body_width} characters" end end end def special_prefix_length(subject) subject.match(/^(fixup|squash)! /) { |match| match[0].length } || 0 end end end ================================================ FILE: lib/overcommit/hook/commit_msg/trailing_period.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::CommitMsg # Ensures commit message subject lines do not have a trailing period class TrailingPeriod < Base def run return :pass if empty_message? if commit_message_lines.first.rstrip.end_with?('.') return :warn, 'Please omit trailing period from commit message subject' end :pass end end end ================================================ FILE: lib/overcommit/hook/post_checkout/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::PostCheckout # Functionality common to all post-checkout hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :previous_head, :new_head, :branch_checkout?, :file_checkout? def skip_file_checkout? @config['skip_file_checkout'] != false end def enabled? return false if file_checkout? && skip_file_checkout? super end end end ================================================ FILE: lib/overcommit/hook/post_checkout/bower_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bower_install' module Overcommit::Hook::PostCheckout # Runs `bower install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BowerInstall class BowerInstall < Base include Overcommit::Hook::Shared::BowerInstall end end ================================================ FILE: lib/overcommit/hook/post_checkout/bundle_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bundle_install' module Overcommit::Hook::PostCheckout # Runs `bundle install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BundleInstall class BundleInstall < Base include Overcommit::Hook::Shared::BundleInstall end end ================================================ FILE: lib/overcommit/hook/post_checkout/composer_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/composer_install' module Overcommit::Hook::PostCheckout # Runs `composer install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::ComposerInstall class ComposerInstall < Base include Overcommit::Hook::Shared::ComposerInstall end end ================================================ FILE: lib/overcommit/hook/post_checkout/index_tags.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/index_tags' module Overcommit::Hook::PostCheckout # Updates ctags index for all source code in the repository. # # @see Overcommit::Hook::Shared::IndexTags class IndexTags < Base include Overcommit::Hook::Shared::IndexTags end end ================================================ FILE: lib/overcommit/hook/post_checkout/npm_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/npm_install' module Overcommit::Hook::PostCheckout # Runs `npm install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::NpmInstall class NpmInstall < Base include Overcommit::Hook::Shared::NpmInstall end end ================================================ FILE: lib/overcommit/hook/post_checkout/submodule_status.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/submodule_status' module Overcommit::Hook::PostCheckout # Checks the status of submodules in the current repository and # notifies the user if any are uninitialized, out of date with # the current index, or contain merge conflicts. class SubmoduleStatus < Base include Overcommit::Hook::Shared::SubmoduleStatus end end ================================================ FILE: lib/overcommit/hook/post_checkout/yarn_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/yarn_install' module Overcommit::Hook::PostCheckout # Runs `yarn install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::YarnInstall class YarnInstall < Base include Overcommit::Hook::Shared::YarnInstall end end ================================================ FILE: lib/overcommit/hook/post_commit/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::PostCommit # Functionality common to all post-commit hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :modified_lines_in_file, :initial_commit? end end ================================================ FILE: lib/overcommit/hook/post_commit/bower_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bower_install' module Overcommit::Hook::PostCommit # Runs `bower install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BowerInstall class BowerInstall < Base include Overcommit::Hook::Shared::BowerInstall end end ================================================ FILE: lib/overcommit/hook/post_commit/bundle_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bundle_install' module Overcommit::Hook::PostCommit # Runs `bundle install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BundleInstall class BundleInstall < Base include Overcommit::Hook::Shared::BundleInstall end end ================================================ FILE: lib/overcommit/hook/post_commit/commitplease.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PostCommit # Check that a commit message conforms to a certain style # # @see https://www.npmjs.com/package/commitplease class Commitplease < Base def run result = execute(command) output = result.stderr return :pass if result.success? && output.empty? [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/post_commit/composer_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/composer_install' module Overcommit::Hook::PostCommit # Runs `composer install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::ComposerInstall class ComposerInstall < Base include Overcommit::Hook::Shared::ComposerInstall end end ================================================ FILE: lib/overcommit/hook/post_commit/git_guilt.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PostCommit # Calculates the change in blame since the last revision. # # @see https://www.npmjs.com/package/git-guilt class GitGuilt < Base PLUS_MINUS_REGEX = /^(.*?)(?:(\++)|(-+))$/.freeze GREEN = 32 RED = 31 def run return :pass if initial_commit? result = execute(command) return :fail, result.stderr unless result.success? return :pass if result.stdout.strip.empty? output = [] result.stdout.scan(PLUS_MINUS_REGEX) do |user, plus, minus| plus = color(GREEN, plus) minus = color(RED, minus) output << "#{user}#{plus}#{minus}" end [:warn, output.join("\n")] end private # Returns text wrapped in ANSI escape code necessary to produce a given # color/text display. # # Taken from Overcommit::Logger as a temporary workaround. # TODO: expose logger instance to hooks for colorized output # # @param code [String] ANSI escape code, e.g. '1;33' for "bold yellow" # @param str [String] string to wrap def color(code, str) STDOUT.tty? ? "\033[#{code}m#{str}\033[0m" : str end end end ================================================ FILE: lib/overcommit/hook/post_commit/index_tags.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/index_tags' module Overcommit::Hook::PostCommit # Updates ctags index for all source code in the repository. # # @see Overcommit::Hook::Shared::IndexTags class IndexTags < Base include Overcommit::Hook::Shared::IndexTags end end ================================================ FILE: lib/overcommit/hook/post_commit/npm_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/npm_install' module Overcommit::Hook::PostCommit # Runs `npm install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::NpmInstall class NpmInstall < Base include Overcommit::Hook::Shared::NpmInstall end end ================================================ FILE: lib/overcommit/hook/post_commit/submodule_status.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/submodule_status' module Overcommit::Hook::PostCommit # Checks the status of submodules in the current repository and # notifies the user if any are uninitialized, out of date with # the current index, or contain merge conflicts. class SubmoduleStatus < Base include Overcommit::Hook::Shared::SubmoduleStatus end end ================================================ FILE: lib/overcommit/hook/post_commit/yarn_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/yarn_install' module Overcommit::Hook::PostCommit # Runs `yarn install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::YarnInstall class YarnInstall < Base include Overcommit::Hook::Shared::YarnInstall end end ================================================ FILE: lib/overcommit/hook/post_merge/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::PostMerge # Functionality common to all post-merge hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :modified_lines_in_file, :squash?, :merge_commit? end end ================================================ FILE: lib/overcommit/hook/post_merge/bower_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bower_install' module Overcommit::Hook::PostMerge # Runs `bower install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BowerInstall class BowerInstall < Base include Overcommit::Hook::Shared::BowerInstall end end ================================================ FILE: lib/overcommit/hook/post_merge/bundle_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bundle_install' module Overcommit::Hook::PostMerge # Runs `bundle install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BundleInstall class BundleInstall < Base include Overcommit::Hook::Shared::BundleInstall end end ================================================ FILE: lib/overcommit/hook/post_merge/composer_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/composer_install' module Overcommit::Hook::PostMerge # Runs `composer install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::ComposerInstall class ComposerInstall < Base include Overcommit::Hook::Shared::ComposerInstall end end ================================================ FILE: lib/overcommit/hook/post_merge/index_tags.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/index_tags' module Overcommit::Hook::PostMerge # Updates ctags index for all source code in the repository. # # @see Overcommit::Hook::Shared::IndexTags class IndexTags < Base include Overcommit::Hook::Shared::IndexTags end end ================================================ FILE: lib/overcommit/hook/post_merge/npm_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/npm_install' module Overcommit::Hook::PostMerge # Runs `npm install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::NpmInstall class NpmInstall < Base include Overcommit::Hook::Shared::NpmInstall end end ================================================ FILE: lib/overcommit/hook/post_merge/submodule_status.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/submodule_status' module Overcommit::Hook::PostMerge # Checks the status of submodules in the current repository and # notifies the user if any are uninitialized, out of date with # the current index, or contain merge conflicts. class SubmoduleStatus < Base include Overcommit::Hook::Shared::SubmoduleStatus end end ================================================ FILE: lib/overcommit/hook/post_merge/yarn_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/yarn_install' module Overcommit::Hook::PostMerge # Runs `yarn install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::YarnInstall class YarnInstall < Base include Overcommit::Hook::Shared::YarnInstall end end ================================================ FILE: lib/overcommit/hook/post_rewrite/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::PostRewrite # Functionality common to all post-rewrite hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :amend?, :rebase?, :rewritten_commits end end ================================================ FILE: lib/overcommit/hook/post_rewrite/bower_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bower_install' module Overcommit::Hook::PostRewrite # Runs `bower install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BowerInstall class BowerInstall < Base include Overcommit::Hook::Shared::BowerInstall end end ================================================ FILE: lib/overcommit/hook/post_rewrite/bundle_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/bundle_install' module Overcommit::Hook::PostRewrite # Runs `bundle install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::BundleInstall class BundleInstall < Base include Overcommit::Hook::Shared::BundleInstall end end ================================================ FILE: lib/overcommit/hook/post_rewrite/composer_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/composer_install' module Overcommit::Hook::PostRewrite # Runs `composer install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::ComposerInstall class ComposerInstall < Base include Overcommit::Hook::Shared::ComposerInstall end end ================================================ FILE: lib/overcommit/hook/post_rewrite/index_tags.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/index_tags' module Overcommit::Hook::PostRewrite # Updates ctags index for all source code in the repository. # # @see Overcommit::Hook::Shared::IndexTags class IndexTags < Base include Overcommit::Hook::Shared::IndexTags def run # Ignore unless this is a rebase (amends are covered by post-commit hook) return :pass unless rebase? super end end end ================================================ FILE: lib/overcommit/hook/post_rewrite/npm_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/npm_install' module Overcommit::Hook::PostRewrite # Runs `npm install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::NpmInstall class NpmInstall < Base include Overcommit::Hook::Shared::NpmInstall end end ================================================ FILE: lib/overcommit/hook/post_rewrite/submodule_status.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/submodule_status' module Overcommit::Hook::PostRewrite # Checks the status of submodules in the current repository and # notifies the user if any are uninitialized, out of date with # the current index, or contain merge conflicts. class SubmoduleStatus < Base include Overcommit::Hook::Shared::SubmoduleStatus end end ================================================ FILE: lib/overcommit/hook/post_rewrite/yarn_install.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/yarn_install' module Overcommit::Hook::PostRewrite # Runs `yarn install` when a change is detected in the repository's # dependencies. # # @see Overcommit::Hook::Shared::YarnInstall class YarnInstall < Base include Overcommit::Hook::Shared::YarnInstall end end ================================================ FILE: lib/overcommit/hook/pre_commit/author_email.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks the format of an author's email address. class AuthorEmail < Base def run email = if ENV.key?('GIT_AUTHOR_EMAIL') ENV['GIT_AUTHOR_EMAIL'] else result = execute(%w[git config --get user.email]) result.stdout.chomp end unless email.match?(/#{config['pattern']}/) return :fail, "Author has an invalid email address: '#{email}'\n" \ 'Set your email with ' \ '`git config --global user.email your_email@example.com` ' \ 'or via the GIT_AUTHOR_EMAIL environment variable' end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/author_name.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Ensures that a commit author has a name with at least first and last names. class AuthorName < Base def run name = if ENV.key?('GIT_AUTHOR_NAME') ENV['GIT_AUTHOR_NAME'] else result = execute(%w[git config --get user.name]) result.stdout.chomp end if name.empty? return :fail, "Author name must be non-0 in length.\n" \ 'Set your name with `git config --global user.name "Your Name"` ' \ 'or via the GIT_AUTHOR_NAME environment variable' end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/base.rb ================================================ # frozen_string_literal: true require 'forwardable' require 'overcommit/utils/messages_utils' module Overcommit::Hook::PreCommit # Functionality common to all pre-commit hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :modified_lines_in_file, :amendment?, :initial_commit? private def extract_messages(*args) Overcommit::Utils::MessagesUtils.extract_messages(*args) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/berksfile_check.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check if local Berksfile.lock matches Berksfile when either changes, unless # Berksfile.lock is ignored by git. # # @see http://berkshelf.com/ class BerksfileCheck < Base LOCK_FILE = 'Berksfile.lock' def run # Ignore if Berksfile.lock is not tracked by git ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n") return :pass if ignored_files.include?(LOCK_FILE) result = execute(command) unless result.success? return :fail, result.stderr end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/broken_symlinks.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for broken symlinks. class BrokenSymlinks < Base def run broken_symlinks = applicable_files. select { |file| Overcommit::Utils.broken_symlink?(file) } if broken_symlinks.any? return :fail, "Broken symlinks detected:\n#{broken_symlinks.join("\n")}" end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/bundle_audit.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for vulnerable versions of gems in Gemfile.lock. # # @see https://github.com/rubysec/bundler-audit class BundleAudit < Base LOCK_FILE = 'Gemfile.lock' def run # Ignore if Gemfile.lock is not tracked by git ignored_files = execute(%W[git ls-files -o -i --exclude-standard -- #{LOCK_FILE}]). stdout.split("\n") return :pass if ignored_files.include?(LOCK_FILE) result = execute(command) if result.success? :pass else [:warn, result.stdout] end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/bundle_check.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check if local Gemfile.lock matches Gemfile when either changes, unless # Gemfile.lock is ignored by git. # # @see http://bundler.io/ class BundleCheck < Base LOCK_FILE = File.basename(ENV['BUNDLE_GEMFILE'] || 'Gemfile') + '.lock' def run # Ignore if Gemfile.lock is not tracked by git ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n") return :pass if ignored_files.include?(LOCK_FILE) previous_lockfile = File.read(LOCK_FILE) if File.exist?(LOCK_FILE) result = execute(command) unless result.success? return :fail, result.stdout end new_lockfile = File.read(LOCK_FILE) if File.exist?(LOCK_FILE) if previous_lockfile != new_lockfile return :fail, "#{LOCK_FILE} is not up-to-date -- run \ `#{command.join(' ')}` or add the Gemfile and/or Gemfile.lock".squeeze end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/bundle_outdated.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check if any gems in Gemfile.lock have newer versions, unless the # Gemfile.lock is ignored by Git. # # @see http://bundler.io/bundle_outdated.html class BundleOutdated < Base LOCK_FILE = 'Gemfile.lock' def run # Ignore if Gemfile.lock is not tracked by git ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n") return :pass if ignored_files.include?(LOCK_FILE) result = execute(command) warn_msgs = result.stdout.split("\n"). reject { |str| str.strip.empty? }. reject { |str| (str.strip =~ /^(\[|\()?warning|deprecation/i) } warnings = warn_msgs.map { |msg| Overcommit::Hook::Message.new(:warning, nil, nil, msg) } warnings.empty? ? :pass : warnings end end end ================================================ FILE: lib/overcommit/hook/pre_commit/case_conflicts.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for files that would conflict in case-insensitive filesystems # Adapted from https://github.com/pre-commit/pre-commit-hooks class CaseConflicts < Base def run repo_files = Set.new(applicable_files) unless Overcommit::GitRepo.initial_commit? paths = repo_files.map { |file| File.dirname(file) + File::SEPARATOR }.uniq repo_files += Overcommit::GitRepo.list_files(paths) end conflict_hash = repo_files.classify(&:downcase). select { |_, files| files.size > 1 } conflict_files = applicable_files. select { |file| conflict_hash.include?(file.downcase) } conflict_files.map do |file| conflicts = conflict_hash[file.downcase].map { |f| File.basename(f) } msg = "Conflict detected for case-insensitive file systems: #{conflicts.join(', ')}" Overcommit::Hook::Message.new(:error, file, nil, msg) end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/chamber_compare.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `chamber compare` against a configurable set of namespaces. # # @see https://github.com/thekompanee/chamber/wiki/Git-Commit-Hooks#chamber-compare-pre-commit-hook # rubocop:disable Metrics/MethodLength class ChamberCompare < Base def run config['namespaces'].each_index do |index| first = config['namespaces'][index] second = config['namespaces'][index + 1] next unless second result = execute( command, args: [ "--first=#{first.join(' ')}", "--second=#{second.join(' ')}", ], ) unless result.stdout.empty? trimmed_result = result.stdout.split("\n") 5.times { trimmed_result.shift } trimmed_result = trimmed_result.join("\n") return [ :warn, "It appears your namespace settings between #{first} and " \ "#{second} are not in sync:\n\n#{trimmed_result}\n\n" \ "Run: chamber compare --first=#{first.join(' ')} " \ "--second=#{second.join(' ')}", ] end end :pass end end # rubocop:enable Metrics/MethodLength end ================================================ FILE: lib/overcommit/hook/pre_commit/chamber_security.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `chamber secure` against any modified Chamber settings files. # # @see https://github.com/thekompanee/chamber class ChamberSecurity < Base def run result = execute(command, args: applicable_files) return :pass if result.stdout.empty? [:fail, "These settings appear to need to be secured but were not: #{result.stdout}"] end end end ================================================ FILE: lib/overcommit/hook/pre_commit/chamber_verification.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `chamber sign --verify`. # # @see https://github.com/thekompanee/chamber/wiki/Git-Commit-Hooks#chamber-verification-pre-commit-hook # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity class ChamberVerification < Base def run approver_name = config.fetch('approver_name') { 'your approver' } approver_email = config['approver_email'] ? " (#{config['approver_email']})" : nil result = execute(command) return :pass if result.stdout.empty? && result.stderr.empty? return :pass if result.stderr =~ /no signature key was found/ output = [ result.stdout.empty? ? nil : result.stdout, result.stderr.empty? ? nil : result.stderr, ]. compact. join("\n\n") output = "\n\n#{output}" unless output.empty? [ :warn, "One or more of your settings files does not match the signature.\n" \ "Talk to #{approver_name}#{approver_email} about getting them " \ "approved.#{output}", ] end end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity end ================================================ FILE: lib/overcommit/hook/pre_commit/code_spell_check.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `alfonsox` spell-checking tool against any modified code file. # # @see https://github.com/diegojromerolopez/alfonsox class CodeSpellCheck < Base def run # Create default file config if it does not exist # Run spell-check result = execute(command, args: applicable_files) return :pass if result.success? spellchecking_errors = result.stderr.split("\n") spellchecking_errors.pop error_messages(spellchecking_errors) end private # Create the error messages def error_messages(spellchecking_errors) messages = [] spellchecking_errors.each do |spellchecking_error_i| error_location, word = spellchecking_error_i.split(' ') error_file_path, line = error_location.split(':') messages << Overcommit::Hook::Message.new( :error, error_file_path, line, "#{error_location}: #{word}" ) end messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/coffee_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `coffeelint` against any modified CoffeeScript files. # # @see http://www.coffeelint.org/ class CoffeeLint < Base MESSAGE_REGEX = / ^(?.+) ,(?\d*),\d* ,(?\w+) ,(?.+)$ /x.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('w') ? :warning : :error end def run result = execute(command, args: applicable_files) parse_messages(result.stdout) end private def parse_messages(output) output.scan(MESSAGE_REGEX).map do |file, line, type, msg| line = line.to_i type = MESSAGE_TYPE_CATEGORIZER.call(type) text = "#{file}:#{line}:#{type} #{msg}" Overcommit::Hook::Message.new(type, file, line, text) end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/cook_style.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `cookstyle` against any modified Chef Ruby files. # # @see https://docs.chef.io/cookstyle.html class CookStyle < Base GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.match?(/^warn/) ? :warning : :error end COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('W') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? generic_messages = extract_messages( result.stderr.split("\n"), /^(?[a-z]+)/i, GENERIC_MESSAGE_TYPE_CATEGORIZER, ) cop_messages = extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+):[^ ]+ (?[^ ]+)/, COP_MESSAGE_TYPE_CATEGORIZER, ) generic_messages + cop_messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/credo.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `credo` against any modified ex files. # # @see https://github.com/rrrene/credo class Credo < Base # example message: # lib/file1.ex:1:11: R: Modules should have a @moduledoc tag. # lib/file2.ex:12:81: R: Line is too long (max is 80, was 81). def run result = execute(command, args: applicable_files) return :pass if result.success? result.stdout.split("\n").map(&:strip).reject(&:empty?). map { |error| message(error) } end private def message(error) file, line = error.split(':') Overcommit::Hook::Message.new(:error, file, Integer(line), error) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/css_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `csslint` against any modified CSS files. # # @see https://github.com/CSSLint/csslint class CssLint < Base MESSAGE_REGEX = / ^(?(?:\w:)?[^:]+):\s (?:line\s(?\d+)[^EW]+)? (?Error|Warning) /x.freeze def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? extract_messages( output.split("\n").reject(&:empty?), MESSAGE_REGEX, lambda { |type| type.downcase.to_sym } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/dart_analyzer.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `dartanalyzer` against modified Dart files. # @see https://dart.dev/tools/dartanalyzer class DartAnalyzer < Base MESSAGE_REGEX = /(?.*)•\ (?[^•]+)•\ (?[^:]+):(?\d+):(\d+)\.*/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, lambda do |type| type.include?('error') ? :error : :warning end ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/dogma.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `dogma` against any modified ex files. # # @see https://github.com/lpil/dogma class Dogma < Base def run result = execute command return :pass if result.success? messages = [] # example message: # == web/channels/user_socket.ex == # 26: LineLength: Line length should not exceed 80 chars (was 83). # 1: ModuleDoc: Module Sample.UserSocket is missing a @moduledoc. output = result.stdout.chomp.match(/(==.+)/m) if output output.captures.first.split(/\n\n/).each do |error_group| errors = error_group.split /\n/ file = errors.shift.gsub /[ =]/, '' errors.each do |error| line = error.split(': ').first messages << Overcommit::Hook::Message.new(:error, file, line, "#{file}: #{error}") end end end messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/erb_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `erblint` against any modified ERB files. # # @see https://github.com/Shopify/erb-lint class ErbLint < Base MESSAGE_REGEX = /(?.+)\nIn file: (?.+):(?\d+)/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n\n")[1..], MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/es_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `eslint` against any modified JavaScript files. # # Protip: if you have an npm script set up to run eslint, you can configure # this hook to run eslint via your npm script by using the `command` option in # your .overcommit.yml file. This can be useful if you have some eslint # configuration built into your npm script that you don't want to repeat # somewhere else. Example: # # EsLint: # required_executable: 'npm' # enabled: true # command: ['npm', 'run', 'lint', '--', '-f', 'compact'] # # Note: This hook supports only compact format. # # @see http://eslint.org/ class EsLint < Base def run eslint_regex = /^(?[^\s](?:\w:)?[^:]+):[^\d]+(?\d+).*?(?Error|Warning)/ result = execute(command, args: applicable_files) output = result.stdout.chomp messages = output.split("\n").grep(eslint_regex) return [:fail, result.stderr] if messages.empty? && !result.success? return :pass if result.success? && output.empty? # example message: # path/to/file.js: line 1, col 0, Error - Error message (ruleName) extract_messages(messages, eslint_regex, lambda { |type| type.downcase.to_sym }) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/execute_permissions.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for files with execute permissions, which are usually not necessary # in source code files (and are typically caused by a misconfigured editor # assigning incorrect default permissions). # # Protip: if you have some files that you want to allow execute permissions # on, you can disable this hook for those files by using the `exclude` option # on your .overcommit.yml file. Example: # # ExecutePermissions: # enabled: true # exclude: # - 'path/to/my/file/that/should/have/execute/permissions.sh' # - 'directory/that/should/have/execute/permissions/**/*' class ExecutePermissions < Base def run file_modes = {} # We have to look in two places to determine the execute permissions of a # file. The first is the Git tree for currently known file modes of all # files, the second is the index for any staged changes to file modes. # Staged changes take priority if they exist. # # This complexity is necessary because this hook can be run in the RunAll # context, where there may be no staged changes but we stil want to check # the permissions. extract_from_git_tree(file_modes) unless initial_commit? extract_from_git_index(file_modes) file_modes.map do |file, mode| next unless execute_permissions?(mode) Overcommit::Hook::Message.new( :error, file, nil, "File #{file} has unnecessary execute permissions", ) end.compact end private def extract_from_git_tree(file_modes) result = execute(%w[git ls-tree HEAD --], args: applicable_files) raise 'Unable to access git tree' unless result.success? result.stdout.split("\n").each do |line| mode, _type, _hash, file = line.split(/\s+/, 4) file_modes[file] = mode end end def extract_from_git_index(file_modes) result = execute(%w[git diff --raw --cached --no-color --], args: applicable_files) raise 'Unable to access git index' unless result.success? result.stdout.split("\n").each do |line| _old_mode, new_mode, _old_hash, _new_hash, _status, file = line.split(/\s+/, 6) file_modes[file] = new_mode end end # Check if the 1st bit is toggled, indicating execute permissions. # # Git tracks only execute permissions, not individual read/write/execute # permissions for user, group, and other, since that concept does not exist # on all operating systems. If any of the user/group/other permissions # have the executable bit set, they all will. Thus we check the first bit. def execute_permissions?(mode) (mode.to_i(8) & 1) == 1 end end end ================================================ FILE: lib/overcommit/hook/pre_commit/fasterer.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `fasterer` against any modified Ruby files. # # @see https://github.com/DamirSvrtan/fasterer class Fasterer < Base def run result = execute(command, args: applicable_files) output = result.stdout if extract_offense_num(output) == 0 :pass else [:warn, output] end end private def extract_offense_num(raw_output) raw_output.scan(/(\d+) offense detected/).flatten.map(&:to_i).inject(0, :+) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/file_size.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for oversized files before committing. class FileSize < Base def run return :pass if oversized_files.empty? oversized_files.map do |file| error_message_for(file) end end def description "Check for files over #{size_limit_bytes} bytes" end private def oversized_files @oversized_files ||= build_oversized_file_list end def build_oversized_file_list applicable_files.select do |file| File.exist?(file) && file_size(file) > size_limit_bytes end end def size_limit_bytes config.fetch('size_limit_bytes') end def error_message_for(file) Overcommit::Hook::Message.new( :error, file, nil, "#{file} is #{file_size(file)} bytes" ) end def file_size(file) File.size(file) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/fix_me.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check for "token" strings class FixMe < Base def run keywords = config['keywords'] result = execute(command, args: [keywords.join('|')] + applicable_files) extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/, lambda { |_type| :warning } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/flay.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `flay` against any modified files. # # @see https://github.com/seattlerb/flay class Flay < Base # Flay prints two kinds of messages: # # 1) IDENTICAL code found in :defn (mass*2 = MASS) # file_path_1.rb:LINE_1 # file_path_2.rb:LINE_2 # # 2) Similar code found in :defn (mass = MASS) # file_path_1.rb:LINE_1 # file_path_2.rb:LINE_2 # def run command = ['flay', '--mass', @config['mass_threshold'].to_s, '--fuzzy', @config['fuzzy'].to_s] # Use a more liberal detection method command += ['--liberal'] if @config['liberal'] messages = [] # Run the command for each file applicable_files.each do |file| result = execute(command, args: [file]) results = result.stdout.split("\n\n") results.shift unless results.empty? error_message = results.join("\n").gsub(/^\d+\)\s*/, '') message = Overcommit::Hook::Message.new(:error, nil, nil, error_message) messages << message end end messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/foodcritic.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `foodcritic` against any modified Ruby files from Chef directory structure. # # @see http://www.foodcritic.io/ # # There are two "modes" you can run this hook in based on the repo: # # SINGLE COOKBOOK REPO MODE # ------------------------- # The default. Use this if your repository contains just a single cookbook, # i.e. the top-level repo directory contains directories called `attributes`, # `libraries`, `recipes`, etc. # # To get this to work well, you'll want to set your Overcommit configuration # for this hook to something like: # # PreCommit: # Foodcritic: # enabled: true # include: # - 'attributes/**/*' # - 'definitions/**/*' # - 'files/**/*' # - 'libraries/**/*' # - 'providers/**/*' # - 'recipes/**/*' # - 'resources/**/*' # - 'templates/**/*' # # MONOLITHIC REPO MODE # -------------------- # Use this if you store multiple cookbooks, environments, and roles (or any # combination thereof) in a single repository. # # There are three configuration options relevant here: # # * `cookbooks_directory` # When set, hook will treat the path as a directory containing cookbooks. # Each subdirectory of this directory will be treated as a separate # cookbook. # # * `environments_directory` # When set, hook will treat the path as a directory containing environment # files. # # * `roles_directory` # When set, hook will treat the given path as a directory containing role # files. # # In order to run in monolithic repo mode, YOU MUST SET `cookbooks_directory`. # The other configuration options are optional, if you happen to store # environments/roles in another repo. # # To get this to work well, you'll want to set your Overcommit configuration # for this hook to something like: # # PreCommit: # Foodcritic: # enabled: true # cookbooks_directory: 'cookbooks' # environments_directory: 'environments' # roles_directory: 'roles' # include: # - 'cookbooks/**/*' # - 'environments/**/*' # - 'roles/**/*' # # ADDITIONAL CONFIGURATION # ------------------------ # You can disable rules using the `flags` hook option. For example: # # PreCommit: # Foodcritic: # enabled: true # ... # flags: # - '--epic-fail=any' # - '-t~FC011' # Missing README in markdown format # - '-t~FC064' # Ensure issues_url is set in metadata # # Any other command line flag supported by the `foodcritic` executable can be # specified here. # # If you want the hook run to fail (and not just warn), set the `on_warn` # option for the hook to `fail`: # # PreCommit: # Foodcritic: # enabled: true # on_warn: fail # ... # # This will treat any warnings as failures and cause the hook to exit # unsuccessfully. class Foodcritic < Base def run args = modified_cookbooks_args + modified_environments_args + modified_roles_args result = execute(command, args: args) if result.success? :pass else [:warn, result.stderr + result.stdout] end end private def directories_changed(dir_prefix) applicable_files. select { |path| path.start_with?(dir_prefix) }. map { |path| path.gsub(%r{^#{dir_prefix}/}, '') }. group_by { |path| path.split('/').first }. keys. map { |path| File.join(dir_prefix, path) } end def modified_environments_args modified('environments').map { |env| %W[-E #{env}] }.flatten end def modified_roles_args modified('roles').map { |role| %W[-R #{role}] }.flatten end def modified_cookbooks_args # Return the repo root if repository contains a single cookbook if !config['cookbooks_directory'] || config['cookbooks_directory'].empty? ['-B', Overcommit::Utils.repo_root] else # Otherwise return all modified cookbooks in the cookbook directory modified('cookbooks').map { |cookbook| ['-B', cookbook] }.flatten end end def modified(type) return [] if !config["#{type}_directory"] || config["#{type}_directory"].empty? @modified ||= {} @modified[type] ||= directories_changed(full_directory_path("#{type}_directory")) end def full_directory_path(config_option) return config[config_option] if config[config_option].start_with?(File::SEPARATOR) File.absolute_path(File.join(Overcommit::Utils.repo_root, config[config_option])) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/forbidden_branches.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Prevents commits to branches matching one of the configured patterns. class ForbiddenBranches < Base def run return :pass unless forbidden_commit? [:fail, "Committing to #{current_branch} is forbidden"] end private def forbidden_commit? forbidden_branch_patterns.any? { |p| File.fnmatch(p, current_branch) } end def forbidden_branch_patterns @forbidden_branch_patterns ||= Array(config['branch_patterns']) end def current_branch @current_branch ||= Overcommit::GitRepo.current_branch end end end ================================================ FILE: lib/overcommit/hook/pre_commit/ginkgo_focus.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check for "focused" tests class GinkgoFocus < Base def run keywords = config['keywords'] result = execute(command, args: [keywords.join('|')] + applicable_files) extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/, lambda { |_type| :warning } ) end def applicable_test_files applicable_files.select do |f| f if f =~ /_test\.go/ end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/go_fmt.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs go fmt for all modified Go files class GoFmt < Base def run errors = [] applicable_files.each do |file| result = execute(command, args: [file]) errors << (result.stdout + result.stderr) unless result.success? end return :pass if errors.empty? [:fail, errors.join("\n")] end end end ================================================ FILE: lib/overcommit/hook/pre_commit/go_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `golint` against any modified Golang files. # # @see https://github.com/golang/lint class GoLint < Base def run output = '' # golint doesn't accept multiple file arguments if # they belong to different packages applicable_files.each do |gofile| result = execute(command, args: Array(gofile)) output += result.stdout + result.stderr end # Unfortunately the exit code is always 0 return :pass if output.empty? # example message: # path/to/file.go:1:1: Error message extract_messages( output.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/ ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/go_vet.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `go vet` against any modified Golang files. # # @see https://godoc.org/code.google.com/p/go-zh.tools/cmd/vet class GoVet < Base def run result = execute(command, args: applicable_files) return :pass if result.success? if result.stderr.match?(/no such tool "vet"/) return :fail, "`go tool vet` is not installed#{install_command_prompt}" end # example message: # path/to/file.go:7: Error message extract_messages( result.stderr.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/ ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/golangci_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `golangci-lint run` against any modified packages # # @see https://github.com/golangci/golangci-lint class GolangciLint < Base def run packages = applicable_files.map { |f| File.dirname(f) }.uniq result = execute(command, args: packages) return :pass if result.success? return [:fail, result.stderr] unless result.stderr.empty? extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/, nil ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/hadolint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `hadolint` against any modified Dockefile files. # # @see http://hadolint.lukasmartinelli.ch/ class Hadolint < Base def run output = '' success = true # hadolint doesn't accept multiple arguments applicable_files.each do |dockerfile| result = execute(command, args: Array(dockerfile)) output += result.stdout success &&= result.success? end return :pass if success extract_messages( output.split("\n"), /^(?[^:]+):(?\d+)/, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/haml_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `haml-lint` against any modified HAML files. # # @see https://github.com/brigade/haml-lint/ class HamlLint < Base MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('W') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)[^ ]* (?[^ ]+)/, MESSAGE_TYPE_CATEGORIZER, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/hard_tabs.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for hard tabs in files. class HardTabs < Base def run result = execute(command, args: applicable_files) extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/hlint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `hlint` against any modified Haskell files. # # @see https://github.com/ndmitchell/hlint class Hlint < Base MESSAGE_REGEX = / ^(?(?:\w:)?[^:]+) :(?\d+) :\d+ :\s*(?\w+) /x.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('W') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? raw_messages = result.stdout.split("\n").grep(MESSAGE_REGEX) # example message: # path/to/file.hs:1:0: Error: message extract_messages( raw_messages, MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/html_hint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `htmlhint` against any modified HTML files. # # @see http://htmlhint.com/ class HtmlHint < Base def run result = execute(command + applicable_files) output = Overcommit::Utils.strip_color_codes(result.stdout.chomp) message_groups = output.split("\n\n")[0..-2] message_groups.map do |group| lines = group.split("\n").map(&:strip) file = lines[0][/(.+):/, 1] extract_messages( lines[1..].map { |msg| "#{file}: #{msg}" }, /^(?(?:\w:)?[^:]+): line (?\d+)/ ) end.flatten end end end ================================================ FILE: lib/overcommit/hook/pre_commit/html_tidy.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `tidy` against any modified HTML files. # # @see http://www.html-tidy.org/ class HtmlTidy < Base MESSAGE_REGEX = / ^(?(?:\w:)?[^:]+):\s line\s(?\d+)\s column\s(?\d+)\s-\s (?Error|Warning):\s(?.+)$ /x.freeze def run # example message: # line 4 column 24 - Warning: proprietary attribute "class" applicable_files.collect do |file| result = execute(command + [file]) output = result.stderr.chomp extract_messages( output.split("\n").collect { |msg| "#{file}: #{msg}" }, MESSAGE_REGEX, lambda { |type| type.downcase.to_sym } ) end.flatten end end end ================================================ FILE: lib/overcommit/hook/pre_commit/image_optim.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for images that can be optimized with `image_optim`. # # @see https://github.com/toy/image_optim class ImageOptim < Base def run result = execute(command, args: applicable_files) return [:fail, result.stdout + result.stderr] unless result.success? optimized_files = extract_optimized_files(result.stdout) return :pass if optimized_files.empty? output = "The following images are optimizable:\n#{optimized_files.join("\n")}" output += "\n\nOptimize them by running `#{command.join(' ')} #{optimized_files.join(' ')}`" [:fail, output] end private def extract_optimized_files(output) output.split("\n"). select { |line| line =~ /^\d+/ }. map { |line| line.split(/\s+/).last } end end end ================================================ FILE: lib/overcommit/hook/pre_commit/java_checkstyle.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `checkstyle` against any modified Java files. # # @see http://checkstyle.sourceforge.net/ class JavaCheckstyle < Base MESSAGE_REGEX = /^(\[(?[^\]]+)\]\s+)?(?(?:\w:)?[^:]+):(?\d+)/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| %w[WARN INFO].include?(type.to_s) ? :warning : :error end def run result = execute(command, args: applicable_files) output = result.stdout.chomp # example message: # path/to/file.java:3:5: Error message extract_messages( output.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/js_hint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `jshint` against any modified JavaScript files. # # @see http://jshint.com/ class JsHint < Base def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? # example message: # path/to/file.js: line 1, col 0, Error message (E001) extract_messages( output.split("\n").grep(/E|W/), /^(?(?:\w:)?[^:]+):[^\d]+(?\d+).+\((?E|W)\d+\)/, lambda { |type| type.include?('W') ? :warning : :error } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/js_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `jslint` against any modified JavaScript files. # # @see http://www.jslint.com/ class JsLint < Base MESSAGE_REGEX = /(?(?:\w:)?[^:]+):(?\d+)/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? # example message: # path/to/file.js:1:1: Error message extract_messages( result.stdout.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/jscs.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `jscs` (JavaScript Code Style Checker) against any modified JavaScript # files. # # @see http://jscs.info/ class Jscs < Base def run result = execute(command, args: applicable_files) return :pass if result.success? # Exit status 2 = Code style errors; everything else we don't know how to # parse. https://github.com/jscs-dev/node-jscs/wiki/Exit-codes unless result.status == 2 return :fail, result.stdout + result.stderr.chomp end # example message: # path/to/file.js: line 7, col 0, ruleName: Error message extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):[^\d]+(?\d+)/, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/jsl.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `jsl` against any modified JavaScript files. # # @see http://www.javascriptlint.com/ class Jsl < Base MESSAGE_REGEX = /(?(?:\w:)?.+)\((?\d+)\):(?[^:]+)/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.match?(/warning/) ? :warning : :error end def run file_flags = applicable_files.map { |file| ['-process', file] } result = execute(command + file_flags.flatten) return :pass if result.success? # example message: # path/to/file.js(1): lint warning: Error message extract_messages( result.stdout.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/json_syntax.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks the syntax of any modified JSON files. class JsonSyntax < Base def run messages = [] applicable_files.each do |file| 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 end end end ================================================ FILE: lib/overcommit/hook/pre_commit/kt_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `ktlint` against modified Kotlin files. # @see https://github.com/shyiko/ktlint class KtLint < Base MESSAGE_REGEX = /((?[^:]+):(?\d+):(\d+):(?.+))/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/license_finder.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs LicenseFinder if any of your package manager declaration files have changed # See more about LicenseFinder at https://github.com/pivotal/LicenseFinder class LicenseFinder < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_commit/license_header.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for license headers in source files class LicenseHeader < Base def run begin license_contents = license_lines rescue Errno::ENOENT return :fail, "Unable to load license file #{license_file}" end messages = applicable_files.map do |file| check_file(file, license_contents) end.compact return :fail, messages.join("\n") if messages.any? :pass end def check_file(file, license_contents) File.readlines(file).each_with_index do |l, i| if i >= license_contents.length break end l.chomp! unless l.end_with?(license_contents[i]) message = "#{file} missing header contents from line #{i} of "\ "#{license_file}: #{license_contents[i]}" return message end end end def license_file config['license_file'] end def license_lines @license_lines ||= begin file_root = Overcommit::Utils.convert_glob_to_absolute(license_file) File.read(file_root).split("\n") end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/line_endings.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for line endings in files. # # WARNING: Works with Git 2.10.0 or newer. class LineEndings < Base def run messages = [] offending_files.map do |file_name| file = File.open(file_name) begin messages += check_file(file, file_name) rescue ArgumentError => e # File is likely a binary file which this check should ignore, but # print a warning just in case messages << Overcommit::Hook::Message.new( :warning, file_name, file.lineno, "#{file_name}:#{file.lineno}:#{e.message}" ) end end messages end private def check_file(file, file_name) messages_for_file = [] file.each_line do |line| # Remove configured line-ending line.gsub!(/#{config['eol']}/, '') # Detect any left over line-ending characters next unless line.end_with?("\n", "\r") messages_for_file << Overcommit::Hook::Message.new( :error, file_name, file.lineno, "#{file_name}:#{file.lineno}:#{line.inspect}" ) end messages_for_file end def offending_files result = execute(%w[git ls-files --eol -z --], args: applicable_files) raise 'Unable to access git tree' unless result.success? result.stdout.split("\0").map do |file_info| info, path = file_info.split("\t") i = info.split.first next if i == 'l/-text' # ignore binary files next if i == "l/#{eol}" path end.compact end def eol @eol ||= case config['eol'] when "\n" 'lf' when "\r\n" 'crlf' else raise 'Invalid `eol` option specified: must be "\n" or "\r\n"' end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/local_paths_in_gemfile.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for local paths in files and issues a warning class LocalPathsInGemfile < Base def run result = execute(command, args: applicable_files) unless result.stdout.empty? return :warn, "Avoid pointing to local paths in Gemfiles:\n#{result.stdout}" end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/mdl.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `mdl` against any modified Markdown files # # @see https://github.com/mivok/markdownlint class Mdl < Base def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? return [:fail, result.stderr] unless result.stderr.empty? # example message: # [{"filename":"file1.md","line":1,"rule":"MD013","aliases":["line-length"], # "description":"Line length"}] json_messages = JSON.parse(output) json_messages.map do |message| Overcommit::Hook::Message.new( :error, message['filename'], message['line'], "#{message['filename']}:#{message['line']} #{message['rule']} #{message['description']}" ) end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/merge_conflicts.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for unresolved merge conflicts class MergeConflicts < Base def run result = execute(command, args: applicable_files) unless result.stdout.empty? return :fail, "Merge conflict markers detected:\n#{result.stdout}" end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_commit/mix_format.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `mix format --check-formatted` against any modified ex/heex/exs files. # # @see https://hexdocs.pm/mix/main/Mix.Tasks.Format.html class MixFormat < Base # example message: # ** (Mix) mix format failed due to --check-formatted. # The following files are not formatted: # # * lib/file1.ex # * lib/file2.ex FILES_REGEX = /^\s+\*\s+(?.+)$/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? result.stderr.scan(FILES_REGEX).flatten. map { |file| message(file) } end private def message(file) Overcommit::Hook::Message.new(:error, file, nil, file) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/nginx_test.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `nginx -t` against any modified Nginx config files. # # @see https://www.nginx.com/resources/wiki/start/topics/tutorials/commandline/ class NginxTest < Base MESSAGE_REGEX = /^nginx: .+ in (?.+):(?\d+)$/.freeze def run messages = [] applicable_files.each do |file| result = execute(command + ['-c', file]) next if result.success? messages += extract_messages( result.stderr.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX ) end messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pep257.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `pep257` against any modified Python files. # # @see https://pypi.python.org/pypi/pep257 class Pep257 < Base def run result = execute(command, args: applicable_files) return :pass if result.success? output = result.stderr.chomp # example message: # path/to/file.py:1 in public method `foo`: # D102: Docstring missing extract_messages( output.gsub(/:\s+/, ': ').split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/ ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pep8.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `pep8` against any modified Python files. # # @see https://pypi.python.org/pypi/pep8 class Pep8 < Base def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? # example message: # path/to/file.py:88:5: E301 expected 1 blank line, found 0 extract_messages( output.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+):\d+:\s(?E|W)/, lambda { |type| type.include?('W') ? :warning : :error } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/php_cs.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `phpcs` against any modified PHP files. class PhpCs < Base # Parse `phpcs` csv mode output MESSAGE_REGEX = /^\"(?.+)\",(?\d+),\d+,(?.+),\"(?.+)\"/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| 'error'.include?(type) ? :error : :warning end def run messages = [] result = execute(command, args: applicable_files) if result.status messages = result.stdout.split("\n") # Discard the csv header messages.shift end return :fail if messages.empty? && !result.success? return :pass if messages.empty? parse_messages(messages) end # Transform the CSV output into a tidy human readable message def parse_messages(messages) output = [] messages.map do |message| message.scan(MESSAGE_REGEX).map do |file, line, type, msg| type = MESSAGE_TYPE_CATEGORIZER.call(type) text = " #{file}:#{line}\n #{msg}" output << Overcommit::Hook::Message.new(type, file, line.to_i, text) end end output end end end ================================================ FILE: lib/overcommit/hook/pre_commit/php_cs_fixer.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `php-cs-fixer` against any modified PHP files. class PhpCsFixer < Base MESSAGE_REGEX = /\s+\d+\)\s+(?.*\.php)(?\s+\(\w+(?:,\s+)?\))?/.freeze def run messages = [] feedback = '' # Exit status for all of the runs. Should be zero! exit_status_sum = 0 applicable_files.each do |file| result = execute(command, args: [file]) output = result.stdout.chomp exit_status_sum += result.status if result.status messages = output.lstrip.split("\n") end end unless messages.empty? feedback = parse_messages(messages) end :pass if exit_status_sum == 0 :pass if feedback.empty? feedback end def parse_messages(messages) output = [] messages.map do |message| message.scan(MESSAGE_REGEX).map do |file, violated_rules| type = :error unless violated_rules.nil? type = :warning end text = if type == :error "Cannot process #{file}: Syntax error" else "#{file} has been fixed" end output << Overcommit::Hook::Message.new(type, file, 0, text) end end output end end end ================================================ FILE: lib/overcommit/hook/pre_commit/php_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `php -l` against any modified PHP files. class PhpLint < Base # Sample String # rubocop:disable Layout/LineLength # PHP Parse error: syntax error, unexpected 'require_once' (T_REQUIRE_ONCE) in site/sumo.php on line 12 # rubocop:enable Layout/LineLength MESSAGE_REGEX = /^(?.+)\:\s+(?.+) in (?.+) on line (?\d+)/.freeze def run # A list of error messages messages = [] # Exit status for all of the runs. Should be zero! exit_status_sum = 0 # Run for each of our applicable files applicable_files.each do |file| result = execute(command, args: [file]) output = result.stdout.chomp exit_status_sum += result.status if result.status # `php -l` returns with a leading newline, and we only need the first # line, there is usually some redundancy messages << output.lstrip.split("\n").first end end # If the sum of all lint status is zero, then none had exit status return :pass if exit_status_sum == 0 # No messages is great news for us return :pass if messages.empty? # Return the list of message objects extract_messages( messages, MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/php_stan.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `phpstan` against any modified PHP files. # For running `phpstan` with Laravel, it requires setup with `ide_helper`. # # References: # https://github.com/phpstan/phpstan/issues/239 # https://gist.github.com/edmondscommerce/89695c9cd2584fefdf540fb1c528d2c2 class PhpStan < Base MESSAGE_REGEX = /^(?.+)\:(?\d+)\:(?.+)/.freeze def run messages = [] result = execute(command, args: applicable_files) unless result.success? messages += result.stdout.lstrip.split("\n") end return :pass if messages.empty? extract_messages( messages, MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pronto.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/pronto' module Overcommit::Hook::PreCommit # Runs `pronto` # # @see https://github.com/mmozuras/pronto class Pronto < Base include Overcommit::Hook::Shared::Pronto end end ================================================ FILE: lib/overcommit/hook/pre_commit/puppet_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs 'puppet-lint' against any modified Puppet files. # # @see http://puppet-lint.com/ class PuppetLint < Base MESSAGE_REGEX = /(?(?:\w:)?.+):(?\d+):\d+:(?\w+)/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type == 'ERROR' ? :error : :warning end def run result = execute(command, args: applicable_files) output = result.stdout.chomp.gsub(/^"|"$/, '') return :pass if result.success? && output.empty? extract_messages( output.split("\n"), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/puppet_metadata_json_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # # Run's the Puppet metadata linter. It has support for adding options # in the .overcommit.yaml # # @see https://voxpupuli.org/blog/2014/11/06/linting-metadata-json/ # class PuppetMetadataJsonLint < Base MESSAGE_REGEX = /\((?.*)\).*/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type == 'WARN' ? :warning : :error end def run result = execute(command, args: applicable_files) output = result.stdout.chomp.gsub(/^"|"$/, '') return :pass if result.success? && output.empty? extract_messages( output.split("\n"), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pycodestyle.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `pycodestyle` against any modified Python files. # # @see https://pypi.python.org/pypi/pycodestyle class Pycodestyle < Base def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? # example message: # path/to/file.py:88:5: E301 expected 1 blank line, found 0 extract_messages( output.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+):\d+:\s(?E|W)/, lambda { |type| type.include?('W') ? :warning : :error } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pydocstyle.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `pydocstyle` against any modified Python files. # # @see https://pypi.python.org/pypi/pydocstyle class Pydocstyle < Base def run result = execute(command, args: applicable_files) return :pass if result.success? output = result.stderr.chomp # example message: # path/to/file.py:1 in public method `foo`: # D102: Docstring missing extract_messages( output.gsub(/:\s+/, ': ').split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/ ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pyflakes.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `pyflakes` against any modified Python files. # # @see https://pypi.python.org/pypi/pyflakes class Pyflakes < Base MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+):/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? errors = get_messages(result.stderr, :error) warnings = get_messages(result.stdout, :warning) errors + warnings end private def get_messages(output, type) # example message: # path/to/file.py:57: local variable 'x' is assigned to but never used extract_messages( output.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, proc { type } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/pylint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `pylint` against any modified Python files. # # @see http://www.pylint.org/ class Pylint < Base MESSAGE_REGEX = /^(?(?:\w:)?.+):(?\d+):(?[CEFRW])/.freeze # Classify 'E' and 'F' message codes as errors, # everything else as warnings. # http://pylint.readthedocs.org/en/latest/tutorial.html#getting-started MESSAGE_TYPE_CATEGORIZER = lambda do |type| 'EF'.include?(type) ? :error : :warning end def run result = execute(command, args: applicable_files) return :pass if result.success? output = result.stdout.chomp # example message: # path/to/file.py:64:C: Missing function docstring (missing-docstring) extract_messages( output.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/python_flake8.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `flake8` against any modified Python files. # # @see https://pypi.python.org/pypi/flake8 class PythonFlake8 < Base MESSAGE_REGEX = /^(?(?:\w:)?.+):(?\d+):\d+:\s(?\w\d+)/.freeze # Classify 'Exxx' and 'Fxxx' message codes as errors, # everything else as warnings. # http://flake8.readthedocs.org/en/latest/warnings.html MESSAGE_TYPE_CATEGORIZER = lambda do |type| 'EF'.include?(type[0]) ? :error : :warning end def run result = execute(command, args: applicable_files) return :pass if result.success? output = result.stdout.chomp # example message: # path/to/file.py:2:13: F812 list comprehension redefines name from line 1 extract_messages( output.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/r_spec.rb ================================================ # 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 ================================================ FILE: lib/overcommit/hook/pre_commit/rails_best_practices.rb ================================================ # frozen_string_literal: true module Overcommit module Hook module PreCommit # Runs `rails_best_practices` against Ruby files # # @see https://github.com/railsbp/rails_best_practices class RailsBestPractices < Base ERROR_REGEXP = /^(?(?:\w:)?[^:]+):(?\d+)\s-\s(?.+)/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? return [:fail, result.stderr] unless result.stderr.empty? extract_messages( filter_output(result.stdout), ERROR_REGEXP ) end private def filter_output(stdout) stdout.split("\n").select do |message| message.match ERROR_REGEXP end end end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/rails_schema_up_to_date.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check to see whether the schema file is in line with the migrations. When a # schema file is present but a migration file is not, this is usually a # failure. The exception is if the schema is at version 0 (i.e before any # migrations have been run). In this case it is OK if there are no migrations. class RailsSchemaUpToDate < Base def run # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity if migration_files.any? && schema_files.none? return :fail, "It looks like you're adding a migration, but did not update the schema file" elsif migration_files.none? && schema_files.any? && non_zero_schema_version? return :fail, "You're trying to change the schema without adding a migration file" elsif migration_files.any? && schema_files.any? # Get the latest version from the migration filename. Use # `File.basename` to prevent finding numbers that could appear in # directories, such as the home directory of a user with a number in # their username. latest_version = migration_files.map do |file| File.basename(file)[/\d+/] end.max up_to_date = schema.include?(latest_version) unless up_to_date return :fail, "The latest migration version you're committing is " \ "#{latest_version}, but your schema file " \ "#{schema_files.join(' or ')} is on a different version." end end :pass end 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} end end def schema_files @schema_files ||= applicable_files.select do |file| file.match %r{db/schema\.rb|db/structure.*\.sql} end end def schema @schema ||= schema_files.map { |file| File.read(file, **(encoding || {})) }.join @schema.tr('_', '') end def non_zero_schema_version? schema =~ /\d{14}/ end end end ================================================ FILE: lib/overcommit/hook/pre_commit/rake_target.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/rake_target' module Overcommit::Hook::PreCommit # Runs rake targets # # @see Overcommit::Hook::Shared::RakeTarget class RakeTarget < Base include Overcommit::Hook::Shared::RakeTarget end end ================================================ FILE: lib/overcommit/hook/pre_commit/reek.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `reek` against any modified Ruby files. # # @see https://github.com/troessner/reek class Reek < Base def run result = execute(command, args: applicable_files) return :pass if result.success? output = scrub_output(result.stdout + result.stderr) extract_messages( output, /^\s*(?(?:\w:)?[^:]+):(?\d+):/, ) end private def scrub_output(raw_output) raw_output.split("\n").grep(/^(.(?!warning))*$/) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/rst_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `rst-lint` against any modified reStructuredText files # # @see https://github.com/twolfson/restructuredtext-lint class RstLint < Base MESSAGE_REGEX = / ^(?INFO|WARNING|ERROR|SEVERE)(?(?:\w:)?[^:]+):(?\d+)\s(?.+) /x.freeze def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? return [:fail, result.stderr] unless result.stderr.empty? # example message: # WARNING README.rst:7 Title underline too short. extract_messages( output.split("\n"), MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/rubo_cop.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `rubocop` against any modified Ruby files. # # @see http://batsov.com/rubocop/ class RuboCop < Base GENERIC_MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.match?(/^warn/) ? :warning : :error end COP_MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('W') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? generic_messages = extract_messages( result.stderr.split("\n"), /^(?[a-z]+)/i, GENERIC_MESSAGE_TYPE_CATEGORIZER, ) cop_messages = extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+):[^ ]+ (?[^ ]+)/, COP_MESSAGE_TYPE_CATEGORIZER, ) generic_messages + cop_messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/ruby_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `ruby-lint` against any modified Ruby files. # # @see https://github.com/YorickPeterse/ruby-lint class RubyLint < Base MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('W') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?[^:]+):(?\d+)/, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/ruby_syntax.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `ruby -c` against all Ruby files. # class RubySyntax < Base MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.match?(/^(syntax)?\s*error/) ? :error : :warning end def run result = execute(command, args: applicable_files) result_lines = result.stderr.split("\n") return :pass if result_lines.length.zero? # Example message: # path/to/file.rb:1: syntax error, unexpected '^' extract_messages( result_lines, /^(?[^:]+):(?\d+):\s*(?[^,]+),\s*(?.+)/, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/scalariform.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `scalariform` against any modified Scala files. # # @see https://github.com/mdr/scalariform class Scalariform < Base MESSAGE_REGEX = /^\[(?FAILED|ERROR)\]\s+(?(?:\w:)?.+)/.freeze def run result = execute(command, args: applicable_files) # example message: # [FAILED] path/to/file.scala extract_messages( result.stdout.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX, lambda { |type| type == 'ERROR' ? :error : :warning } ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/scalastyle.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `scalastyle` against any modified Scala files. # # @see http://www.scalastyle.org/ class Scalastyle < Base MESSAGE_REGEX = / ^(?error|warning)\s file=(?(?:\w:)?.+)\s message=.+\s* (line=(?\d+))? /x.freeze def run result = execute(command, args: applicable_files) output = result.stdout.chomp + result.stderr.chomp messages = output.split("\n").grep(MESSAGE_REGEX) return [:fail, output] unless result.success? || messages.any? # example message: # error file=/path/to/file.scala message=Error message line=1 column=1 extract_messages( messages, MESSAGE_REGEX, lambda(&:to_sym) ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/scss_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `scss-lint` against any modified SCSS files. # # @see https://github.com/sds/scss-lint class ScssLint < Base def run result = execute(command, args: applicable_files) # Status code 81 indicates the applicable files were all filtered by # exclusions defined by the configuration. In this case, we're happy to # return success since there were technically no lints. return :pass if [0, 81].include?(result.status) # Any status that isn't indicating lint warnings or errors indicates failure return :fail, (result.stdout + result.stderr) unless [1, 2].include?(result.status) begin collect_lint_messages(JSON.parse(result.stdout)) rescue JSON::ParserError => e [:fail, "Unable to parse JSON returned by SCSS-Lint: #{e.message}\n" \ "STDOUT: #{result.stdout}\nSTDERR: #{result.stderr}"] end end private def collect_lint_messages(files_to_lints) files_to_lints.flat_map do |path, lints| lints.map do |lint| severity = lint['severity'] == 'warning' ? :warning : :error message = lint['reason'] message = "#{lint['linter']}: #{message}" if lint['linter'] message = "#{path}:#{lint['line']} #{message}" Overcommit::Hook::Message.new(severity, path, lint['line'], message) end end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/semi_standard.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `semistandard` against any modified JavaScript files. # # @see https://github.com/Flet/semistandard class SemiStandard < Base MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+)/.freeze def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? # example message: # path/to/file.js:1:1: Error message (ruleName) extract_messages( output.split("\n").grep(MESSAGE_REGEX), # ignore header line MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/shell_check.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `shellcheck` against any modified shell script files. # # @see http://www.shellcheck.net/ class ShellCheck < Base MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('note') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+):[^ ]+ (?[^ ]+)/, MESSAGE_TYPE_CATEGORIZER, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/slim_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `slim-lint` against any modified Slim templates. # # @see https://github.com/sds/slim-lint class SlimLint < Base MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('W') ? :warning : :error end def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)[^ ]* (?[^ ]+)/, MESSAGE_TYPE_CATEGORIZER, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/solargraph.rb ================================================ # frozen_string_literal: true require 'overcommit' require 'overcommit/hook/pre_commit/base' module Overcommit module Hook module PreCommit # Runs `solargraph typecheck` against any modified Ruby files. # # @see https://github.com/castwide/solargraph class Solargraph < Base MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+) - /.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? stderr_lines = remove_harmless_glitches(result.stderr) violation_lines = result.stdout.split("\n").grep(MESSAGE_REGEX) if violation_lines.empty? if stderr_lines.empty? [:fail, 'Solargraph failed to run'] else # let's feed it stderr so users see the errors extract_messages(stderr_lines, MESSAGE_REGEX) end else extract_messages(violation_lines, MESSAGE_REGEX) end end private # @param stderr [String] # # @return [Array] def remove_harmless_glitches(stderr) stderr.split("\n").reject do |line| line.include?('[WARN]') || line.include?('warning: parser/current is loading') || line.include?('Please see https://github.com/whitequark') end end end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/sorbet.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs 'srb tc' against any modified files. # # @see https://github.com/sorbet/sorbet class Sorbet < Base # example of output: # sorbet.rb:1: Method `foo` does not exist on `T.class_of(Bar)` https://srb.help/7003 MESSAGE_REGEX = /^(?[^:]+):(?\d+): (?.*)$/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? output = result.stderr.split("\n").grep(MESSAGE_REGEX) extract_messages( output, MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/sqlint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs 'sqlint' against any modified SQL files. # # @see https://github.com/purcell/sqlint class Sqlint < Base MESSAGE_REGEX = /(?(?:\w:)?.+):(?\d+):\d+:(?\w+)/.freeze MESSAGE_TYPE_CATEGORIZER = lambda do |type| type == 'ERROR' ? :error : :warning end def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? extract_messages( output.split("\n"), MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/standard.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `standard` against any modified JavaScript files. # # @see https://github.com/feross/standard class Standard < Base MESSAGE_REGEX = /^\s*(?(?:\w:)?[^:]+):(?\d+)/.freeze def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? # example message: # path/to/file.js:1:1: Error message (ruleName) extract_messages( output.split("\n").grep(MESSAGE_REGEX), # ignore header line MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/stylelint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `stylelint` against any modified CSS file. # # @see https://github.com/stylelint/stylelint class Stylelint < Base # example of output: # index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation) MESSAGE_REGEX = /^(?[^:]+):\D*(?\d+).*$/.freeze def run result = execute(command, args: applicable_files) output = result.stdout + result.stderr.chomp return :pass if result.success? && output.empty? extract_messages( output.split("\n"), MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/swift_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `swiftlint lint` against modified Swift files. # @see https://github.com/realm/SwiftLint class SwiftLint < Base MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+)[^ ]* (?[^ ]+):(?.*)/.freeze def run result = execute(command, args: applicable_files) return :pass if result.success? extract_messages( result.stdout.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/terraform_format.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs 'terraform fmt' against any modified *.tf files. # # @see https://www.terraform.io/docs/commands/fmt.html class TerraformFormat < Base def run messages = [] applicable_files.each do |f| result = execute(command, args: [f]) unless result.success? messages << Overcommit::Hook::Message.new(:error, f, nil, "violation found in #{f}") end end messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/trailing_whitespace.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks for trailing whitespace in files. class TrailingWhitespace < Base def run result = execute(command, args: applicable_files) extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/, ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/travis_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `travis-lint` against any modified Travis CI files. # # @see https://github.com/travis-ci/travis.rb class TravisLint < Base def run result = execute(command, args: applicable_files) return :pass if result.success? [:fail, (result.stdout + result.stderr).strip] end end end ================================================ FILE: lib/overcommit/hook/pre_commit/ts_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `tslint` against modified TypeScript files. # @see http://palantir.github.io/tslint/ class TsLint < Base # example message: # "src/file/anotherfile.ts[298, 1]: exceeds maximum line length of 140" # or # "ERROR: src/AccountController.ts[4, 28]: expected call-signature to have a typedef" MESSAGE_REGEX = /^(?.+: )?(?.+?(?=\[))[^\d]+(?\d+).*?/.freeze def run result = execute(command, args: applicable_files) output = result.stdout.chomp return :pass if result.success? && output.empty? output_lines = output.split("\n").map(&:strip).reject(&:empty?) type_categorizer = ->(type) { type.nil? || type.include?('ERROR') ? :error : :warning } extract_messages( output_lines, MESSAGE_REGEX, type_categorizer ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/vint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `vint` against any modified Vim script files. # # @see https://github.com/Kuniwak/vint class Vint < Base def run result = execute(command, args: applicable_files) return :pass if result.success? return [:fail, result.stderr] unless result.stderr.empty? # example message: # path/to/file.vim:1:1: Error message extract_messages( result.stdout.split("\n"), /^(?(?:\w:)?[^:]+):(?\d+)/ ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/w3c_css.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `w3c_validators` against any modified CSS files. # # @see https://github.com/alexdunae/w3c_validators class W3cCss < Base def run collect_messages rescue W3CValidators::ParsingError, W3CValidators::ValidatorUnavailable => e [:fail, e.message] end private def collect_messages applicable_files.collect do |path| results = validator.validate_file(path) messages = results.errors + results.warnings messages.collect do |msg| # Some warnings are not per-line, so use 0 as a default line = Integer(msg.line || 0) # Build message by hand to reduce noise from the validator response text = "#{msg.type.to_s.upcase}; URI: #{path}; line #{line}: #{msg.message.strip}" Overcommit::Hook::Message.new(msg.type, path, line, text) end end.flatten end def validator unless @validator @validator = W3CValidators::CSSValidator.new(opts) @validator.set_language!(language) unless language.nil? @validator.set_profile!(profile) unless profile.nil? @validator.set_warn_level!(warn_level) unless warn_level.nil? end @validator end def opts @opts ||= { validator_uri: config['validator_uri'], proxy_server: config['proxy_server'], proxy_port: config['proxy_port'], proxy_user: config['proxy_user'], proxy_pass: config['proxy_pass'] } end def language @language ||= config['language'] end # Values specified at # http://www.rubydoc.info/gems/w3c_validators/1.2/W3CValidators#CSS_PROFILES def profile @profile ||= config['profile'] end # One of 0, 1, 2, 'no' def warn_level @warn_level ||= config['warn_level'] end end end ================================================ FILE: lib/overcommit/hook/pre_commit/w3c_html.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `w3c_validators` against any modified HTML files. # # @see https://github.com/alexdunae/w3c_validators class W3cHtml < Base def run collect_messages rescue W3CValidators::ParsingError, W3CValidators::ValidatorUnavailable => e [:fail, e.message] end private def collect_messages applicable_files.collect do |path| results = validator.validate_file(path) messages = results.errors + results.warnings messages.collect do |msg| # Some warnings are not per-line, so use 0 as a default line = Integer(msg.line || 0) # Build message by hand to reduce noise from the validator response text = "#{msg.type.to_s.upcase}; URI: #{path}; line #{line}: #{msg.message.strip}" Overcommit::Hook::Message.new(msg.type, path, line, text) end end.flatten end def validator unless @validator @validator = W3CValidators::MarkupValidator.new(opts) @validator.set_charset!(charset, true) unless charset.nil? @validator.set_doctype!(doctype, true) unless doctype.nil? @validator.set_debug! end @validator end def opts @opts ||= { validator_uri: config['validator_uri'], proxy_server: config['proxy_server'], proxy_port: config['proxy_port'], proxy_user: config['proxy_user'], proxy_pass: config['proxy_pass'] } end # Values specified at # http://www.rubydoc.info/gems/w3c_validators/1.2/W3CValidators#CHARSETS def charset @charset ||= config['charset'] end # Values specified at # http://www.rubydoc.info/gems/w3c_validators/1.2/W3CValidators#DOCTYPES def doctype @doctype ||= config['doctype'] end end end ================================================ FILE: lib/overcommit/hook/pre_commit/xml_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `xmllint` against any modified XML files. # # @see http://xmlsoft.org/xmllint.html class XmlLint < Base MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+):/.freeze def run result = execute(command, args: applicable_files) output = result.stderr.chomp return :pass if result.success? && output.empty? # example message: # path/to/file.xml:1: parser error : Error message extract_messages( output.split("\n").grep(MESSAGE_REGEX), MESSAGE_REGEX ) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/xml_syntax.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks the syntax of any modified XML files. class XmlSyntax < Base def run messages = [] applicable_files.each do |file| 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 end end end ================================================ FILE: lib/overcommit/hook/pre_commit/yaml_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Runs `YAMLLint` against any modified YAML files. # # @see https://github.com/adrienverge/yamllint class YamlLint < Base MESSAGE_REGEX = / ^(?.+) :(?\d+) :(?\d+) :\s\[(?\w+)\] \s(?.+)$ /x.freeze def run result = execute(command, args: applicable_files) parse_messages(result.stdout) end private def parse_messages(output) repo_root = Overcommit::Utils.repo_root output.scan(MESSAGE_REGEX).map do |file, line, col, type, msg| line = line.to_i type = type.to_sym # Obtain the path relative to the root of the repository # for nicer output: relpath = file.dup relpath.slice!("#{repo_root}/") text = "#{relpath}:#{line}:#{col}:#{type} #{msg}" Overcommit::Hook::Message.new(type, file, line, text) end end end end ================================================ FILE: lib/overcommit/hook/pre_commit/yaml_syntax.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Checks the syntax of any modified YAML files. class YamlSyntax < Base def run messages = [] applicable_files.each do |file| YAML.load_file(file, aliases: true) rescue ArgumentError begin YAML.load_file(file) rescue ArgumentError, Psych::SyntaxError => e messages << Overcommit::Hook::Message.new(:error, file, nil, e.message) end rescue Psych::DisallowedClass => e messages << error_message(file, e) end messages end private def error_message(file, error) text = "#{file}: #{error.message}" Overcommit::Hook::Message.new(:error, file, nil, text) end end end ================================================ FILE: lib/overcommit/hook/pre_commit/yard_coverage.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Class to check yard documentation coverage. # # Use option "min_coverage_percentage" in your YardCoverage configuration # to set your desired documentation coverage percentage. # class YardCoverage < Base def run # Run a no-stats yard command to get the coverage args = flags + applicable_files result = execute(command, args: args) warnings_and_stats_text, undocumented_objects_text = result.stdout.split('Undocumented Objects:') warnings_and_stats = warnings_and_stats_text.strip.split("\n") # Stats are the last 7 lines before the undocumented objects stats = warnings_and_stats.slice(-7, 7) # If no stats present (shouldn't happen), warn the user and end if stats.class != Array || stats.length != 7 return [:warn, 'Impossible to read the yard stats. Please, check your yard installation.'] end # Check the yard coverage yard_coverage = check_yard_coverage(stats) if yard_coverage == :warn return [ :warn, 'Impossible to read yard doc coverage. Please, check your yard installation.' ] end return :pass if yard_coverage == :pass error_messages(yard_coverage, undocumented_objects_text) end private # Check the yard coverage # # Return a :pass if the coverage is enough, :warn if it couldn't be read, # otherwise, it has been read successfully. # def check_yard_coverage(stat_lines) if config['min_coverage_percentage'] match = stat_lines.last.match(/^\s*([\d.]+)%\s+documented\s*$/) unless match return :warn end yard_coverage = match.captures[0].to_f if yard_coverage >= config['min_coverage_percentage'].to_f return :pass end yard_coverage end end # Create the error messages def error_messages(yard_coverage, error_text) first_message = "You have a #{yard_coverage}% yard documentation coverage. "\ "#{config['min_coverage_percentage']}% is the minimum required." # Add the undocumented objects text as error messages messages = [Overcommit::Hook::Message.new(:error, nil, nil, first_message)] errors = error_text.strip.split("\n") errors.each do |undocumented_object| undocumented_object_message, file_info = undocumented_object.split(/:?\s+/) file_info_match = file_info.match(/^\(([^:]+):(\d+)\)/) # In case any compacted error does not follow the format, ignore it if file_info_match file = file_info_match.captures[0] line = file_info_match.captures[1] messages << Overcommit::Hook::Message.new( :error, file, line, "#{file}:#{line}: #{undocumented_object_message}" ) end end messages end end end ================================================ FILE: lib/overcommit/hook/pre_commit/yarn_check.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreCommit # Check if local yarn.lock matches package.json when either changes, unless # yarn.lock is ignored by git. # # @see https://yarnpkg.com/en/docs/cli/check class YarnCheck < Base LOCK_FILE = 'yarn.lock' # A lot of the errors returned by `yarn check` are outside the developer's control # (are caused by bad package specification, in the hands of the upstream maintainer) # So limit reporting to errors the developer can do something about ACTIONABLE_ERRORS = [ 'Lockfile does not contain pattern', ].freeze def run # Ignore if yarn.lock is not tracked by git ignored_files = execute(%w[git ls-files -o -i --exclude-standard]).stdout.split("\n") return :pass if ignored_files.include?(LOCK_FILE) previous_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil result = execute(command) new_lockfile = File.exist?(LOCK_FILE) ? File.read(LOCK_FILE) : nil # `yarn check` also throws many warnings, which should be ignored here errors_regex = Regexp.new("^error (.*)(#{ACTIONABLE_ERRORS.join('|')})(.*)$") errors = errors_regex.match(result.stderr) unless errors.nil? && previous_lockfile == new_lockfile return :fail, "#{LOCK_FILE} is not up-to-date -- run `yarn install`" end :pass end end end ================================================ FILE: lib/overcommit/hook/pre_push/base.rb ================================================ # frozen_string_literal: true require 'forwardable' require 'overcommit/utils/messages_utils' module Overcommit::Hook::PrePush # Functionality common to all pre-push hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :remote_name, :remote_url, :pushed_refs def run? super && !exclude_remotes.include?(remote_name) && (include_remote_ref_deletions? || !@context.remote_ref_deletion?) end private def extract_messages(*args) Overcommit::Utils::MessagesUtils.extract_messages(*args) end def exclude_remotes @config['exclude_remotes'] || [] end def include_remote_ref_deletions? @config['include_remote_ref_deletions'] end end end ================================================ FILE: lib/overcommit/hook/pre_push/brakeman.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `brakeman` whenever Ruby/Rails files change. # # @see http://brakemanscanner.org/ class Brakeman < Base def run result = execute(command) return :pass if result.success? [:fail, result.stdout] end end end ================================================ FILE: lib/overcommit/hook/pre_push/cargo_test.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `cargo test` before push if Rust files changed class CargoTest < Base def run result = execute(command) return :pass if result.success? [:fail, result.stdout] end end end ================================================ FILE: lib/overcommit/hook/pre_push/flutter_test.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs Flutter test suite (`flutter test`) before push # # @see https://api.flutter.dev/flutter/flutter_test/flutter_test-library.html class FlutterTest < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/go_test.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `go test ./...` command on prepush class GoTest < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/golangci_lint.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs golangci-lint # # @see https://github.com/golangci/golangci-lint class GolangciLint < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/minitest.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `minitest` test suite before push # # @see https://github.com/seattlerb/minitest class Minitest < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end def command super + included_files.map { |file| "-r#{file}" } end end end ================================================ FILE: lib/overcommit/hook/pre_push/mix_test.rb ================================================ # 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 ================================================ FILE: lib/overcommit/hook/pre_push/php_unit.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `phpunit` test suite before push # # @see https://phpunit.de/ class PhpUnit < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/pronto.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/pronto' module Overcommit::Hook::PrePush # Runs `pronto` # # @see https://github.com/mmozuras/pronto class Pronto < Base include Overcommit::Hook::Shared::Pronto end end ================================================ FILE: lib/overcommit/hook/pre_push/protected_branches.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Prevents updates to specified branches. # Accepts a 'destructive_only' option globally or per branch # to only prevent destructive updates. class ProtectedBranches < Base def run return :pass unless illegal_pushes.any? messages = illegal_pushes.map do |pushed_ref| "Deleting or force-pushing to #{pushed_ref.remote_ref} is not allowed." end [:fail, messages.join("\n")] end private def illegal_pushes @illegal_pushes ||= pushed_refs.select do |pushed_ref| protected?(pushed_ref) end end def protected?(ref) find_pattern(ref.remote_ref)&.destructive?(ref) end def find_pattern(remote_ref) ref_name = remote_ref[%r{refs/heads/(.*)}, 1] return if ref_name.nil? patterns.find do |pattern| File.fnmatch(pattern.to_s, ref_name) end end def patterns @patterns ||= fetch_patterns end def fetch_patterns branch_configurations.map do |pattern| if pattern.is_a?(Hash) Pattern.new(pattern.keys.first, pattern['destructive_only']) else Pattern.new(pattern, global_destructive_only?) end end end def branch_configurations config['branches'].to_a + config['branch_patterns'].to_a end def global_destructive_only? config['destructive_only'].nil? || config['destructive_only'] end Pattern = Struct.new('Pattern', :name, :destructive_only) do alias_method :to_s, :name alias_method :destructive_only?, :destructive_only def destructive?(ref) if destructive_only? ref.destructive? else true end end end end end ================================================ FILE: lib/overcommit/hook/pre_push/pub_test.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs Dart test suite (`pub run test`) before push # # @see https://pub.dev/packages/test#running-tests class PubTest < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/pytest.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `pytest` test suite before push # # @see https://github.com/pytest-dev/pytest class Pytest < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/python_nose.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `nose` test suite before push # # @see https://nose.readthedocs.io/en/latest/ class PythonNose < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_push/r_spec.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/r_spec' module Overcommit::Hook::PrePush # Runs `rspec` test suite # # @see http://rspec.info/ class RSpec < Base include Overcommit::Hook::Shared::RSpec end end ================================================ FILE: lib/overcommit/hook/pre_push/rake_target.rb ================================================ # frozen_string_literal: true require 'overcommit/hook/shared/rake_target' module Overcommit::Hook::PrePush # Runs rake targets # # @see Overcommit::Hook::Shared::RakeTarget class RakeTarget < Base include Overcommit::Hook::Shared::RakeTarget end end ================================================ FILE: lib/overcommit/hook/pre_push/test_unit.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrePush # Runs `test-unit` test suite before push # # @see https://github.com/test-unit/test-unit class TestUnit < Base def run result = execute(command) return :pass if result.success? output = result.stdout + result.stderr [:fail, output] end end end ================================================ FILE: lib/overcommit/hook/pre_rebase/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::PreRebase # Functionality common to all pre-rebase hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :upstream_branch, :rebased_branch, :detached_head?, :fast_forward?, :rebased_commits end end ================================================ FILE: lib/overcommit/hook/pre_rebase/merged_commits.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PreRebase # Prevents rebasing commits that have already been merged into one of # a specified set of branches. class MergedCommits < Base def run # Allow rebasing a detached HEAD since no refs are changed. return :pass if detached_head? || illegal_commits.empty? message = 'Cannot rebase commits that have already been merged into ' \ "one of #{branches.join(', ')}" [:fail, message] end private def branches @branches ||= config['branches'] end def illegal_commits @illegal_commits ||= rebased_commits.select do |commit_sha1| branches_containing_commit = Overcommit::GitRepo.branches_containing_commit(commit_sha1) (branches_containing_commit & branches).any? end end end end ================================================ FILE: lib/overcommit/hook/prepare_commit_msg/base.rb ================================================ # frozen_string_literal: true require 'forwardable' module Overcommit::Hook::PrepareCommitMsg # Functionality common to all prepare-commit-msg hooks. class Base < Overcommit::Hook::Base extend Forwardable def_delegators :@context, :commit_message_filename, :commit_message_source, :commit, :lock def modify_commit_message raise 'This expects a block!' unless block_given? # NOTE: this assumes all the hooks of the same type share the context's # memory. If that's not the case, this won't work. lock.synchronize do contents = File.read(commit_message_filename) File.open(commit_message_filename, 'w') do |f| f << (yield contents) end end end end end ================================================ FILE: lib/overcommit/hook/prepare_commit_msg/replace_branch.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::PrepareCommitMsg # Prepends the commit message with a message based on the branch name. # # === What to prepend # # It's possible to reference parts of the branch name through the captures in # the `branch_pattern` regex. # # For instance, if your current branch is `123-topic` then this config # # branch_pattern: '(\d+)-(\w+)' # replacement_text: '[#\1] ' # # would make this hook prepend commit messages with `[#123] `. # # Similarly, a replacement text of `[\1][\2]` would result in `[123][topic]`. # # == When to run this hook # # You can configure this to run only for specific types of commits by setting # the `skipped_commit_types`. The allowed types are # # - 'message' - if message is given via `-m`, `-F` # - 'template' - if `-t` is given or `commit.template` is set # - 'commit' - if `-c`, `-C`, or `--amend` is given # - 'merge' - if merging # - 'squash' - if squashing # class ReplaceBranch < Base DEFAULT_BRANCH_PATTERN = /\A(\d+)-(\w+).*\z/.freeze def run return :pass if skip? Overcommit::Utils.log.debug( "Checking if '#{Overcommit::GitRepo.current_branch}' matches #{branch_pattern}" ) return :warn unless branch_pattern.match?(Overcommit::GitRepo.current_branch) Overcommit::Utils.log.debug("Writing #{commit_message_filename} with #{new_template}") modify_commit_message do |old_contents| "#{new_template}#{old_contents}" end :pass end def new_template @new_template ||= begin curr_branch = Overcommit::GitRepo.current_branch curr_branch.gsub(branch_pattern, replacement_text) end end def branch_pattern @branch_pattern ||= begin pattern = config['branch_pattern'] Regexp.new((pattern || '').empty? ? DEFAULT_BRANCH_PATTERN : pattern) end end def replacement_text @replacement_text ||= begin if File.exist?(replacement_text_config) File.read(replacement_text_config).chomp else replacement_text_config end end end def replacement_text_config @replacement_text_config ||= config['replacement_text'] end def skipped_commit_types @skipped_commit_types ||= config['skipped_commit_types'].map(&:to_sym) end def skip? super || skipped_commit_types.include?(commit_message_source) end end end ================================================ FILE: lib/overcommit/hook/shared/bower_install.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all BowerInstall hooks. Runs `bower install` when a # change is detected in the repository's dependencies. # # @see http://bower.io/ module BowerInstall def run result = execute(command) return :fail, result.stderr unless result.success? :pass end end end ================================================ FILE: lib/overcommit/hook/shared/bundle_install.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all BundleInstall hooks. Runs `bundle install` when a # change is detected in the repository's dependencies. # # @see http://bundler.io/ module BundleInstall def run result = execute(command) return :fail, result.stdout unless result.success? :pass end end end ================================================ FILE: lib/overcommit/hook/shared/composer_install.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all ComposerInstall hooks. Runs `composer install` when # a change is detected in the repository's dependencies. # # @see https://getcomposer.org/ module ComposerInstall def run result = execute(command) return :fail, result.stdout unless result.success? :pass end end end ================================================ FILE: lib/overcommit/hook/shared/index_tags.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all IndexTags hooks. It runs ctags in the background so # your tag definitions are up-to-date. # # @see http://ctags.sourceforge.net/ module IndexTags def run execute_in_background([Overcommit::Utils.script_path('index-tags')]) :pass end end end ================================================ FILE: lib/overcommit/hook/shared/npm_install.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all NpmInstall hooks. Runs `npm install` when a change # is detected in the repository's dependencies. # # @see https://www.npmjs.com/ module NpmInstall def run result = execute(command) return :fail, result.stderr unless result.success? :pass end end end ================================================ FILE: lib/overcommit/hook/shared/pronto.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all Pronto hooks. Runs pronto linters. # @see https://github.com/prontolabs/pronto module Pronto MESSAGE_TYPE_CATEGORIZER = lambda do |type| type.include?('E') ? :error : :warning end MESSAGE_REGEX = /^(?(?:\w:)?[^:]+):(?\d+) (?[^ ]+)/.freeze def run result = execute(command) return :pass if result.success? # e.g. runtime errors generic_errors = extract_messages( result.stderr.split("\n"), /^(?[a-z]+)/i ) pronto_infractions = extract_messages( result.stdout.split("\n").select { |line| line.match?(MESSAGE_REGEX) }, MESSAGE_REGEX, MESSAGE_TYPE_CATEGORIZER, ) generic_errors + pronto_infractions end end end ================================================ FILE: lib/overcommit/hook/shared/r_spec.rb ================================================ # 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 ================================================ FILE: lib/overcommit/hook/shared/rake_target.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # runs specified rake targets. It fails on the first non- # successful exit. # module RakeTarget def run targets = config['targets'] if Array(targets).empty? raise 'RakeTarget: targets parameter is empty. Add at least one task to ' \ 'the targets parameter. Valid: Array of target names or String of ' \ 'target names' end targets.each do |task| result = execute(command + [task]) unless result.success? return :fail, "Rake target #{task}:\n#{result.stdout}" end end :pass end end end ================================================ FILE: lib/overcommit/hook/shared/submodule_status.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all `SubmoduleStatus` hooks to notify the user if any # submodules are uninitialized, out of date with the current index, or contain # merge conflicts. module SubmoduleStatus def run messages = [] submodule_statuses.each do |submodule_status| path = submodule_status.path if submodule_status.uninitialized? messages << "Submodule #{path} is uninitialized." elsif submodule_status.outdated? messages << "Submodule #{path} is out of date with the current index." elsif submodule_status.merge_conflict? messages << "Submodule #{path} has merge conflicts." end end return :pass if messages.empty? [:warn, messages.join("\n")] end private def submodule_statuses Overcommit::GitRepo.submodule_statuses(recursive: config['recursive']) end end end ================================================ FILE: lib/overcommit/hook/shared/yarn_install.rb ================================================ # frozen_string_literal: true module Overcommit::Hook::Shared # Shared code used by all YarnInstall hooks. Runs `yarn install` when a change # is detected in the repository's dependencies. # # @see https://yarnpkg.com/ module YarnInstall def run result = execute(command) return :fail, result.stderr unless result.success? :pass end end end ================================================ FILE: lib/overcommit/hook_context/base.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to the context with which a hook is being run. # # It acts as an adapter to the arguments passed to the hook, as well as # context-specific information such as staged files, providing a single source # of truth for this context. # # This is also important to house in a separate object so that any # calculations can be memoized across all hooks in a single object, which # helps with performance. # # @abstract class Base # Creates a hook context from the given configuration and input options. # # @param config [Overcommit::Configuration] # @param args [Array] # @param input [IO] standard input stream # @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 # command-line arguments and the standard input stream. # # This is intended to be used by ad hoc hooks so developers can link up # their existing git hooks with Overcommit. def execute_hook(command) Overcommit::Utils.execute(command, args: @args, input: input_string) end # Returns the camel-cased type of this hook (e.g. PreCommit) # # @return [String] def hook_class_name self.class.name.split('::').last end # Returns the snake-cased type of this hook (e.g. pre_commit) # # @return [String] def hook_type_name Overcommit::Utils.snake_case(hook_class_name) end # Returns the actual name of the hook script being run (e.g. pre-commit). # # @return [String] def hook_script_name hook_type_name.tr('_', '-') end # Initializes anything related to the environment. # # This is called before the hooks are run by the [HookRunner]. Different # hook types can perform different setup. def setup_environment # Implemented by subclass, if applicable end # Resets the environment to an appropriate state. # # This is called after the hooks have been run by the [HookRunner]. # Different hook types can perform different cleanup operations, which are # intended to "undo" the results of the call to {#setup_environment}. def cleanup_environment # Implemented by subclass, if applicable end # Returns a list of files that have been modified. # # By default, this returns an empty list. Subclasses should implement if # there is a concept of files changing for the type of hook being run. # # @return [Array] def modified_files [] end # Returns the full list of files tracked by git # # @return [Array] def all_files Overcommit::GitRepo.all_files end # Returns the contents of the entire standard input stream that were passed # to the hook. # # @return [String] def input_string @input_string ||= @input.read end # Returns an array of lines passed to the hook via the standard input # stream. # # @return [Array] def input_lines @input_lines ||= input_string.split("\n") end # Returns a message to display on failure. # # @return [String] def post_fail_message nil end private def filter_modified_files(modified_files) filter_directories(filter_nonexistent(modified_files)) end # Filter out non-existent files (unless it's a broken symlink, in which case # it's a file that points to a non-existent file). This could happen if a # file was renamed as part of an amendment, leading to the old file no # longer existing. def filter_nonexistent(modified_files) modified_files.select do |file| File.exist?(file) || Overcommit::Utils.broken_symlink?(file) end end # Filter out directories. This could happen when changing a symlink to a # directory as part of an amendment, since the symlink will still appear as # a file, but the actual working tree will have a directory. def filter_directories(modified_files) modified_files.reject do |file| File.directory?(file) && !Overcommit::Utils::FileUtils.symlink?(file) end end end end ================================================ FILE: lib/overcommit/hook_context/commit_msg.rb ================================================ # frozen_string_literal: true require_relative 'pre_commit' require_relative 'helpers/stash_unstaged_changes' require_relative 'helpers/file_modifications' module Overcommit::HookContext # Contains helpers related to contextual information used by commit-msg hooks. class CommitMsg < Base include Overcommit::HookContext::Helpers::StashUnstagedChanges include Overcommit::HookContext::Helpers::FileModifications def empty_message? commit_message.strip.empty? end # User commit message stripped of comments and diff (from verbose output). def commit_message commit_message_lines.join end # Updates the commit message to the specified text. def update_commit_message(message) ::File.open(commit_message_file, 'w') do |file| file.write(message) end end def commit_message_lines raw_commit_message_lines. take_while { |line| !line.start_with?('diff --git') }. reject { |line| line.start_with?(comment_character) } end def comment_character @comment_character ||= Overcommit::GitConfig.comment_character end def commit_message_file @args[0] end def post_fail_message "Failed commit message:\n#{commit_message_lines.join.chomp}\n\n" \ "Try again with your existing commit message by running:\n" \ "git commit --edit --file=#{commit_message_file}" end private def raw_commit_message_lines ::IO.readlines(commit_message_file) end end end ================================================ FILE: lib/overcommit/hook_context/diff.rb ================================================ # 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 ================================================ FILE: lib/overcommit/hook_context/helpers/file_modifications.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext module Helpers # This module contains methods for determining what files were changed and on what unique line # numbers did the change occur. module FileModifications # Returns whether this hook run was triggered by `git commit --amend` def amendment? return @amendment unless @amendment.nil? cmd = Overcommit::Utils.parent_command return unless cmd amend_pattern = 'commit(\s.*)?\s--amend(\s|$)' # Since the ps command can return invalid byte sequences for commands # containing unicode characters, we replace the offending characters, # since the pattern we're looking for will consist of ASCII characters unless cmd.valid_encoding? cmd = Overcommit::Utils. parent_command. encode('UTF-16be', invalid: :replace, replace: '?'). encode('UTF-8') end return @amendment if # True if the command is a commit with the --amend flag @amendment = !(/\s#{amend_pattern}/ =~ cmd).nil? # Check for git aliases that call `commit --amend` `git config --get-regexp "^alias\\." "#{amend_pattern}"`. scan(/alias\.([-\w]+)/). # Extract the alias each do |match| return @amendment if # True if the command uses a git alias for `commit --amend` @amendment = !(/git(\.exe)?\s+#{match[0]}/ =~ cmd).nil? end @amendment end # Get a list of added, copied, or modified files that have been staged. # Renames and deletions are ignored, since there should be nothing to check. def modified_files unless @modified_files currently_staged = Overcommit::GitRepo.modified_files(staged: true) @modified_files = currently_staged # Include files modified in last commit if amending if amendment? subcmd = 'show --format=%n' previously_modified = Overcommit::GitRepo.modified_files(subcmd: subcmd) @modified_files |= filter_modified_files(previously_modified) end end @modified_files end # Returns the set of line numbers corresponding to the lines that were # changed in a specified file. def modified_lines_in_file(file) @modified_lines ||= {} unless @modified_lines[file] @modified_lines[file] = Overcommit::GitRepo.extract_modified_lines(file, staged: true) # Include lines modified in last commit if amending if amendment? subcmd = 'show --format=%n' @modified_lines[file] += Overcommit::GitRepo.extract_modified_lines(file, subcmd: subcmd) end end @modified_lines[file] end end end end ================================================ FILE: lib/overcommit/hook_context/helpers/stash_unstaged_changes.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext module Helpers # This module contains behavior for stashing unstaged changes before hooks are ran and restoring # them afterwards module StashUnstagedChanges # Stash unstaged contents of files so hooks don't see changes that aren't # about to be committed. def setup_environment store_modified_times Overcommit::GitRepo.store_merge_state Overcommit::GitRepo.store_cherry_pick_state # Don't attempt to stash changes if all changes are staged, as this # prevents us from modifying files at all, which plays better with # editors/tools which watch for file changes. if !initial_commit? && unstaged_changes? stash_changes # While running hooks make it appear as if nothing changed restore_modified_times end end # Returns whether the current git branch is empty (has no commits). def initial_commit? return @initial_commit unless @initial_commit.nil? @initial_commit = Overcommit::GitRepo.initial_commit? end # Restore unstaged changes and reset file modification times so it appears # as if nothing ever changed. # # We want to restore the modification times for each of the files after # every step to ensure as little time as possible has passed while the # modification time on the file was newer. This helps us play more nicely # with file watchers. def cleanup_environment if @changes_stashed clear_working_tree restore_working_tree restore_modified_times end Overcommit::GitRepo.restore_merge_state Overcommit::GitRepo.restore_cherry_pick_state end private # Stores the modification times for all modified files to make it appear like # they never changed. # # This prevents (some) editors from complaining about files changing when we # stash changes before running the hooks. def store_modified_times @modified_times = {} staged_files = modified_files unstaged_files = Overcommit::GitRepo.modified_files(staged: false) (staged_files + unstaged_files).each do |file| next if Overcommit::Utils.broken_symlink?(file) next unless File.exist?(file) # Ignore renamed files (old file no longer exists) @modified_times[file] = File.mtime(file) end end # Returns whether there are any changes to tracked files which have not yet # been staged. def unstaged_changes? result = Overcommit::Utils.execute(%w[git --no-pager diff --quiet]) !result.success? end def stash_changes @stash_attempted = true stash_message = "Overcommit: Stash of repo state before hook run at #{Time.now}" result = Overcommit::Utils.with_environment('GIT_LITERAL_PATHSPECS' => '0') do Overcommit::Utils.execute( %w[git -c commit.gpgsign=false stash save --keep-index --quiet] + [stash_message] ) end unless result.success? # Failure to stash in this case is likely due to a configuration # issue (e.g. author/email not set or GPG signing key incorrect) raise Overcommit::Exceptions::HookSetupFailed, "Unable to setup environment for #{hook_script_name} hook run:" \ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}" end @changes_stashed = `git stash list -1`.include?(stash_message) end # Restores the file modification times for all modified files to make it # appear like they never changed. def restore_modified_times @modified_times.each do |file, time| next if Overcommit::Utils.broken_symlink?(file) next unless File.exist?(file) File.utime(time, time, file) end end # Clears the working tree so that the stash can be applied. def clear_working_tree removed_submodules = Overcommit::GitRepo.staged_submodule_removals result = Overcommit::Utils.execute(%w[git reset --hard]) unless result.success? raise Overcommit::Exceptions::HookCleanupFailed, "Unable to cleanup working tree after #{hook_script_name} hooks run:" \ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}" end # Hard-resetting a staged submodule removal results in the index being # reset but the submodule being restored as an empty directory. This empty # directory prevents us from stashing on a subsequent run if a hook fails. # # Work around this by removing these empty submodule directories as there # doesn't appear any reason to keep them around. removed_submodules.each do |submodule| FileUtils.rmdir(submodule.path) end end # Applies the stash to the working tree to restore the user's state. def restore_working_tree result = Overcommit::Utils.execute(%w[git stash pop --index]) unless result.success? raise Overcommit::Exceptions::HookCleanupFailed, "Unable to restore working tree after #{hook_script_name} hooks run:" \ "\nSTDOUT:#{result.stdout}\nSTDERR:#{result.stderr}" end end end end end ================================================ FILE: lib/overcommit/hook_context/post_checkout.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to contextual information used by post-checkout # hooks. class PostCheckout < Base # Returns the ref of the HEAD that we transitioned from. def previous_head @args[0] end # Returns the ref of the new current HEAD. def new_head @args[1] end # Returns whether this checkout was the result of changing/updating a # branch. def branch_checkout? @args[2].to_i == 1 end # Returns whether this checkout was for a single file. def file_checkout? !branch_checkout? end # Get a list of files that have been added or modified between # `previous_head` and `new_head`. Renames and deletions are ignored, since # there should be nothing to check. def modified_files @modified_files ||= Overcommit::GitRepo.modified_files(refs: "#{previous_head} #{new_head}") end end end ================================================ FILE: lib/overcommit/hook_context/post_commit.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to contextual information used by post-commit # hooks. class PostCommit < Base # Get a list of files that were added, copied, or modified in the last # commit. Renames and deletions are ignored, since there should be nothing # to check. def modified_files subcmd = 'show --format=%n' @modified_files ||= Overcommit::GitRepo.modified_files(subcmd: subcmd) end # Returns the set of line numbers corresponding to the lines that were # changed in a specified file. def modified_lines_in_file(file) subcmd = 'show --format=%n' @modified_lines ||= {} @modified_lines[file] ||= Overcommit::GitRepo.extract_modified_lines(file, subcmd: subcmd) end # Returns whether the commit that triggered this hook is the first commit on # the branch. # # @return [true,false] def initial_commit? return @initial_commit unless @initial_commit.nil? @initial_commit = !Overcommit::Utils.execute(%w[git rev-parse HEAD~]).success? end end end ================================================ FILE: lib/overcommit/hook_context/post_merge.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to contextual information used by post-merge # hooks. class PostMerge < Base attr_accessor :args # Get a list of files that were added, copied, or modified in the merge # commit. Renames and deletions are ignored, since there should be nothing # to check. def modified_files staged = squash? refs = 'HEAD^ HEAD' if merge_commit? @modified_files ||= Overcommit::GitRepo.modified_files(staged: staged, refs: refs) end # Returns the set of line numbers corresponding to the lines that were # changed in a specified file. def modified_lines_in_file(file) staged = squash? refs = 'HEAD^ HEAD' if merge_commit? @modified_lines ||= {} @modified_lines[file] ||= Overcommit::GitRepo.extract_modified_lines(file, staged: staged, refs: refs) end # Returns whether this merge was made using --squash def squash? @args[0].to_i == 1 end # Returns whether this merge was made without --squash def merge_commit? !squash? end end end ================================================ FILE: lib/overcommit/hook_context/post_rewrite.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers for contextual information used by post-rewrite hooks. class PostRewrite < Base # Returns whether this post-rewrite was triggered by `git commit --amend`. # # @return [true,false] def amend? @args[0] == 'amend' end # Returns whether this post-rewrite was triggered by `git rebase`. # # @return [true,false] def rebase? @args[0] == 'rebase' end # Returns the list of commits rewritten by the action that triggered this # hook run. # # @return [Array] def rewritten_commits @rewritten_commits ||= input_lines.map do |line| RewrittenCommit.new(*line.split(' ')) end end # Get a list of files that have been added or modified as part of a # rewritten commit. Renames and deletions are ignored, since there should be # nothing to check. def modified_files @modified_files ||= begin @modified_files = [] rewritten_commits.each do |rewritten_commit| refs = "#{rewritten_commit.old_hash} #{rewritten_commit.new_hash}" @modified_files |= Overcommit::GitRepo.modified_files(refs: refs) end filter_modified_files(@modified_files) end end # Struct encapsulating the old and new SHA1 hashes of a rewritten commit RewrittenCommit = Struct.new(:old_hash, :new_hash) end end ================================================ FILE: lib/overcommit/hook_context/pre_commit.rb ================================================ # frozen_string_literal: true require 'fileutils' require 'set' require_relative 'helpers/stash_unstaged_changes' require_relative 'helpers/file_modifications' module Overcommit::HookContext # Contains helpers related to contextual information used by pre-commit hooks. # # This includes staged files, which lines of those files have been modified, # etc. It is also responsible for saving/restoring the state of the repo so # hooks only inspect staged changes. class PreCommit < Base include Overcommit::HookContext::Helpers::StashUnstagedChanges include Overcommit::HookContext::Helpers::FileModifications end end ================================================ FILE: lib/overcommit/hook_context/pre_push.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to contextual information used by pre-push hooks. class PrePush < Base attr_accessor :args def remote_name @args[0] end def remote_url @args[1] end def remote_ref_deletion? return @remote_ref_deletion if defined?(@remote_ref_deletion) @remote_ref_deletion ||= input_lines. first&. split(' ')&. first == '(deleted)' end def pushed_refs input_lines.map do |line| PushedRef.new(*line.split(' ')) end end def modified_files @modified_files ||= pushed_refs.map(&:modified_files).flatten.uniq end def modified_lines_in_file(file) @modified_lines ||= {} @modified_lines[file] = pushed_refs.each_with_object(Set.new) do |pushed_ref, set| set.merge(pushed_ref.modified_lines_in_file(file)) end end PushedRef = Struct.new(:local_ref, :local_sha1, :remote_ref, :remote_sha1) do def forced? !(created? || deleted? || overwritten_commits.empty?) end def created? remote_sha1 == '0' * 40 end def deleted? local_sha1 == '0' * 40 end def destructive? deleted? || forced? end def modified_files Overcommit::GitRepo.modified_files(refs: ref_range) end def modified_lines_in_file(file) Overcommit::GitRepo.extract_modified_lines(file, refs: ref_range) end def to_s "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}" end private def ref_range "#{remote_sha1}..#{local_sha1}" end def overwritten_commits return @overwritten_commits if defined? @overwritten_commits result = Overcommit::Subprocess.spawn(%W[git rev-list #{remote_sha1} ^#{local_sha1}]) if result.success? result.stdout.split("\n") else raise Overcommit::Exceptions::GitRevListError, "Unable to check if commits on the remote ref will be overwritten: #{result.stderr}" end end end end end ================================================ FILE: lib/overcommit/hook_context/pre_rebase.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to contextual information used by pre-rebase # hooks. class PreRebase < Base # Returns the name of the branch we are rebasing onto. def upstream_branch @args[0] end # Returns the name of the branch being rebased. Empty if rebasing a # detached HEAD. def rebased_branch @rebased_branch ||= @args[1] || `git symbolic-ref --short --quiet HEAD`.chomp end # Returns whether we are rebasing a detached HEAD rather than a branch def detached_head? rebased_branch.empty? end # Returns whether this rebase is a fast-forward def fast_forward? rebased_commits.empty? end # Returns the SHA1-sums of the series of commits to be rebased # in reverse topological order. def rebased_commits rebased_ref = detached_head? ? 'HEAD' : rebased_branch @rebased_commits ||= `git rev-list --topo-order --reverse #{upstream_branch}..#{rebased_ref}`. split("\n") end end end ================================================ FILE: lib/overcommit/hook_context/prepare_commit_msg.rb ================================================ # frozen_string_literal: true module Overcommit::HookContext # Contains helpers related to contextual information used by prepare-commit-msg # hooks. class PrepareCommitMsg < Base # Returns the name of the file that contains the commit log message def commit_message_filename @args[0] end # Returns the source of the commit message, and can be: message (if a -m or # -F option was given); template (if a -t option was given or the # configuration option commit.template is set); merge (if the commit is a # merge or a .git/MERGE_MSG file exists); squash (if a .git/SQUASH_MSG file # exists); or commit, followed by a commit SHA-1 (if a -c, -C or --amend # option was given) def commit_message_source @args[1]&.to_sym end # Returns the commit's SHA-1. # If commit_message_source is :commit, it's passed through the command-line. def commit_message_source_ref @args[2] || `git rev-parse HEAD` end # Lock for the pre_commit_message file. Should be shared by all # prepare-commit-message hooks def lock @lock ||= Monitor.new end end end ================================================ FILE: lib/overcommit/hook_context/run_all.rb ================================================ # frozen_string_literal: true require 'set' module Overcommit::HookContext # Simulates a pre-commit context pretending that all files have been changed. # # This results in pre-commit hooks running against the entire repository, # which is useful for automated CI scripts. class RunAll < Base def modified_files @modified_files ||= all_files end # Returns all lines in the file since in this context the entire repo is # being scrutinized. # # @param file [String] # @return [Set] def modified_lines_in_file(file) @modified_lines_in_file ||= {} @modified_lines_in_file[file] ||= Set.new(1..count_lines(file)) end def hook_class_name 'PreCommit' end def hook_type_name 'pre_commit' end def hook_script_name 'pre-commit' end def initial_commit? return @initial_commit unless @initial_commit.nil? @initial_commit = Overcommit::GitRepo.initial_commit? end private def count_lines(file) File.foreach(file).count end end end ================================================ FILE: lib/overcommit/hook_context.rb ================================================ # frozen_string_literal: true # Utility module which manages the creation of {HookContext}s. module Overcommit::HookContext 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, **cli_options) rescue LoadError, NameError => e # Could happen when a symlink was created for a hook type Overcommit does # not yet support. raise Overcommit::Exceptions::HookContextLoadError, "Unable to load '#{hook_type}' hook context: '#{e}'", e.backtrace end end ================================================ FILE: lib/overcommit/hook_loader/base.rb ================================================ # frozen_string_literal: true module Overcommit::HookLoader # Responsible for loading hooks from a file. class Base # @param config [Overcommit::Configuration] # @param context [Overcommit::HookContext] # @param logger [Overcommit::Logger] def initialize(config, context, logger) @config = config @context = context @log = logger end # When implemented in subclasses, loads the hooks for which that subclass is # responsible. # # @return [Array] def load_hooks raise NotImplementedError end private attr_reader :log # Load and return a {Hook} from a CamelCase hook name. def create_hook(hook_name) hook_type_class = Overcommit::Hook.const_get(@context.hook_class_name) hook_base_class = hook_type_class.const_get(:Base) hook_class = hook_type_class.const_get(hook_name) unless hook_class < hook_base_class raise Overcommit::Exceptions::HookLoadError, "Class #{hook_name} is not a subclass of #{hook_base_class}." end begin Overcommit::Hook.const_get(@context.hook_class_name). const_get(hook_name). new(@config, @context) rescue LoadError, NameError => e raise Overcommit::Exceptions::HookLoadError, "Unable to load hook '#{hook_name}': #{e}", e.backtrace end end end end ================================================ FILE: lib/overcommit/hook_loader/built_in_hook_loader.rb ================================================ # frozen_string_literal: true module Overcommit::HookLoader # Responsible for loading hooks that ship with Overcommit. class BuiltInHookLoader < Base def load_hooks @config.enabled_builtin_hooks(@context).map do |hook_name| underscored_hook_name = Overcommit::Utils.snake_case(hook_name) require "overcommit/hook/#{@context.hook_type_name}/#{underscored_hook_name}" create_hook(hook_name) end end end end ================================================ FILE: lib/overcommit/hook_loader/plugin_hook_loader.rb ================================================ # frozen_string_literal: true require 'digest' module Overcommit::HookLoader # Responsible for loading hooks that are specific to the repository Overcommit # is running in. class PluginHookLoader < Base def load_hooks check_for_modified_plugins if @config.verify_signatures? hooks = plugin_paths.map do |plugin_path| require plugin_path hook_name = Overcommit::Utils.camel_case(File.basename(plugin_path, '.rb')) create_hook(hook_name) end hooks + ad_hoc_hook_names.map do |hook_name| create_ad_hoc_hook(hook_name) end end def update_signatures log.success('No plugin signatures have changed') if modified_plugins.empty? modified_plugins.each do |plugin| plugin.update_signature! log.warning "Updated signature of plugin #{plugin.hook_name}" end end private def plugin_paths directory = File.join(@config.plugin_directory, @context.hook_type_name) Dir[File.join(directory, '*.rb')].sort end def plugin_hook_names plugin_paths.map do |path| Overcommit::Utils.camel_case(File.basename(path, '.rb')) end end def ad_hoc_hook_names @config.enabled_ad_hoc_hooks(@context) end def modified_plugins (plugin_hook_names + ad_hoc_hook_names). map { |hook_name| Overcommit::HookSigner.new(hook_name, @config, @context) }. select(&:signature_changed?) end def check_for_modified_plugins return if modified_plugins.empty? log.bold_warning "The following #{@context.hook_script_name} plugins " \ 'have been added, changed, or had their configuration modified:' log.newline modified_plugins.each do |signer| log.warning " * #{signer.hook_name} in #{signer.hook_path}" end log.newline log.bold_warning 'You should verify the changes and then run:' log.newline log.warning "overcommit --sign #{@context.hook_script_name}" log.newline log.log "For more information, see #{Overcommit::REPO_URL}#security" raise Overcommit::Exceptions::InvalidHookSignature end def create_ad_hoc_hook(hook_name) hook_module = Overcommit::Hook.const_get(@context.hook_class_name) hook_base = hook_module.const_get('Base') # Implement a simple class that executes the command and returns pass/fail # based on the exit status hook_class = Class.new(hook_base) do def run result = @context.execute_hook(command) if result.success? :pass else [:fail, result.stdout + result.stderr] end end end hook_module.const_set(hook_name, hook_class).new(@config, @context) rescue LoadError, NameError => e raise Overcommit::Exceptions::HookLoadError, "Unable to load hook '#{hook_name}': #{e}", e.backtrace end end end ================================================ FILE: lib/overcommit/hook_runner.rb ================================================ # frozen_string_literal: true module Overcommit # Responsible for loading the hooks the repository has configured and running # them, collecting and displaying the results. class HookRunner # rubocop:disable Metrics/ClassLength # @param config [Overcommit::Configuration] # @param logger [Overcommit::Logger] # @param context [Overcommit::HookContext] # @param printer [Overcommit::Printer] def initialize(config, logger, context, printer) @config = config @log = logger @context = context @printer = printer @hooks = [] @lock = Mutex.new @resource = ConditionVariable.new @slots_available = @config.concurrency end # Loads and runs the hooks registered for this {HookRunner}. def run # ASSUMPTION: we assume the setup and cleanup calls will never need to be # interrupted, i.e. they will finish quickly. Should further evidence # suggest this assumption does not hold, we will have to separately wrap # these calls to allow some sort of "are you sure?" double-interrupt # functionality, but until that's deemed necessary let's keep it simple. InterruptHandler.isolate_from_interrupts do # Load hooks before setting up the environment so that the repository # has not been touched yet. This way any load errors at this point don't # result in Overcommit leaving the repository in a bad state. load_hooks # Setup the environment without automatically calling # `cleanup_environment` on an error. This is because it's possible that # the `setup_environment` code did not fully complete, so there's no # guarantee that `cleanup_environment` will be able to accomplish # anything of value. The safest thing to do is therefore nothing in the # unlikely case of failure. @context.setup_environment begin run_hooks ensure @context.cleanup_environment end end end private attr_reader :log def run_hooks # rubocop:disable Metrics/MethodLength if @hooks.any?(&:enabled?) @printer.start_run # Sort so hooks requiring fewer processors get queued first. This # ensures we make better use of our available processors @hooks_left = @hooks.sort_by { |hook| processors_for_hook(hook) } @threads = Array.new(@config.concurrency) { Thread.new(&method(:consume)) } begin InterruptHandler.disable_until_finished_or_interrupted do @threads.each(&:join) end rescue Interrupt @printer.interrupt_triggered # We received an interrupt on the main thread, so alert the # remaining workers that an exception occurred @interrupted = true @threads.each { |thread| thread.raise Interrupt } end print_results hook_failed = @failed || @interrupted if hook_failed message = @context.post_fail_message @printer.hook_run_failed(message) unless message.nil? end !hook_failed else @printer.nothing_to_run true # Run was successful end end def consume loop do hook = @lock.synchronize { @hooks_left.pop } break unless hook run_hook(hook) end end def wait_for_slot(hook) @lock.synchronize do slots_needed = processors_for_hook(hook) loop do if @slots_available >= slots_needed @slots_available -= slots_needed # Give another thread a chance since there are still slots available @resource.signal if @slots_available > 0 break elsif @slots_available > 0 # It's possible that another hook that requires fewer slots can be # served, so give another a chance @resource.signal # Wait for a signal from another thread to try again @resource.wait(@lock) else # Otherwise there are not slots left, so just wait for signal @resource.wait(@lock) end end end end def release_slot(hook) @lock.synchronize do slots_released = processors_for_hook(hook) @slots_available += slots_released # Signal every time in case there are threads that are already waiting for # these slots to be released @resource.signal end end def processors_for_hook(hook) hook.parallelize? ? hook.processors : @config.concurrency end def print_results if @interrupted @printer.run_interrupted elsif @failed @printer.run_failed elsif @warned @printer.run_warned else @printer.run_succeeded end end def run_hook(hook) # rubocop:disable Metrics/CyclomaticComplexity status, output = nil, nil begin wait_for_slot(hook) return if should_skip?(hook) status, output = hook.run_and_transform rescue Overcommit::Exceptions::MessageProcessingError => e status = :fail output = e.message rescue StandardError => e status = :fail output = "Hook raised unexpected error\n#{e.message}\n#{e.backtrace.join("\n")}" end @failed = true if status == :fail @warned = true if status == :warn @printer.end_hook(hook, status, output) unless @interrupted status rescue Interrupt @interrupted = true ensure release_slot(hook) end def should_skip?(hook) return true if @interrupted || !hook.enabled? if hook.skip? if hook.required? @printer.required_hook_not_skipped(hook) else # Tell user if hook was skipped only if it actually would have run @printer.hook_skipped(hook) if hook.run? return true end end !hook.run? end def load_hooks require "overcommit/hook/#{@context.hook_type_name}/base" @hooks += HookLoader::BuiltInHookLoader.new(@config, @context, @log).load_hooks # Load plugin hooks after so they can subclass existing hooks @hooks += HookLoader::PluginHookLoader.new(@config, @context, @log).load_hooks rescue LoadError => e # Include a more helpful message that will probably save some confusion message = 'A load error occurred. ' + if @config['gemfile'] "Did you forget to specify a gem in your `#{@config['gemfile']}`?" else 'Did you forget to install a gem?' end raise Overcommit::Exceptions::HookLoadError, "#{message}\n#{e.message}", e.backtrace end end end ================================================ FILE: lib/overcommit/hook_signer.rb ================================================ # frozen_string_literal: true module Overcommit # Calculates, stores, and retrieves stored signatures of hook plugins. class HookSigner attr_reader :hook_name # We don't want to include the skip setting as it is set by Overcommit # itself IGNORED_CONFIG_KEYS = %w[skip].freeze # @param hook_name [String] name of the hook # @param config [Overcommit::Configuration] # @param context [Overcommit::HookContext] def initialize(hook_name, config, context) @hook_name = hook_name @config = config @context = context end # Returns the path of the file that should be incorporated into this hooks # signature. # # @return [String] def hook_path @hook_path ||= begin plugin_path = File.join(@config.plugin_directory, @context.hook_type_name, "#{Overcommit::Utils.snake_case(@hook_name)}.rb") if File.exist?(plugin_path) plugin_path else # Otherwise this is an ad hoc hook using an existing hook script hook_config = @config.for_hook(@hook_name, @context.hook_class_name) command = Array(hook_config['command'] || hook_config['required_executable']) if @config.verify_signatures? && signable_file?(command.first) && !Overcommit::GitRepo.tracked?(command.first) raise Overcommit::Exceptions::InvalidHookDefinition, 'Hook specified a `required_executable` or `command` that ' \ 'is a path relative to the root of the repository, and so ' \ 'must be tracked by Git in order to be signed' end File.join(Overcommit::Utils.repo_root, command.first.to_s) end end end def signable_file?(file) return unless file sep = Overcommit::OS.windows? ? '\\' : File::SEPARATOR file.start_with?(".#{sep}") || file.start_with?(Overcommit::Utils.repo_root) end # Return whether the signature for this hook has changed since it was last # calculated. # # @return [true,false] def signature_changed? signature != stored_signature end # Update the current stored signature for this hook. def update_signature! result = Overcommit::Utils.execute( %w[git config --local] + [signature_config_key, signature] ) unless result.success? raise Overcommit::Exceptions::GitConfigError, "Unable to write to local repo git config: #{result.stderr}" end end private # Calculates a hash of a hook using a combination of its configuration and # file contents. # # This way, if either the plugin code changes or its configuration changes, # the hash will change and we can alert the user to this change. def signature hook_config = @config.for_hook(@hook_name, @context.hook_class_name). dup. tap { |config| IGNORED_CONFIG_KEYS.each { |k| config.delete(k) } } content_to_sign = if signable_file?(hook_path) && Overcommit::GitRepo.tracked?(hook_path) hook_contents end Digest::SHA256.hexdigest(content_to_sign.to_s + hook_config.to_s) end def hook_contents File.read(hook_path) end def stored_signature result = Overcommit::Utils.execute( %w[git config --local --get] + [signature_config_key] ) if result.status == 1 # Key doesn't exist return '' elsif result.status != 0 raise Overcommit::Exceptions::GitConfigError, "Unable to read from local repo git config: #{result.stderr}" end result.stdout.chomp end def signature_config_key "overcommit.#{@context.hook_class_name}.#{@hook_name}.signature" end end end ================================================ FILE: lib/overcommit/installer.rb ================================================ # frozen_string_literal: true require 'fileutils' module Overcommit # Manages the installation of Overcommit hooks in a git repository. class Installer # rubocop:disable Metrics/ClassLength TEMPLATE_DIRECTORY = File.join(Overcommit::HOME, 'template-dir') MASTER_HOOK = File.join(TEMPLATE_DIRECTORY, 'hooks', 'overcommit-hook') def initialize(logger) @log = logger end def run(target, options) @target = target @options = options validate_target case @options[:action] when :uninstall then uninstall when :update then update else install end end private attr_reader :log def install log.log "Installing hooks into #{@target}" ensure_directory(hooks_path) preserve_old_hooks install_master_hook install_hook_files install_starter_config # Auto-sign configuration file on install config(verify: false).update_signature! log.success "Successfully installed hooks into #{@target}" end def uninstall log.log "Removing hooks from #{@target}" uninstall_hook_files uninstall_master_hook restore_old_hooks log.success "Successfully removed hooks from #{@target}" end # @return [true,false] whether the hooks were updated def update unless FileUtils.compare_file(MASTER_HOOK, master_hook_install_path) preserve_old_hooks install_master_hook install_hook_files log.success "Hooks updated to Overcommit version #{Overcommit::VERSION}" true end end def hooks_path @hooks_path ||= Dir.chdir(@target) { GitConfig.hooks_path } end def old_hooks_path File.join(hooks_path, 'old-hooks') end def master_hook_install_path File.join(hooks_path, 'overcommit-hook') end def ensure_directory(path) FileUtils.mkdir_p(path) end def validate_target absolute_target = File.expand_path(@target) unless File.directory?(absolute_target) raise Overcommit::Exceptions::InvalidGitRepo, 'is not a directory' end git_dir_check = Dir.chdir(absolute_target) do Overcommit::Utils.execute(%w[git rev-parse --git-dir]) end unless git_dir_check.success? raise Overcommit::Exceptions::InvalidGitRepo, 'does not appear to be a git repository' end end def install_master_hook FileUtils.mkdir_p(hooks_path) FileUtils.cp(MASTER_HOOK, master_hook_install_path) end def uninstall_master_hook FileUtils.rm_rf(master_hook_install_path, secure: true) end def install_hook_files # Copy each hook type (pre-commit, commit-msg, etc.) from the master hook. Dir.chdir(hooks_path) do Overcommit::Utils.supported_hook_types.each do |hook_type| unless can_replace_file?(hook_type) raise Overcommit::Exceptions::PreExistingHooks, "Hook '#{File.expand_path(hook_type)}' already exists and " \ 'was not installed by Overcommit' end FileUtils.rm_f(hook_type) FileUtils.cp('overcommit-hook', hook_type) end end end def can_replace_file?(file) @options[:force] || !File.exist?(file) || overcommit_hook?(file) end def preserve_old_hooks return unless File.directory?(hooks_path) ensure_directory(old_hooks_path) Overcommit::Utils.supported_hook_types.each do |hook_type| hook_file = File.join(hooks_path, hook_type) unless can_replace_file?(hook_file) log.warning "Hook '#{File.expand_path(hook_type)}' already exists and " \ "was not installed by Overcommit. Moving to '#{old_hooks_path}'" FileUtils.mv(hook_file, old_hooks_path) end end # Remove old-hooks directory if empty (i.e. no old hooks were preserved) FileUtils.rmdir(old_hooks_path) if Dir.entries(old_hooks_path).size <= 2 end def restore_old_hooks return unless File.directory?(old_hooks_path) log.log "Restoring old hooks from #{old_hooks_path}" Dir.chdir(old_hooks_path) do Overcommit::Utils.supported_hook_types.each do |hook_type| FileUtils.mv(hook_type, hooks_path) if File.exist?(hook_type) end end # Remove old-hooks directory if empty FileUtils.rmdir(old_hooks_path) log.success "Successfully restored old hooks from #{old_hooks_path}" end def uninstall_hook_files return unless File.directory?(hooks_path) Dir.chdir(hooks_path) do Overcommit::Utils.supported_hook_types.each do |hook_type| FileUtils.rm_rf(hook_type, secure: true) if overcommit_hook?(hook_type) end end end def install_starter_config repo_config_file = File.join(@target, Overcommit::CONFIG_FILE_NAME) return if File.exist?(repo_config_file) FileUtils.cp(File.join(Overcommit::HOME, 'config', 'starter.yml'), repo_config_file) end def overcommit_hook?(file) File.read(file) =~ /OVERCOMMIT_DISABLE/ rescue Errno::ENOENT # Some Ruby implementations (e.g. JRuby) raise an error when the file # doesn't exist. Standardize the behavior to return false. false end # Returns the configuration for this repository. def config(options = {}) Overcommit::ConfigurationLoader.new(log, options).load_repo_config end end end ================================================ FILE: lib/overcommit/interrupt_handler.rb ================================================ # frozen_string_literal: true require 'singleton' # Provides a handler for interrupt signals (SIGINT), allowing the application to # finish what it's currently working on. class InterruptHandler include Singleton attr_accessor :isolate_signals, :signal_received, :reenable_on_interrupt # Initialize safe interrupt signal handling. def initialize self.isolate_signals = false self.signal_received = false self.reenable_on_interrupt = false Signal.trap('INT') do if isolate_signals self.signal_received = true else if reenable_on_interrupt self.reenable_on_interrupt = false self.isolate_signals = true end raise Interrupt # Allow interrupt to propagate to code end end end class << self # Provide a way to allow a single Ctrl-C interrupt to happen and atomically # re-enable interrupt protections once that interrupt is propagated. # # This prevents a race condition where code like the following: # # begin # InterruptHandler.disable! # ... do stuff ... # rescue Interrupt # ... handle it ... # ensure # InterruptHandler.enable! # end # # ...could have the `enable!` call to the interrupt handler not called in # the event another interrupt was received in between the interrupt being # handled and the `ensure` block being entered. # # Thus you should always write: # # begin # InterruptHandler.disable_until_finished_or_interrupted do # ... do stuff ... # end # rescue Interrupt # ... handle it ... # rescue # ... handle any other exceptions ... # end def disable_until_finished_or_interrupted instance.reenable_on_interrupt = true instance.isolate_signals = false yield ensure instance.isolate_signals = true end # Disable interrupt isolation. def disable! instance.isolate_signals = false end # Enable interrupt isolation. def enable! instance.isolate_signals = true end # Enable interrupt isolation while executing the provided block. # # @yield block to execute with interrupt isolation def isolate_from_interrupts instance.signal_received = false instance.isolate_signals = true result = yield instance.isolate_signals = false result end end end ================================================ FILE: lib/overcommit/logger.rb ================================================ # frozen_string_literal: true module Overcommit # Encapsulates all communication to an output source. class Logger # Helper for creating a logger which outputs nothing. def self.silent new(File.open(File::NULL, 'w')) end # Creates a logger that will write to the given output stream. # # @param out [IO] def initialize(out) @out = out @colorize = if ENV.key?('OVERCOMMIT_COLOR') !%w[0 false no].include?(ENV['OVERCOMMIT_COLOR']) else @out.tty? end end # Write output without a trailing newline. def partial(*args) @out.print(*args) end # Prints a newline character (alias for readability). def newline log end # Flushes the [IO] object for partial lines def flush @out.flush if @out.respond_to? :flush end # Write a line of output. # # A newline character will always be appended. def log(*args) @out.puts(*args) end # Write a line of output if debug mode is enabled. def debug(*args) color('35', *args) unless ENV.fetch('OVERCOMMIT_DEBUG') { '' }.empty? end # Write a line of output that is intended to be emphasized. def bold(*args) color('1', *args) end # Write a line of output indicating a problem or error. def error(*args) color(31, *args) end # Write a line of output indicating a problem or error which is emphasized # over a regular problem or error. def bold_error(*args) color('1;31', *args) end # Write a line of output indicating a successful or noteworthy event. def success(*args) color(32, *args) end # Write a line of output indicating a potential cause for concern, but not # an actual error. def warning(*args) color(33, *args) end # Write a line of output indicating a potential cause for concern, but with # greater emphasize compared to other warnings. def bold_warning(*args) color('1;33', *args) end private # Outputs text wrapped in ANSI escape code necessary to produce a given # color/text display. # # @param code [String] ANSI escape code, e.g. '1;33' for "bold yellow" # @param str [String] string to wrap # @param partial [true,false] whether to omit a newline def color(code, str, partial = false) send(partial ? :partial : :log, @colorize ? "\033[#{code}m#{str}\033[0m" : str) end end end ================================================ FILE: lib/overcommit/message_processor.rb ================================================ # frozen_string_literal: true module Overcommit # Utility class that encapsulates the handling of hook messages and whether # they affect lines the user has modified or not. # # This class exposes an endpoint that extracts an appropriate hook/status # output tuple from an array of {Overcommit::Hook::Message}s, respecting the # configuration settings for the given hook. class MessageProcessor ERRORS_MODIFIED_HEADER = 'Errors on modified lines:' WARNINGS_MODIFIED_HEADER = 'Warnings on modified lines:' ERRORS_UNMODIFIED_HEADER = "Errors on lines you didn't modify:" WARNINGS_UNMODIFIED_HEADER = "Warnings on lines you didn't modify:" ERRORS_GENERIC_HEADER = 'Errors:' WARNINGS_GENERIC_HEADER = 'Warnings:' # @param hook [Overcommit::Hook::Base] # @param unmodified_lines_setting [String] how to treat messages on # unmodified lines def initialize(hook, unmodified_lines_setting) @hook = hook @setting = unmodified_lines_setting end # Returns a hook status/output tuple from the messages this processor was # initialized with. # # @return [Array] def hook_result(messages) status, output = basic_status_and_output(messages) # Nothing to do if there are no problems to begin with return [status, output] if status == :pass # Return as-is if this type of hook doesn't have the concept of modified lines return [status, output] unless @hook.respond_to?(:modified_lines_in_file) handle_modified_lines(messages, status) end private def handle_modified_lines(messages, status) messages = remove_ignored_messages(messages) messages_with_line, generic_messages = messages.partition(&:line) # Always print generic messages first output = print_messages( generic_messages, ERRORS_GENERIC_HEADER, WARNINGS_GENERIC_HEADER ) messages_on_modified_lines, messages_on_unmodified_lines = messages_with_line.partition { |message| message_on_modified_line?(message) } output += print_messages( messages_on_modified_lines, ERRORS_MODIFIED_HEADER, WARNINGS_MODIFIED_HEADER ) output += print_messages( messages_on_unmodified_lines, ERRORS_UNMODIFIED_HEADER, WARNINGS_UNMODIFIED_HEADER ) [transform_status(status, generic_messages + messages_on_modified_lines), output] end def transform_status(status, messages_on_modified_lines) # `report` indicates user wants the original status return status if @setting == 'report' error_messages, warning_messages = messages_on_modified_lines.partition { |msg| msg.type == :error } if can_upgrade_to_warning?(status, error_messages) status = :warn end if can_upgrade_to_passing?(status, warning_messages) status = :pass end status end def can_upgrade_to_warning?(status, error_messages) status == :fail && error_messages.empty? end def can_upgrade_to_passing?(status, warning_messages) status == :warn && @setting == 'ignore' && warning_messages.empty? end # Returns status and output for messages assuming no special treatment of # messages occurring on unmodified lines. def basic_status_and_output(messages) status = if messages.any? { |message| message.type == :error } :fail elsif messages.any? { |message| message.type == :warning } :warn else :pass end output = '' if messages.any? output += messages.join("\n") + "\n" end [status, output] end def print_messages(messages, error_heading, warning_heading) output = '' errors, warnings = messages.partition { |msg| msg.type == :error } if errors.any? output += "#{error_heading}\n#{errors.join("\n")}\n" end if warnings.any? output += "#{warning_heading}\n#{warnings.join("\n")}\n" end output end def remove_ignored_messages(messages) # If user wants to ignore messages on unmodified lines, simply remove them return messages unless @setting == 'ignore' messages.select { |message| message_on_modified_line?(message) } end def message_on_modified_line?(message) # Message without line number assumed to apply to entire file return true unless message.line @hook.modified_lines_in_file(message.file).include?(message.line) end end end ================================================ FILE: lib/overcommit/os.rb ================================================ # frozen_string_literal: true require 'rbconfig' module Overcommit # Methods relating to the current operating system module OS class << self def windows? !(/mswin|msys|mingw|bccwin|wince|emc/ =~ host_os).nil? end def cygwin? !(/cygwin/ =~ host_os).nil? end def mac? !(/darwin|mac os/ =~ host_os).nil? end def unix? !windows? end def linux? unix? && !mac? && !cygwin? end private def host_os @host_os ||= ::RbConfig::CONFIG['host_os'].freeze end end SEPARATOR = (windows? ? '\\' : File::SEPARATOR).freeze end end ================================================ FILE: lib/overcommit/printer.rb ================================================ # frozen_string_literal: true require 'monitor' module Overcommit # Provide a set of callbacks which can be executed as events occur during the # course of {HookRunner#run}. class Printer attr_reader :log def initialize(config, logger, context) @config = config @log = logger @context = context @lock = Monitor.new # Need to use monitor so we can have re-entrant locks synchronize_all_methods end # Executed at the very beginning of running the collection of hooks. def start_run log.bold "Running #{hook_script_name} hooks" unless @config['quiet'] end def nothing_to_run log.debug "✓ No applicable #{hook_script_name} hooks to run" end def hook_skipped(hook) log.warning "Skipping #{hook.name}" end def required_hook_not_skipped(hook) log.warning "Cannot skip #{hook.name} since it is required" end # Executed at the end of an individual hook run. def end_hook(hook, status, output) # Want to print the header for quiet hooks only if the result wasn't good # so that the user knows what failed print_header(hook) if (!hook.quiet? && !@config['quiet']) || status != :pass print_result(hook, status, output) end def interrupt_triggered log.error "\nInterrupt signal received. Stopping hooks..." end # Executed when a hook run was interrupted/cancelled by user. def run_interrupted log.newline log.warning '⚠ Hook run interrupted by user' log.warning "⚠ If files appear modified/missing, check your stash to recover them\n" end # Executed when one or more hooks by the end of the run. def run_failed log.newline log.error "✗ One or more #{hook_script_name} hooks failed" log.newline end # Executed when no hooks failed by the end of the run, but some warned. def run_warned log.newline log.warning "⚠ All #{hook_script_name} hooks passed, but with warnings" log.newline end # Executed when no hooks failed by the end of the run. def run_succeeded unless @config['quiet'] log.newline log.success "✓ All #{hook_script_name} hooks passed" log.newline end end def hook_run_failed(message) log.newline log.log message log.newline end private def print_header(hook) hook_name = "[#{hook.name}] " log.partial hook.description log.partial '.' * [70 - hook.description.length - hook_name.length, 0].max log.partial hook_name log.flush end def print_result(hook, status, output) # rubocop:disable Metrics/CyclomaticComplexity case status when :pass log.success 'OK' unless @config['quiet'] || hook.quiet? when :warn log.warning 'WARNING' print_report(output, :bold_warning) when :fail log.error 'FAILED' print_report(output, :bold_error) when :interrupt log.error 'INTERRUPTED' print_report(output, :bold_error) else log.error '???' print_report("Hook returned unknown status `#{status.inspect}` -- ignoring.", :bold_error) end end def print_report(output, format = :log) log.send(format, output) unless output.nil? || output.empty? end def hook_script_name @context.hook_script_name end # Get all public methods that were defined on this class and wrap them with # synchronization locks so we ensure the output isn't interleaved amongst # the various threads. def synchronize_all_methods methods = self.class.instance_methods - self.class.superclass.instance_methods methods.each do |method_name| old_method = :"old_#{method_name}" new_method = :"synchronized_#{method_name}" self.class.__send__(:alias_method, old_method, method_name) self.class.send(:define_method, new_method) do |*args| @lock.synchronize { __send__(old_method, *args) } end self.class.__send__(:alias_method, method_name, new_method) end end end end ================================================ FILE: lib/overcommit/subprocess.rb ================================================ # frozen_string_literal: true require 'childprocess' require 'tempfile' require 'overcommit/os' module Overcommit # Manages execution of a child process, collecting the exit status and # standard out/error output. class Subprocess # Encapsulates the result of a process. # # @attr_reader status [Integer] exit status code returned by process # @attr_reader stdout [String] standard output stream output # @attr_reader stderr [String] standard error stream output Result = Struct.new(:status, :stdout, :stderr) do def success? status == 0 end end class << self # Spawns a new process using the given array of arguments (the first # element is the command). # # @param args [Array] # @param options [Hash] # @option options [String] input string to pass via standard input stream # @return [Result] def spawn(args, options = {}) args = win32_prepare_args(args) if OS.windows? process = ChildProcess.build(*args) out, err = assign_output_streams(process) process.duplex = true if options[:input] # Make stdin available if needed process.start if options[:input] begin process.io.stdin.puts(options[:input]) rescue StandardError # Silently ignore if the standard input stream of the spawned # process is closed before we get a chance to write to it. This # happens on JRuby a lot. ensure process.io.stdin.close end end process.wait err.rewind out.rewind Result.new(process.exit_code, out.read, err.read) end # Spawns a new process in the background using the given array of # arguments (the first element is the command). def spawn_detached(args) args = win32_prepare_args(args) if OS.windows? process = ChildProcess.build(*args) process.detach = true assign_output_streams(process) process.start end private # Necessary to run commands in the cmd.exe context. # Args are joined to properly handle quotes and special characters. def win32_prepare_args(args) args = args.map do |arg| # Quote args that contain whitespace arg = "\"#{arg}\"" if arg =~ /\s/ # Escape cmd.exe metacharacters arg.gsub(/[()%!^"<>&|]/, '^\0') end %w[cmd.exe /c] + [args.join(' ')] end # @param process [ChildProcess] # @return [Array] def assign_output_streams(process) %w[out err].map do |stream_name| ::Tempfile.new(stream_name).tap do |stream| stream.sync = true process.io.send("std#{stream_name}=", stream) end end end end end end ================================================ FILE: lib/overcommit/utils/file_utils.rb ================================================ # frozen_string_literal: true require 'overcommit/os' require 'overcommit/subprocess' module Overcommit::Utils # Utility functions for file IO. module FileUtils class << self # When the host OS is Windows, uses the `mklink` command to create an # NTFS symbolic link from `new_name` to `old_name`. Otherwise delegates # to `File.symlink` def symlink(old_name, new_name) return File.symlink(old_name, new_name) unless Overcommit::OS.windows? result = win32_mklink_cmd(old_name, new_name) result.status end # When the host OS is Windows, uses the `dir` command to check whether # `file_name` is an NTFS symbolic link. Otherwise delegates to # `File.symlink`. def symlink?(file_name) return File.symlink?(file_name) unless Overcommit::OS.windows? result = win32_dir_cmd(file_name) win32_symlink?(result.stdout) end # When the host OS is Windows, uses the `dir` command to check whether # `link_name` is an NTFS symbolic link. If so, it parses the target from # the command output. Otherwise raises an `ArgumentError`. Delegates to # `File.readlink` if the host OS is not Windows. def readlink(link_name) return File.readlink(link_name) unless Overcommit::OS.windows? result = win32_dir_cmd(link_name) unless win32_symlink?(result.stdout) raise ArgumentError, "#{link_name} is not a symlink" end # Extract symlink target from output, which looks like: # 11/13/2012 12:53 AM mysymlink [C:\Windows\Temp\somefile.txt] result.stdout[/\[(.+)\]/, 1] end private def win32_dir_cmd(file_name) Overcommit::Subprocess.spawn( %W[dir #{win32_fix_pathsep(file_name)}] ) end def win32_mklink_cmd(old_name, new_name) Overcommit::Subprocess.spawn( %W[mklink #{win32_fix_pathsep(new_name)} #{win32_fix_pathsep(old_name)}] ) end def win32_fix_pathsep(path) path.tr(File::SEPARATOR, Overcommit::OS::SEPARATOR) end def win32_symlink?(dir_output) !(dir_output =~ //).nil? end end end end ================================================ FILE: lib/overcommit/utils/messages_utils.rb ================================================ # frozen_string_literal: true module Overcommit::Utils # Utility to process messages module MessagesUtils class << self # Extract file, line number, and type of message from an error/warning # messages in output. # # Assumes each element of `output` is a separate error/warning with all # information necessary to identify it. # # @param output_messages [Array] unprocessed error/warning messages # @param regex [Regexp] regular expression defining `file`, `line` and # `type` capture groups used to extract file locations and error/warning # type from each line of output # @param type_categorizer [Proc] function executed against the `type` # capture group to convert it to a `:warning` or `:error` symbol. Assumes # `:error` if `nil`. # @raise [Overcommit::Exceptions::MessageProcessingError] line of output did # not match regex # @return [Array] def extract_messages(output_messages, regex, type_categorizer = nil) output_messages.map.with_index do |message, index| unless match = message.match(regex) raise Overcommit::Exceptions::MessageProcessingError, 'Unexpected output: unable to determine line number or type ' \ "of error/warning for output:\n" \ "#{output_messages[index..].join("\n")}" end file = extract_file(match, message) line = extract_line(match, message) if match.names.include?('line') && match[:line] type = extract_type(match, message, type_categorizer) Overcommit::Hook::Message.new(type, file, line, message) end end private def extract_file(match, message) return unless match.names.include?('file') if match[:file].to_s.empty? raise Overcommit::Exceptions::MessageProcessingError, "Unexpected output: no file found in '#{message}'" end match[:file] end def extract_line(match, message) return unless match.names.include?('line') Integer(match[:line]) rescue ArgumentError, TypeError raise Overcommit::Exceptions::MessageProcessingError, "Unexpected output: invalid line number found in '#{message}'" end def extract_type(match, message, type_categorizer) if type_categorizer type_match = match.names.include?('type') ? match[:type] : nil type = type_categorizer.call(type_match) unless Overcommit::Hook::MESSAGE_TYPES.include?(type) raise Overcommit::Exceptions::MessageProcessingError, "Invalid message type '#{type}' for '#{message}': must " \ "be one of #{Overcommit::Hook::MESSAGE_TYPES.inspect}" end type else :error # Assume error since no categorizer was defined end end end end end ================================================ FILE: lib/overcommit/utils.rb ================================================ # frozen_string_literal: true require 'pathname' require 'overcommit/os' require 'overcommit/subprocess' require 'overcommit/command_splitter' require 'tempfile' module Overcommit # Utility functions for general use. module Utils # Helper class for doing quick constraint validations on version numbers. # # This allows us to execute code based on the git version. class Version < Gem::Version # Overload comparison operators so we can conveniently compare this # version directly to a string in code. %w[< <= > >= == !=].each do |operator| define_method operator do |version| case version when String super(Gem::Version.new(version)) else super(version) end end end end class << self # @return [Overcommit::Logger] logger with which to send debug output attr_accessor :log def script_path(script) File.join(Overcommit::HOME, 'libexec', script) end # Returns an absolute path to the root of the repository. # # We do this ourselves rather than call `git rev-parse --show-toplevel` to # solve an issue where the .git directory might not actually be valid in # tests. # # @return [String] def repo_root @repo_root ||= begin result = execute(%w[git rev-parse --show-toplevel]) unless result.success? raise Overcommit::Exceptions::InvalidGitRepo, 'Unable to determine location of GIT_DIR. ' \ 'Not a recognizable Git repository!' end result.stdout.chomp("\n") end end # Returns an absolute path to the .git directory for a repo. # # @return [String] def git_dir @git_dir ||= begin cmd = %w[git rev-parse] cmd << (GIT_VERSION < '2.5' ? '--git-dir' : '--git-common-dir') result = execute(cmd) unless result.success? raise Overcommit::Exceptions::InvalidGitRepo, 'Unable to determine location of GIT_DIR. ' \ 'Not a recognizable Git repository!' end File.expand_path(result.stdout.chomp("\n"), Dir.pwd) end end # Remove ANSI escape sequences from a string. # # This is useful for stripping colorized output from external tools. # # @param text [String] # @return [String] def strip_color_codes(text) text.gsub(/\e\[(\d+)(;\d+)*m/, '') end # Shamelessly stolen from: # stackoverflow.com/questions/1509915/converting-camel-case-to-underscore-case-in-ruby def snake_case(str) str.gsub(/::/, '/'). gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2'). gsub(/([a-z\d])([A-Z])/, '\1_\2'). tr('-', '_'). downcase end # Converts a string containing underscores/hyphens/spaces into CamelCase. def camel_case(str) str.split(/_|-| /).map { |part| part.sub(/^\w/, &:upcase) }.join end # Returns a list of supported hook types (pre-commit, commit-msg, etc.) def supported_hook_types Dir[File.join(HOOK_DIRECTORY, '*')]. select { |file| File.directory?(file) }. reject { |file| File.basename(file) == 'shared' }. map { |file| File.basename(file).tr('_', '-') } end # Returns a list of supported hook classes (PreCommit, CommitMsg, etc.) def supported_hook_type_classes supported_hook_types.map do |file| file.split('-').map(&:capitalize).join end end # @param cmd [String] # @return [true,false] whether a command can be found given the current # environment path. def in_path?(cmd) # ENV['PATH'] doesn't include the repo root, but that is a valid # location for executables, so we want to add it to the list of places # we are checking for the executable. paths = [repo_root] + ENV['PATH'].split(File::PATH_SEPARATOR) exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : [''] paths.each do |path| exts.each do |ext| cmd_with_ext = cmd.upcase.end_with?(ext.upcase) ? cmd : "#{cmd}#{ext}" full_path = File.join(path, cmd_with_ext) return true if File.executable?(full_path) end end false end # Return the parent command that triggered this hook run # # @return [String,nil] the command as a string, if a parent exists. def parent_command # When run in Docker containers, there may be no parent process. return if Process.ppid.zero? if OS.windows? `wmic process where ProcessId=#{Process.ppid} get CommandLine /FORMAT:VALUE`. strip. slice(/(?<=CommandLine=).+/) elsif OS.cygwin? # Cygwin's `ps` command behaves differently than the traditional # Linux version, but a comparable `procps` is provided to compensate. `procps -ocommand= -p #{Process.ppid}`.chomp else `ps -ocommand= -p #{Process.ppid}`.chomp end end # Execute a command in a subprocess, capturing exit status and output from # both standard and error streams. # # This is intended to provide a centralized place to perform any checks or # filtering of the command before executing it. # # The `args` option provides a convenient way of splitting up long # argument lists which would otherwise exceed the maximum command line # length of the OS. It will break up the list into chunks and run the # command with the same prefix `initial_args`, finally combining the # output together at the end. # # This requires that the external command you are running can have its # work split up in this way and still produce the same resultant output # when outputs of the individual commands are concatenated back together. # # @param initial_args [Array] # @param options [Hash] # @option options [Array] :args long list of arguments to split up # @return [Overcommit::Subprocess::Result] status, stdout, and stderr def execute(initial_args, options = {}) if initial_args.include?('|') raise Overcommit::Exceptions::InvalidCommandArgs, 'Cannot pipe commands with the `execute` helper' end result = if (splittable_args = options.fetch(:args) { [] }).any? debug(initial_args.join(' ') + " ... (#{splittable_args.length} splittable args)") Overcommit::CommandSplitter.execute(initial_args, options) else debug(initial_args.join(' ')) Overcommit::Subprocess.spawn(initial_args, options) end debug("EXIT STATUS: #{result.status}") debug("STDOUT: #{result.stdout.inspect}") debug("STDERR: #{result.stderr.inspect}") result end # Execute a command in a subprocess, returning immediately. # # This provides a convenient way to execute long-running processes for # which we do not need to know the result. # # @param args [Array] # @return [ChildProcess] detached process spawned in the background def execute_in_background(args) if args.include?('|') raise Overcommit::Exceptions::InvalidCommandArgs, 'Cannot pipe commands with the `execute_in_background` helper' end debug("Spawning background task: #{args.join(' ')}") Subprocess.spawn_detached(args) end # Return the number of processors used by the OS for process scheduling. # # @see https://github.com/grosser/parallel/blob/v1.6.1/lib/parallel/processor_count.rb#L17-L51 def processor_count # rubocop:disable all @processor_count ||= begin if Overcommit::OS.windows? require 'win32ole' result = WIN32OLE.connect('winmgmts://').ExecQuery( 'select NumberOfLogicalProcessors from Win32_Processor' ) result.to_enum.collect(&:NumberOfLogicalProcessors).reduce(:+) elsif File.readable?('/proc/cpuinfo') IO.read('/proc/cpuinfo').scan(/^processor/).size elsif File.executable?('/usr/bin/hwprefs') IO.popen('/usr/bin/hwprefs thread_count').read.to_i elsif File.executable?('/usr/sbin/psrinfo') IO.popen('/usr/sbin/psrinfo').read.scan(/^.*on-*line/).size elsif File.executable?('/usr/sbin/ioscan') IO.popen('/usr/sbin/ioscan -kC processor') do |out| out.read.scan(/^.*processor/).size end elsif File.executable?('/usr/sbin/pmcycles') IO.popen('/usr/sbin/pmcycles -m').read.count("\n") elsif File.executable?('/usr/sbin/lsdev') IO.popen('/usr/sbin/lsdev -Cc processor -S 1').read.count("\n") elsif File.executable?('/usr/sbin/sysctl') IO.popen('/usr/sbin/sysctl -n hw.ncpu').read.to_i elsif File.executable?('/sbin/sysctl') IO.popen('/sbin/sysctl -n hw.ncpu').read.to_i else # Unknown platform; assume 1 processor 1 end end end # Calls a block of code with a modified set of environment variables, # restoring them once the code has executed. def with_environment(env) old_env = {} env.each do |var, value| old_env[var] = ENV[var.to_s] ENV[var.to_s] = value end yield ensure old_env.each { |var, value| ENV[var.to_s] = value } end # Returns whether a file is a broken symlink. # # @return [true,false] def broken_symlink?(file) # JRuby's implementation of File.exist? returns true for broken # symlinks, so we need use File.size? Overcommit::Utils::FileUtils.symlink?(file) && File.size?(file).nil? end # Convert a glob pattern to an absolute path glob pattern rooted from the # repository root directory. # # @param glob [String] # @return [String] def convert_glob_to_absolute(glob) File.join(repo_root, glob) end # Return whether a pattern matches the given path. # # @param pattern [String] # @param path [String] def matches_path?(pattern, path) File.fnmatch?( pattern, path, File::FNM_PATHNAME | # Wildcard doesn't match separator File::FNM_DOTMATCH # Wildcards match dotfiles ) end private # Log debug output. # # This is necessary since some specs indirectly call utility functions but # don't explicitly set the logger for the Utils class, so we do a quick # check here to see if it's set before we attempt to log. # # @param args [Array] def debug(*args) log&.debug(*args) end end end end ================================================ FILE: lib/overcommit/version.rb ================================================ # frozen_string_literal: true # Defines the gem version. module Overcommit VERSION = '0.68.0' end ================================================ FILE: lib/overcommit.rb ================================================ # frozen_string_literal: true require 'overcommit/os' require 'overcommit/constants' require 'overcommit/exceptions' require 'overcommit/utils/file_utils' require 'overcommit/utils' require 'overcommit/git_version' require 'overcommit/configuration_validator' require 'overcommit/configuration' require 'overcommit/configuration_loader' require 'overcommit/hook/base' require 'overcommit/hook_context/base' require 'overcommit/hook_context' require 'overcommit/git_config' require 'overcommit/git_repo' require 'overcommit/hook_signer' require 'overcommit/hook_loader/base' require 'overcommit/hook_loader/built_in_hook_loader' require 'overcommit/hook_loader/plugin_hook_loader' require 'overcommit/interrupt_handler' require 'overcommit/printer' require 'overcommit/hook_runner' require 'overcommit/installer' require 'overcommit/logger' require 'overcommit/version' ================================================ FILE: libexec/gerrit-change-id ================================================ #!/bin/sh # From Gerrit Code Review 2.5.1 # # Part of Gerrit Code Review (http://code.google.com/p/gerrit/) # # Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # CHANGE_ID_AFTER="Bug|Issue|Story" MSG="$1" # Check for, and add if missing, a unique Change-Id # add_ChangeId() { clean_message=`sed -e ' /^diff --git a\/.*/{ s/// q } /^Signed-off-by:/d /^#/d ' "$MSG" | git stripspace` if test -z "$clean_message" then return fi # Does Change-Id: already exist? if so, exit (no change). if grep -i '^Change-Id:' "$MSG" >/dev/null then return fi id=`_gen_ChangeId` T="$MSG.tmp.$$" AWK=awk if [ -x /usr/xpg4/bin/awk ]; then # Solaris AWK is just too broken AWK=/usr/xpg4/bin/awk fi # How this works: # - parse the commit message as (textLine+ blankLine*)* # - assume textLine+ to be a footer until proven otherwise # - exception: the first block is not footer (as it is the title) # - read textLine+ into a variable # - then count blankLines # - once the next textLine appears, print textLine+ blankLine* as these # aren't footer # - in END, the last textLine+ block is available for footer parsing $AWK ' BEGIN { # while we start with the assumption that textLine+ # is a footer, the first block is not. isFooter = 0 footerComment = 0 blankLines = 0 } # Skip lines starting with "#" without any spaces before it. /^#/ { next } # Skip the line starting with the diff command and everything after it, # up to the end of the file, assuming it is only patch data. # If more than one line before the diff was empty, strip all but one. /^diff --git a/ { blankLines = 0 while (getline) { } next } # Count blank lines outside footer comments /^$/ && (footerComment == 0) { blankLines++ next } # Catch footer comment /^\[[a-zA-Z0-9-]+:/ && (isFooter == 1) { footerComment = 1 } /]$/ && (footerComment == 1) { footerComment = 2 } # We have a non-blank line after blank lines. Handle this. (blankLines > 0) { print lines for (i = 0; i < blankLines; i++) { print "" } lines = "" blankLines = 0 isFooter = 1 footerComment = 0 } # Detect that the current block is not the footer (footerComment == 0) && (!/^\[?[a-zA-Z0-9-]+:/ || /^[a-zA-Z0-9-]+:\/\//) { isFooter = 0 } { # We need this information about the current last comment line if (footerComment == 2) { footerComment = 0 } if (lines != "") { lines = lines "\n"; } lines = lines $0 } # Footer handling: # If the last block is considered a footer, splice in the Change-Id at the # right place. # Look for the right place to inject Change-Id by considering # CHANGE_ID_AFTER. Keys listed in it (case insensitive) come first, # then Change-Id, then everything else (eg. Signed-off-by:). # # Otherwise just print the last block, a new line and the Change-Id as a # block of its own. END { unprinted = 1 if (isFooter == 0) { print lines "\n" lines = "" } changeIdAfter = "^(" tolower("'"$CHANGE_ID_AFTER"'") "):" numlines = split(lines, footer, "\n") for (line = 1; line <= numlines; line++) { if (unprinted && match(tolower(footer[line]), changeIdAfter) != 1) { unprinted = 0 print "Change-Id: I'"$id"'" } print footer[line] } if (unprinted) { print "Change-Id: I'"$id"'" } }' "$MSG" > $T && mv $T "$MSG" || rm -f $T } _gen_ChangeIdInput() { echo "tree `git write-tree`" if parent=`git rev-parse "HEAD^0" 2>/dev/null` then echo "parent $parent" fi echo "author `git var GIT_AUTHOR_IDENT`" echo "committer `git var GIT_COMMITTER_IDENT`" echo printf '%s' "$clean_message" } _gen_ChangeId() { _gen_ChangeIdInput | git hash-object -t commit --stdin } add_ChangeId ================================================ FILE: libexec/index-tags ================================================ #!/bin/sh # Indexes all tags for this repository for easy navigation with Vim, storing the # file in /.git/tags. If you're using Fugitive.vim, you'll automatically # have access to the tags file; otherwise you'll have to modify your `tags` # option in your vimrc to search the generated file. set -e dir="`git rev-parse --git-dir`" trap "rm -f $dir/tags.$$" EXIT err_file=$dir/ctags.err if ctags --tag-relative -Rf$dir/tags.$$ --exclude=.git "$@" 2>${err_file}; then mv $dir/tags.$$ $dir/tags [ -e ${err_file} ] && rm -f ${err_file} else # Ignore STDERR unless `ctags` returned a non-zero exit code cat ${err_file} fi ================================================ FILE: overcommit.gemspec ================================================ # frozen_string_literal: true require_relative './lib/overcommit/constants' require_relative './lib/overcommit/version' Gem::Specification.new do |s| s.name = 'overcommit' s.version = Overcommit::VERSION s.license = 'MIT' s.summary = 'Git hook manager' s.description = 'Utility to install, configure, and extend Git hooks' s.authors = ['Shane da Silva'] s.email = ['shane@dasilva.io'] s.homepage = Overcommit::REPO_URL s.post_install_message = 'Install hooks by running `overcommit --install` in your Git repository' s.metadata = { 'changelog_uri' => 'https://github.com/sds/overcommit/blob/main/CHANGELOG.md' } s.require_paths = %w[lib] s.executables = ['overcommit'] s.files = Dir['bin/**/*'] + Dir['config/*.yml'] + Dir['lib/**/*.rb'] + Dir['libexec/**/*'] + Dir['template-dir/**/*'] s.required_ruby_version = '>= 2.6' s.add_dependency 'childprocess', '>= 0.6.3', '< 6' s.add_dependency 'iniparse', '~> 1.4' s.add_dependency 'rexml', '>= 3.3.9' end ================================================ FILE: spec/integration/committing_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'commiting' do subject { shell(%w[git commit --allow-empty -m Test]) } let(:config) { <<-YML } CommitMsg: ALL: enabled: false PreCommit: ALL: enabled: false AuthorName: enabled: true YML around do |example| repo do File.open('.overcommit.yml', 'w') { |f| f.write(config) } `overcommit --install > #{File::NULL}` example.run end end context 'when a hook fails' do before do `git config --local user.name ""` end it 'exits with a non-zero status' do subject.status.should_not == 0 end end context 'when no hooks fail on single author name' do before do `git config --local user.name "John"` end it 'exits successfully' do subject.status.should == 0 end end context 'when no hooks fail' do before do `git config --local user.name "John Doe"` end it 'exits successfully' do subject.status.should == 0 end end end describe 'commiting to an empty repo' do subject { shell(%w[git commit -m Test]) } let(:config) { <<-YML } CommitMsg: ALL: enabled: false PreCommit: ALL: enabled: false HardTabs: enabled: true YML around do |example| repo do `overcommit --install > #{File::NULL}` File.open('.overcommit.yml', 'w') { |f| f.write(config) } File.open('test.txt', 'w') { |f| f.write(file_contents) } `git add test.txt` example.run end end context 'when a hook fails' do let(:file_contents) { "\t\tFile with some hard tabs" } it 'exits with a non-zero status' do subject.status.should_not == 0 end it 'does not complain about missing HEAD' do subject.stderr.should_not include 'HEAD' end it 'does not lose changes' do File.open('test.txt').read.should == file_contents end end context 'when no hooks fail' do let(:file_contents) { 'File without hard tabs' } it 'exits successfully' do subject.status.should == 0 end it 'does not complain about missing HEAD' do subject.stderr.should_not include 'HEAD' end it 'does not lose changes' do subject File.open('test.txt').read.should == file_contents end end end ================================================ FILE: spec/integration/configuration_signing_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'yaml' describe 'configuration file signing' do let(:enable_verification) { true } let(:new_verify_signatures) { verify_signatures } let(:config) do { 'verify_signatures' => verify_signatures, 'CommitMsg' => { 'ALL' => { 'enabled' => false }, }, 'PreCommit' => { 'ALL' => { 'enabled' => false }, }, } end let(:new_config) do config.dup.tap do |conf| conf['verify_signatures'] = new_verify_signatures end end subject { shell(%w[git commit --allow-empty -m Test]) } around do |example| repo do `overcommit --install > #{File::NULL}` echo(config.to_yaml, '.overcommit.yml') `overcommit --sign` if configuration_signed echo(new_config.to_yaml, '.overcommit.yml') example.run end end context 'when verify_signatures is true' do let(:verify_signatures) { true } context 'and the configuration has not been signed' do let(:configuration_signed) { false } it 'reports a signature error' do subject.status.should_not == 0 end end context 'and the configuration has been signed' do let(:configuration_signed) { true } it 'does not report a signature error' do subject.status.should == 0 end context 'and verify_signatures was changed to false' do let(:new_verify_signatures) { false } it 'reports a signature error' do subject.status.should_not == 0 end end end end context 'when verify_signatures is false' do let(:verify_signatures) { false } context 'and the configuration has not been signed' do let(:configuration_signed) { false } it 'reports a signature error' do subject.status.should_not == 0 end end context 'and the configuration has been signed' do let(:configuration_signed) { true } it 'does not report a signature error' do subject.status.should == 0 end context 'and verify_signatures was changed to true' do let(:new_verify_signatures) { true } it 'reports a signature error' do subject.status.should_not == 0 end end end end end ================================================ FILE: spec/integration/diff_flag_spec.rb ================================================ # 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 ================================================ FILE: spec/integration/disable_overcommit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'disabling Overcommit' do subject { shell(%w[git commit --allow-empty -m Test]) } around do |example| repo do `overcommit --install > #{File::NULL}` Overcommit::Utils.with_environment('OVERCOMMIT_DISABLE' => overcommit_disable) do touch 'blah' `git add blah` example.run end end end context 'when the OVERCOMMIT_DISABLE environment variable is set' do let(:overcommit_disable) { '1' } it 'exits successfully' do subject.status.should == 0 end it 'does not run any hooks' do subject.stdout.should_not be_empty subject.stderr.should_not include 'Running pre-commit hooks' end end context 'when the OVERCOMMIT_DISABLE environment variable is set to zero' do let(:overcommit_disable) { '0' } it 'exits successfully' do subject.status.should == 0 end it 'runs the hooks' do subject.stderr.should include 'Running pre-commit hooks' end end context 'when the OVERCOMMIT_DISABLE environment variable is unset' do let(:overcommit_disable) { nil } it 'exits successfully' do subject.status.should == 0 end it 'runs the hooks' do subject.stderr.should include 'Running pre-commit hooks' end end end ================================================ FILE: spec/integration/gemfile_option_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'specifying `gemfile` option in Overcommit configuration' do 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 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') `overcommit --install > #{File::NULL}` FileUtils.mkdir_p(File.join('.git-hooks', 'pre_commit')) 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 subject { shell(%w[git commit --allow-empty -m Test]) } context 'when configuration explicitly sets the gemfile to false' do let(:config) { "gemfile: false\n" + super() } it 'runs the hook successfully' do subject.status.should == 0 end end end end ================================================ FILE: spec/integration/hook_signing_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'yaml' describe 'hook signing' do let(:enable_verification) { true } let(:fake_hook_config) do { 'enabled' => true, 'requires_files' => false, 'required_executable' => script_path, } end let(:script_path) do Overcommit::OS.windows? ? '.\\pre-commit.bat' : './pre-commit' end let(:hook_script) { normalize_indent(<<-BASH) } echo Hello BASH let(:config) do { 'verify_signatures' => verify_signatures, 'CommitMsg' => { 'ALL' => { 'enabled' => false }, }, 'PreCommit' => { 'ALL' => { 'enabled' => false }, 'FakeHook' => fake_hook_config, }, } end subject { shell(%w[git commit --allow-empty -m Test]) } context 'when a plugin hook configuration is changed' do let(:new_fake_hook_config) { fake_hook_config.merge('some_option' => true) } let(:new_config) do config.dup.tap do |conf| conf['PreCommit']['FakeHook'] = new_fake_hook_config end end around do |example| repo do echo(config.to_yaml, '.overcommit.yml') `overcommit --install > #{File::NULL}` echo(hook_script, script_path) FileUtils.chmod(0o755, script_path) `git add #{script_path}` `overcommit --sign` `overcommit --sign pre-commit` echo(new_config.to_yaml, '.overcommit.yml') example.run end end context 'and signatures are verified' do let(:verify_signatures) { true } it 'reports a signature error' do subject.status.should_not == 0 end end context 'and signatures are not verified' do let(:verify_signatures) { false } it 'does not report a signature error' do subject.status.should == 0 end end end context 'and a plugin hook is changed' do let(:new_hook_script) { normalize_indent(<<-BASH) } echo This could potentially be malicious code BASH around do |example| repo do echo(config.to_yaml, '.overcommit.yml') `overcommit --install > #{File::NULL}` echo(hook_script, script_path) FileUtils.chmod(0o755, script_path) `git add #{script_path}` `overcommit --sign` `overcommit --sign pre-commit` echo(new_hook_script, script_path) example.run end end context 'and signatures are verified' do let(:verify_signatures) { true } it 'reports a signature error' do subject.status.should_not == 0 end end context 'and signatures are not verified' do let(:verify_signatures) { false } it 'does not report a signature error' do subject.status.should == 0 end end end end ================================================ FILE: spec/integration/installing_overcommit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'installing Overcommit' do let(:enable_verification) { true } it 'signs the configuration file' do repo do `overcommit --install` touch('some-file') `git add some-file` result = shell(%w[git commit --allow-empty -m Test]) result.status.should == 0 end end context 'when template directory points to the Overcommit template directory' do around do |example| repo(template_dir: Overcommit::Installer::TEMPLATE_DIRECTORY) do example.run end end it 'automatically installs Overcommit hooks for new repositories' do Overcommit::Utils.supported_hook_types.each do |hook_type| hook_file = File.join('.git', 'hooks', hook_type) File.read(hook_file).should include 'OVERCOMMIT' end end context 'and Overcommit is manually installed' do before do `overcommit --install` end it 'leaves the hooks intact' do Overcommit::Utils.supported_hook_types.each do |hook_type| hook_file = File.join('.git', 'hooks', hook_type) File.read(hook_file).should include 'OVERCOMMIT' end end end end end ================================================ FILE: spec/integration/parallelize_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'timeout' describe 'running a hook with parallelism disabled' do subject { shell(%w[git commit --allow-empty -m Test]) } let(:config) { <<-YML } concurrency: 20 CommitMsg: TrailingPeriod: enabled: true parallelize: false command: ['ruby', '-e', 'sleep 1'] TextWidth: enabled: true parallelize: true processors: 1 YML around do |example| repo do File.open('.overcommit.yml', 'w') { |f| f.write(config) } `overcommit --install > #{File::NULL}` example.run end end # Test fails on Ruby 3.0 on Windows but nothing else. Would glady accept a pull # request that resolves. unless Overcommit::OS.windows? && Overcommit::Utils::Version.new(RUBY_VERSION) >= '3' && Overcommit::Utils::Version.new(RUBY_VERSION) < '3.1' it 'does not hang' do result = Timeout.timeout(5) { subject } result.stderr.should_not include 'No live threads left. Deadlock?' end end end ================================================ FILE: spec/integration/protected_branches_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::ProtectedBranches, if: Overcommit::GIT_VERSION >= '2.0' do let(:flags) { '' } let(:pushed_ref) { remote_ref } subject do shell("git push #{flags} origin #{pushed_ref}:#{remote_ref}".split) end let(:config) { <<-YML } CommitMsg: ALL: enabled: false PreCommit: ALL: enabled: false PrePush: ALL: enabled: false ProtectedBranches: enabled: true branches: - protected - protected_for_destructive_only: destructive_only: true YML around do |example| remote_repo = repo do `git checkout -b protected > #{File::NULL} 2>&1` `git commit --allow-empty -m "Remote commit"` `git checkout -b unprotected > #{File::NULL} 2>&1` `git checkout -b dummy > #{File::NULL} 2>&1` end repo do File.open('.overcommit.yml', 'w') { |f| f.write(config) } `git remote add origin file://#{remote_repo}` `git checkout -b #{remote_ref} > #{File::NULL} 2>&1` `git commit --allow-empty -m "Local commit"` example.run end end shared_context 'deleting' do let(:pushed_ref) { '' } end shared_context 'force-pushing' do let(:flags) { '--force' } end shared_context 'remote exists locally' do before { `git fetch origin #{remote_ref} > #{File::NULL} 2>&1` } end shared_context 'local branch up-to-date' do before { `git rebase --keep-empty origin/#{remote_ref} > #{File::NULL} 2>&1` } end shared_context 'ProtectedBranches enabled' do before { `overcommit --install > #{File::NULL}` } end shared_examples 'push succeeds' do it 'exits successfully' do subject.status.should == 0 end end shared_examples 'push fails' do it 'exits with a non-zero status' do subject.status.should_not == 0 end end shared_examples 'push succeeds when remote exists locally' do context 'when remote exists locally' do include_context 'remote exists locally' context 'when up-to-date with remote' do include_context 'local branch up-to-date' include_examples 'push succeeds' end context 'when not up-to-date with remote' do include_examples 'push succeeds' end end context 'when remote does not exist locally' do include_examples 'push fails' end end shared_examples 'push succeeds when up-to-date with remote' do context 'when remote exists locally' do include_context 'remote exists locally' context 'when up-to-date with remote' do include_context 'local branch up-to-date' include_examples 'push succeeds' end context 'when not up-to-date with remote' do include_examples 'push fails' end end context 'when remote does not exist locally' do include_examples 'push fails' end end shared_examples 'push always fails' do context 'when remote exists locally' do include_context 'remote exists locally' context 'when up-to-date with remote' do include_context 'local branch up-to-date' include_examples 'push fails' end context 'when not up-to-date with remote' do include_examples 'push fails' end end context 'when remote does not exist locally' do include_examples 'push fails' end end shared_examples 'push always succeeds' do context 'when remote exists locally' do include_context 'remote exists locally' context 'when up-to-date with remote' do include_context 'local branch up-to-date' include_examples 'push succeeds' end context 'when not up-to-date with remote' do include_examples 'push succeeds' end end context 'when remote does not exist locally' do include_examples 'push succeeds' end end context 'when pushing to a protected branch' do let(:remote_ref) { 'protected' } context 'when force-pushing' do include_context 'force-pushing' context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push succeeds when up-to-date with remote' end context 'with ProtectedBranches disabled' do include_examples 'push always succeeds' end end context 'when deleting' do include_context 'deleting' context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push always fails' end context 'with ProtectedBranches disabled' do include_examples 'push always succeeds' end end context 'when not deleting or force-pushing' do context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push succeeds when up-to-date with remote' end context 'with ProtectedBranches disabled' do include_examples 'push succeeds when up-to-date with remote' end end end context 'when pushing to an unprotected branch' do let(:remote_ref) { 'unprotected' } context 'when force-pushing' do include_context 'force-pushing' context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push always succeeds' end context 'with ProtectedBranches disabled' do include_examples 'push always succeeds' end end context 'when deleting' do include_context 'deleting' context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push always succeeds' end context 'with ProtectedBranches disabled' do include_examples 'push always succeeds' end end context 'when not deleting or force-pushing' do context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push succeeds when up-to-date with remote' end context 'with ProtectedBranches disabled' do include_examples 'push succeeds when up-to-date with remote' end end end context 'when pushing to a nonexistent branch' do let(:remote_ref) { 'new-branch' } context 'when force-pushing' do include_context 'force-pushing' context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push succeeds' end context 'with ProtectedBranches disabled' do include_examples 'push succeeds' end end context 'when deleting' do include_context 'deleting' context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push fails' end context 'with ProtectedBranches disabled' do include_examples 'push fails' end end context 'when not deleting or force-pushing' do context 'with ProtectedBranches enabled' do include_context 'ProtectedBranches enabled' include_examples 'push succeeds' end context 'with ProtectedBranches disabled' do include_examples 'push succeeds' end end end end ================================================ FILE: spec/integration/resolving_cherry_pick_conflict_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'resolving cherry-pick conflicts' do subject { shell(%w[git commit -m Test -i some-file]) } let(:config) { <<-YML } PreCommit: TrailingWhitespace: enabled: true YML around do |example| repo do File.open('.overcommit.yml', 'w') { |f| f.write(config) } `git add .overcommit.yml` `git commit -m "Add Overcommit config"` echo('Master', 'some-file') `git add some-file` `git commit -m "Add some-file"` `git checkout -q -b branch1` echo('Branch 1 Addition', 'some-file') `git add some-file` `git commit -m "Add Branch 1 addition"` `git checkout -q master` `git checkout -q -b branch2` echo('Branch 2 Addition', 'some-file') `git add some-file` `git commit -m "Add Branch 2 addition"` `git checkout -q master` `git cherry-pick branch1 > #{File::NULL} 2>&1` `overcommit --install > #{File::NULL}` `git cherry-pick branch2 > #{File::NULL} 2>&1` # Results in cherry-pick conflict echo('Conflicts Resolved ', 'some-file') # Fail trailing whitespace hook `git add some-file` example.run end end it 'exits with a non-zero status' do skip 'Skipping flakey test on AppVeyor Windows builds' if ENV['APPVEYOR'] subject.status.should_not == 0 end it 'does not remove the CHERRY_PICK_HEAD file' do skip 'Skipping flakey test on AppVeyor Windows builds' if ENV['APPVEYOR'] subject Dir['.git/*'].should include '.git/CHERRY_PICK_HEAD' end it 'keeps the commit message from the cherry-picked commit' do skip 'Skipping flakey test on AppVeyor Windows builds' if ENV['APPVEYOR'] subject File.read(File.join('.git', 'MERGE_MSG')).should include 'Add Branch 2 addition' end end ================================================ FILE: spec/integration/resolving_merge_conflict_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'resolving merge conflicts' do subject { shell(%w[git commit -m Test -i some-file]) } around do |example| repo do echo('Master', 'some-file') `git add some-file` `git commit -m "Add some-file"` `git checkout -q -b branch1` echo('Branch 1 Addition', 'some-file') `git add some-file` `git commit -m "Add Branch 1 addition"` `git checkout -q master` `git checkout -q -b branch2` echo('Branch 2 Addition', 'some-file') `git add some-file` `git commit -m "Add Branch 2 addition"` `git checkout -q master` `git merge branch1` `git merge branch2` # Results in merge conflict `overcommit --install > #{File::NULL}` echo('Conflicts Resolved', 'some-file') `git add some-file` example.run end end it 'exits successfully' do subject.status.should == 0 end it 'does not display an error about MERGE_HEAD missing' do subject.stderr.should_not include 'MERGE_HEAD' end end ================================================ FILE: spec/integration/run_flag_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'overcommit --run' do subject { shell(%w[overcommit --run]) } context 'when using an existing pre-commit hook script' do if Overcommit::OS.windows? let(:script_name) { 'test-script.bat' } let(:script_contents) { 'exit 0' } else let(:script_name) { 'test-script' } let(:script_contents) { "#!/bin/bash\nexit 0" } end 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 ================================================ FILE: spec/integration/template_dir_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'fileutils' describe 'template directory' do let(:template_dir) { File.join(Overcommit::HOME, 'template-dir') } let(:hooks_dir) { File.join(template_dir, 'hooks') } it 'contains a hooks directory' do File.directory?(hooks_dir).should == true end describe 'the hooks directory' do it 'contains the master hook as an actual file with content' do master_hook = File.join(hooks_dir, 'overcommit-hook') File.exist?(master_hook).should == true File.size?(master_hook).should > 0 Overcommit::Utils::FileUtils.symlink?(master_hook).should == false end it 'contains all other hooks as copies of the master hook' do Overcommit::Utils.supported_hook_types.each do |hook_type| FileUtils.compare_file(File.join(hooks_dir, hook_type), File.join(hooks_dir, 'overcommit-hook')).should == true end end it 'contains no symlinks' do Overcommit::Utils.supported_hook_types.each do |hook_type| Overcommit::Utils::FileUtils.symlink?(File.join(hooks_dir, hook_type)).should == false end end end end ================================================ FILE: spec/overcommit/cli_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/cli' require 'overcommit/hook_context/diff' require 'overcommit/hook_context/run_all' describe Overcommit::CLI do describe '#run' do let(:logger) { Overcommit::Logger.silent } let(:input) { double('input') } let(:cli) { described_class.new(arguments, input, logger) } subject { cli.run } before do Overcommit::Utils.stub(:repo_root).and_return('current-dir') end context 'with no arguments' do let(:arguments) { [] } it 'attempts to install in the current directory' do Overcommit::Installer.any_instance. should_receive(:run). with('current-dir', hash_including(action: :install)) subject end end context 'with the --list-hooks option specified' do let(:arguments) { ['--list-hooks'] } let(:contexts) do Overcommit::ConfigurationLoader.new(logger).load_repo_config.all_hook_configs.keys end before { cli.stub(:halt) } it 'prints the installed hooks' do logger.should_receive(:log).at_least(contexts.count) subject end end context 'with the uninstall switch specified' do let(:arguments) { ['--uninstall'] } it 'uninstalls hooks from the current directory' do Overcommit::Installer.any_instance. should_receive(:run). with('current-dir', hash_including(action: :uninstall)) subject end context 'and an explicit target' do let(:arguments) { super() + ['target-dir'] } it 'uninstalls hooks from the target directory' do Overcommit::Installer.any_instance. should_receive(:run). with('target-dir', hash_including(action: :uninstall)) subject end end end context 'with the install switch specified' do let(:arguments) { ['--install'] } it 'installs hooks into the current directory' do Overcommit::Installer.any_instance. should_receive(:run). with('current-dir', hash_including(action: :install)) subject end context 'and an explicit target' do let(:arguments) { super() + ['target-dir'] } it 'installs hooks from the target directory' do Overcommit::Installer.any_instance. should_receive(:run). with('target-dir', hash_including(action: :install)) subject end end end context 'with the template directory switch specified' do let(:arguments) { ['--template-dir'] } before do cli.stub(:halt) end it 'prints the location of the template directory' do capture_stdout { subject }.chomp.should end_with 'template-dir' end end context 'with the run switch specified' do let(:arguments) { ['--run'] } let(:config) { Overcommit::ConfigurationLoader.default_configuration } before do cli.stub(:halt) end it 'creates a HookRunner with the run-all context' do Overcommit::HookRunner.should_receive(:new). with(config, logger, instance_of(Overcommit::HookContext::RunAll), instance_of(Overcommit::Printer)). and_call_original subject end it 'runs the HookRunner' do Overcommit::HookRunner.any_instance.should_receive(:run) 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 ================================================ FILE: spec/overcommit/command_splitter_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::CommandSplitter do describe '.execute' do let(:args_prefix) { %w[cmd] } let(:max_command_length) { 10 } let(:options) { { args: splittable_args } } subject { described_class.execute(args_prefix, options) } before do described_class.stub(:max_command_length).and_return(max_command_length) Overcommit::Subprocess.stub(:spawn). and_return(Overcommit::Subprocess::Result.new(0, 'output', 'error')) end context 'with no splittable arguments' do let(:splittable_args) { [] } it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::InvalidCommandArgs end end context 'with splittable arguments under the limit' do let(:splittable_args) { %w[1 2 3 4 5 6 7] } it 'executes one command' do Overcommit::Subprocess.should_receive(:spawn).once subject end end context 'with splittable arguments just over the limit' do let(:splittable_args) { %w[1 2 3 4 5 6 7 8] } it 'executes two commands with the appropriately split arguments' do Overcommit::Subprocess.should_receive(:spawn).with(%w[cmd 1 2 3 4 5 6 7], hash_excluding(:args)) Overcommit::Subprocess.should_receive(:spawn).with(%w[cmd 8], hash_excluding(:args)) subject end context 'when both commands return successfully' do it 'returns a successful result' do subject.should be_success subject.status.should == 0 end it 'returns concatenated output' do subject.stdout.should == 'output' * 2 subject.stderr.should == 'error' * 2 end end context 'when one command fails' do before do Overcommit::Subprocess.stub(:spawn). with(%w[cmd 8], anything). and_return(Overcommit::Subprocess::Result.new(2, 'whoa', 'bad error')) end it 'returns an unsuccessful result' do subject.should_not be_success subject.status.should == 1 end it 'returns concatenated output' do subject.stdout.should == 'outputwhoa' subject.stderr.should == 'errorbad error' end end end context 'with splittable arguments well over the limit' do let(:splittable_args) { Array.new(15) { |i| (i + 1).to_s } } it 'executes multiple commands with the appropriately split arguments' do Overcommit::Subprocess.should_receive(:spawn).with(%w[cmd 1 2 3 4 5 6 7], hash_excluding(:args)) Overcommit::Subprocess.should_receive(:spawn).with(%w[cmd 8 9 10 11], hash_excluding(:args)) Overcommit::Subprocess.should_receive(:spawn).with(%w[cmd 12 13 14], hash_excluding(:args)) Overcommit::Subprocess.should_receive(:spawn).with(%w[cmd 15], hash_excluding(:args)) subject end end context 'with a splittable argument that on its own exceeds the limit' do let(:splittable_args) { %w[1 2 ohmylookareallylongargument] } it 'executes no commands and raises an exception' do Overcommit::Subprocess.should_not_receive(:spawn) expect { subject }.to raise_error Overcommit::Exceptions::InvalidCommandArgs end end context 'with a command prefix that exceeds the limit' do let(:args_prefix) { %w[reallylong] } let(:splittable_args) { %w[1 2 3] } it 'executes no commands and raises an exception' do Overcommit::Subprocess.should_not_receive(:spawn) expect { subject }.to raise_error Overcommit::Exceptions::InvalidCommandArgs end end context 'with a standard input stream specified' do let(:max_command_length) { 5 } let(:args_prefix) { %w[cat] } let(:options) { { args: %w[- - -], input: 'Hello' } } it 'passes the same standard input to each command' do Overcommit::Subprocess.should_receive(:spawn).with(%w[cat - -], hash_including(input: 'Hello')) Overcommit::Subprocess.should_receive(:spawn).with(%w[cat -], hash_including(input: 'Hello')) subject end end end end ================================================ FILE: spec/overcommit/configuration_loader_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::ConfigurationLoader do let(:output) { StringIO.new } let(:logger) { Overcommit::Logger.new(output) } describe '#load_repo_config' do subject { described_class.new(logger).load_repo_config } context 'when repo does not contain a configuration file' do around do |example| repo do example.run end end it 'returns the default configuration' do subject.should == described_class.default_configuration end end context 'when repo contains a configuration file' do let(:config_contents) { <<-CFG } plugin_directory: 'some-directory' CFG around do |example| repo do File.open('.overcommit.yml', 'w') { |f| f.write(config_contents) } example.run end end it 'loads the file' do Overcommit::ConfigurationLoader.any_instance. should_receive(:load_file). with(File.expand_path('.overcommit.yml')) subject end it 'merges the loaded file with the default configuration' do subject.plugin_directory.should == File.expand_path('some-directory') end context 'and the configuration file contains a hook with no `enabled` option' do let(:config_contents) { <<-CFG } PreCommit: ScssLint: command: ['bundle', 'exec', 'scss-lint'] CFG it 'displays a warning' do subject output.string.should =~ /PreCommit::ScssLint.*not.*enabled/i 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 ================================================ FILE: spec/overcommit/configuration_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Configuration do let(:hash) { {} } let(:config) { described_class.new(hash) } describe '#new' do let(:internal_hash) { config.instance_variable_get(:@hash) } subject { config } context 'when no configuration exists for a hook type' do it 'creates sections for those hook types' do internal_hash.should have_key 'PreCommit' end it 'creates the special ALL section for the hook type' do internal_hash['PreCommit'].should have_key 'ALL' end end context 'when keys with empty values exist' do let(:hash) do { 'PreCommit' => { 'SomeHook' => nil }, } end it 'converts the values to empty hashes' do internal_hash['PreCommit']['SomeHook'].should == {} end end end describe '#plugin_directory' do let(:hash) { { 'plugin_directory' => 'some-directory' } } subject { config.plugin_directory } around do |example| repo do example.run end end it { should == File.expand_path('some-directory') } end describe '#enabled_builtin_hooks' do let(:hash) do { 'PreCommit' => { 'AuthorName' => nil, 'AuthorEmail' => { 'enabled' => false }, } } end let(:context) { double('context') } subject { config.enabled_builtin_hooks(context) } before do context.stub(hook_class_name: 'PreCommit', hook_type_name: 'pre_commit') end it 'excludes hooks that are not explicitly enabled' do subject.should_not include 'AuthorName' end end describe '#for_hook' do let(:hash) do { 'PreCommit' => { 'ALL' => { 'required' => false, }, 'SomeHook' => { 'enabled' => true, 'quiet' => false, } } } end subject { config.for_hook('SomeHook', 'PreCommit') } it 'returns the subset of the config for the specified hook' do subject['enabled'].should == true subject['quiet'].should == false end it 'merges the the hook config with the ALL section' do subject['required'].should == false end end describe '#merge' do let(:parent_config) { described_class.new(parent) } let(:child_config) { described_class.new(child) } subject { parent_config.merge(child_config) } context 'when parent and child are empty' do let(:parent) { {} } let(:child) { {} } it 'returns a config equivalent to both' do subject.should == parent_config subject.should == child_config end end context 'when parent and child are the same' do let(:parent) { child } let(:child) do { 'plugin_directory' => 'some-directory', 'pre-commit' => { 'SomeHook' => { 'enabled' => false, } }, } end it 'returns a config equivalent to both' do subject.should == parent_config subject.should == child_config end end context 'when parent item contains a hash' do let(:parent) { { 'PreCommit' => { 'SomeHook' => { 'some-value' => 1 } } } } context 'and child item contains a different hash under the same key' do let(:child) { { 'PreCommit' => { 'SomeOtherHook' => { 'something' => 2 } } } } it 'merges the hashes together' do subject.for_hook('SomeHook', 'PreCommit').should include('some-value' => 1) subject.for_hook('SomeOtherHook', 'PreCommit').should include('something' => 2) end end context 'and child item contains a hash under a different key' do let(:child) { { 'CommitMsg' => { 'SomeHook' => { 'some-value' => 2 } } } } it 'appends the item to the parent array' do subject.for_hook('SomeHook', 'PreCommit').should include('some-value' => 1) subject.for_hook('SomeHook', 'CommitMsg').should include('some-value' => 2) end end context 'and child item contains a hash under the ALL key' do let(:child) do { 'PreCommit' => { 'ALL' => { 'some-value' => 2 }, 'SomeOtherHook' => { 'some-value' => 3 }, }, } end it 'overrides the value in the parent item' do subject.for_hook('SomeHook', 'PreCommit').should include('some-value' => 2) end it 'does not override the value in other child items' do subject.for_hook('SomeOtherHook', 'PreCommit').should include('some-value' => 3) end context 'and the parent contains a hash under the ALL key' do let(:parent) do super().tap do |hash| hash['PreCommit']['ALL'] = { 'some-value' => 1 } end end it 'overrides the ALL value in the parent item' do subject.for_hook('SomeHook', 'PreCommit').should include('some-value' => 2) end it 'does not override the value in other child items' do subject.for_hook('SomeOtherHook', 'PreCommit').should include('some-value' => 3) end end end end context 'when parent item contains an array' do let(:parent) { { 'PreCommit' => { 'SomeHook' => { 'list' => [1, 2, 3] } } } } context 'and child item contains an array' do let(:child) { { 'PreCommit' => { 'SomeHook' => { 'list' => [4, 5] } } } } it 'overrides the value in the parent item' do subject.for_hook('SomeHook', 'PreCommit')['list'].should == [4, 5] end end context 'and child item contains a single item' do let(:child) { { 'PreCommit' => { 'SomeHook' => { 'list' => 4 } } } } it 'overrides the value in the parent item' do subject.for_hook('SomeHook', 'PreCommit')['list'].should == 4 end end end end describe '#apply_environment!' do let(:hash) { {} } let(:config) { described_class.new(hash) } let!(:old_config) { described_class.new(hash.dup) } let(:context) { double('context') } subject { config } before do context.stub(:hook_type_name).and_return('pre_commit') context.stub(:hook_class_name).and_return('PreCommit') config.apply_environment!(context, env) end context 'when no hooks are requested to be skipped' do let(:env) { {} } it 'does nothing to the configuration' do subject.should == old_config end end context 'when a non-existent hook is requested to be skipped' do let(:env) { { 'SKIP' => 'SomeMadeUpHook' } } it 'does nothing to the configuration' do subject.should == old_config end end context 'when an existing hook is requested to be skipped' do let(:env) { { 'SKIP' => 'AuthorName' } } it 'sets the skip option of the hook to true' do subject.for_hook('AuthorName', 'PreCommit')['skip'].should == true end context 'and the hook is spelt with underscores' do let(:env) { { 'SKIP' => 'author_name' } } it 'sets the skip option of the hook to true' do subject.for_hook('AuthorName', 'PreCommit')['skip'].should == true end end context 'and the hook is spelt with hyphens' do let(:env) { { 'SKIP' => 'author-name' } } it 'sets the skip option of the hook to true' do subject.for_hook('AuthorName', 'PreCommit')['skip'].should == true end end end context 'when the word "all" is included in the skip list' do let(:env) { { 'SKIP' => 'all' } } it 'sets the skip option of the ALL section to true' do subject.for_hook('ALL', 'PreCommit')['skip'].should == true end context 'and "all" is capitalized' do let(:env) { { 'SKIP' => 'ALL' } } it 'sets the skip option of the special ALL config to true' do subject.for_hook('ALL', 'PreCommit')['skip'].should == true end end end context 'when hooks are filtered using the ONLY environment variable' do let(:env) { { 'ONLY' => 'AuthorName' } } it 'sets the skip option of the ALL section to true' do subject.for_hook('ALL', 'PreCommit')['skip'].should == true end it 'sets the skip option of the filtered hook to false' do subject.for_hook('AuthorName', 'PreCommit')['skip'].should == false end end end end ================================================ FILE: spec/overcommit/configuration_validator_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::ConfigurationValidator do let(:output) { StringIO.new } let(:logger) { Overcommit::Logger.new(output) } let(:options) { { logger: logger } } let(:config) { Overcommit::Configuration.new(config_hash, validate: false) } subject { described_class.new.validate(config, config_hash, options) } context 'when hook has an invalid name' do let(:config_hash) do { 'PreCommit' => { 'My_Hook' => { 'enabled' => false, }, }, } end it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError end end context 'when hook has `env` set' do let(:config_hash) do { 'PreCommit' => { 'MyHook' => { 'enabled' => true, 'env' => env, }, }, } end context 'and it is a single string' do let(:env) { 'OVERCOMMIT_ENV_VAR=1' } it 'raises an error and mentions `env` must be a hash' do expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError output.string.should =~ /must be a hash/i end end context 'and it is a hash with string values' do let(:env) { { 'OVERCOMMIT_ENV_VAR' => '1', 'OVERCOMMIT_ENV_VAR_2' => '2' } } it 'is valid' do expect { subject }.not_to raise_error end end context 'and it is a hash with integer values' do let(:env) { { 'OVERCOMMIT_ENV_VAR' => 1, 'OVERCOMMIT_ENV_VAR_2' => 2 } } it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError output.string.should =~ /`OVERCOMMIT_ENV_VAR`.*must be a string/i output.string.should =~ /`OVERCOMMIT_ENV_VAR_2`.*must be a string/i end end context 'and it is a hash with boolean values' do let(:env) { { 'OVERCOMMIT_ENV_VAR' => true, 'OVERCOMMIT_ENV_VAR_2' => false } } it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError output.string.should =~ /`OVERCOMMIT_ENV_VAR`.*must be a string/i output.string.should =~ /`OVERCOMMIT_ENV_VAR_2`.*must be a string/i end end end context 'when hook has `processors` set' do let(:concurrency) { 4 } let(:config_hash) do { 'concurrency' => concurrency, 'PreCommit' => { 'MyHook' => { 'enabled' => true, 'processors' => processors, }, }, } end context 'and it is larger than `concurrency`' do let(:processors) { concurrency + 1 } it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::ConfigurationError end end context 'and it is equal to `concurrency`' do let(:processors) { concurrency } it 'is valid' do expect { subject }.not_to raise_error end end context 'and it is less than `concurrency`' do let(:processors) { concurrency - 1 } it 'is valid' do expect { subject }.not_to raise_error end end end end ================================================ FILE: spec/overcommit/default_configuration_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe 'default configuration' do default_config = begin YAML.load_file(Overcommit::ConfigurationLoader::DEFAULT_CONFIG_PATH, aliases: true).to_hash rescue ArgumentError YAML.load_file(Overcommit::ConfigurationLoader::DEFAULT_CONFIG_PATH).to_hash end Overcommit::Utils.supported_hook_types.each do |hook_type| hook_class = Overcommit::Utils.camel_case(hook_type) Dir[File.join(Overcommit::HOOK_DIRECTORY, hook_type.tr('-', '_'), '*')]. map { |hook_file| Overcommit::Utils.camel_case(File.basename(hook_file, '.rb')) }. each do |hook| next if hook == 'Base' context "for the #{hook} #{hook_type} hook" do it 'exists in config/default.yml' do default_config[hook_class][hook].should_not be_nil end it 'explicitly sets the enabled option' do # Use variable names so it reads nicer in the RSpec output hook_enabled_option_set = !default_config[hook_class][hook]['enabled'].nil? all_hook_enabled_option_set = !default_config[hook_class]['ALL']['enabled'].nil? (hook_enabled_option_set || all_hook_enabled_option_set).should == true end it 'defines a description' do default_config[hook_class][hook]['description'].should_not be_nil end end end end end ================================================ FILE: spec/overcommit/git_config_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::GitConfig do describe '.comment_character' do subject { described_class.comment_character } context 'with no configuration' do it 'should be "#"' do repo do `git config --local core.commentchar ""` expect(subject).to eq '#' end end end context 'with custom configuration' do it 'should be the configured character' do repo do `git config --local core.commentchar x` expect(subject).to eq 'x' end end end end describe '.hooks_path' do subject { described_class.hooks_path } context 'when not explicitly set' do around do |example| repo do example.run end end it 'returns the default hook path' do expect(subject).to eq File.expand_path(File.join('.git', 'hooks')) end end context 'when explicitly set to an empty string' do around do |example| repo do `git config --local core.hooksPath ""` example.run end end it 'returns the default hook path' do expect(subject).to eq File.expand_path(File.join('.git', 'hooks')) end end context 'when explicitly set to an absolute path' do around do |example| repo do `git config --local core.hooksPath /etc/hooks` example.run end end it 'returns the absolute path' do expect(subject).to eq File.absolute_path('/etc/hooks') end end context 'when explicitly set to a relative path' do around do |example| repo do `git config --local core.hooksPath my-hooks` example.run end end it 'returns the absolute path to the directory relative to the repo root' do expect(subject).to eq File.expand_path('my-hooks') end end context 'when explicitly set to a path starting with a tilde' do around do |example| repo do `git config --local core.hooksPath ~/my-hooks` example.run end end it 'returns the absolute path to the folder in the users home path' do expect(subject).to eq File.expand_path('~/my-hooks') expect(subject).not_to include('~') end end end end ================================================ FILE: spec/overcommit/git_repo_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::GitRepo do describe '.submodule_statuses' do let(:options) { {} } subject { described_class.submodule_statuses(options) } context 'when repo contains no submodules' do around do |example| repo do example.run end end it { should be_empty } end context 'when repo contains submodules' do around do |example| nested_submodule = repo do `git commit --allow-empty -m "Initial commit"` end submodule = repo do `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 -c protocol.file.allow=always submodule add #{submodule} sub 2>&1 > #{File::NULL}` example.run end end it 'returns the submodule statuses' do subject.map(&:path).should == ['sub'] end context 'when recursive flag is specified' do let(:options) { { recursive: true } } it 'returns submodule statuses including nested submodules' do subject.map(&:path).sort.should == ['sub', 'sub/nested-sub'] end end end end describe '.extract_modified_lines' do let(:file) { 'file.txt' } let(:options) { {} } subject { described_class.extract_modified_lines(file, options) } around do |example| repo do echo("Hello World\nHow are you?", file) `git add file.txt` `git commit -m "Initial commit"` example.run end end context 'when no lines were modified' do it { should be_empty } end context 'when lines were added' do before do echo('Hello Again', file, append: true) end it 'includes the added lines' do subject.to_a.should == [3] end end context 'when lines were removed' do before do echo('Hello World', file) end it { should be_empty } end end describe '.modified_files' do let(:options) { {} } subject { described_class.modified_files(options) } around do |example| repo do example.run end end context 'when `staged` option is set' do let(:options) { { staged: true } } context 'when files were added' do before do touch 'added.txt' `git add added.txt` end it { should == [File.expand_path('added.txt')] } end context 'when files were renamed' do before do touch 'file.txt' `git add file.txt` `git commit -m "Initial commit"` `git mv file.txt renamed.txt` end it { should == [File.expand_path('renamed.txt')] } end context 'when files were modified' do before do touch 'file.txt' `git add file.txt` `git commit -m "Initial commit"` echo('Modification', 'file.txt', append: true) `git add file.txt` end it { should == [File.expand_path('file.txt')] } end context 'when files were deleted' do before do touch 'file.txt' `git add file.txt` `git commit -m "Initial commit"` `git rm file.txt` end it { should == [] } end context 'when submodules were added' do let(:submodule) do repo do `git commit --allow-empty -m "Initial commit"` end end before do `git -c protocol.file.allow=always submodule add #{submodule} sub 2>&1 > #{File::NULL}` end it { should_not include File.expand_path('sub') } end end end describe '.list_files' do let(:paths) { [] } let(:options) { {} } subject { described_class.list_files(paths, options) } around do |example| repo do `git commit --allow-empty -m "Initial commit"` example.run end end context 'when path includes a submodule directory' do let(:submodule_dir) { 'sub-repo' } before do submodule = repo do `git commit --allow-empty -m "Submodule commit"` end `git -c protocol.file.allow=always submodule add \ #{submodule} #{submodule_dir} 2>&1 > #{File::NULL}` `git commit -m "Add submodule"` end it { should_not include(File.expand_path(submodule_dir)) } end context 'when listing contents of a directory' do let(:dir) { 'some-dir' } let(:paths) { [dir + File::SEPARATOR] } before do FileUtils.mkdir(dir) end context 'when directory is empty' do it { should be_empty } end context 'when directory contains a file' do let(:file) { "#{dir}/file" } before do touch(file) `git add "#{file}"` `git commit -m "Add file"` end context 'when path contains no spaces' do it { should include(File.expand_path(file)) } end context 'when path contains spaces' do let(:dir) { 'some dir' } it { should include(File.expand_path(file)) } end end end context 'when the git ls-tree command fails for whatever reason' do before do result = double('result', success?: false, statuses: [1], stdouts: '', stderrs: '') allow(Overcommit::Utils). to receive(:execute). with(%w[git ls-tree --name-only HEAD], args: []). and_return(result) end it 'raises' do expect { subject }.to raise_error Overcommit::Exceptions::Error end end end describe '.tracked?' do subject { described_class.tracked?(file) } around do |example| repo do touch 'untracked' touch 'tracked' `git add tracked` `git commit -m "Initial commit"` touch 'staged' `git add staged` example.run end end context 'when file is untracked' do let(:file) { 'untracked' } it { should == false } end context 'when file is committed' do let(:file) { 'tracked' } it { should == true } end context 'when file is staged' do let(:file) { 'staged' } it { should == true } end end describe '.all_files' do subject { described_class.all_files } let(:submodule) do repo do `git commit --allow-empty -m "Initial commit"` end end around do |example| repo do touch 'untracked' touch 'tracked' `git add tracked` `git commit -m "Initial commit"` `git -c protocol.file.allow=always submodule add #{submodule} sub 2>&1 > #{File::NULL}` touch 'staged' `git add staged` example.run end end it { should include(*%w[tracked staged].map { |file| File.expand_path(file) }) } it { should_not include File.expand_path('sub') } end describe '.initial_commit?' do subject { described_class.initial_commit? } context 'when there are no existing commits in the repository' do around do |example| repo do example.run end end it { should == true } end context 'when there are commits in the repository' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` example.run end end it { should == false } end end describe '.staged_submodule_removals' do subject { described_class.staged_submodule_removals } around do |example| submodule = repo do `git commit --allow-empty -m "Submodule commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub-repo 2>&1 > #{File::NULL}` `git commit -m "Initial commit"` example.run end end context 'when there are no submodule removals staged' do it { should be_empty } end context 'when there are submodule additions staged' do before do another_submodule = repo do `git commit --allow-empty -m "Another submodule"` end `git -c protocol.file.allow=always submodule add \ #{another_submodule} another-sub-repo 2>&1 > #{File::NULL}` end it { should be_empty } end context 'when there is one submodule removal staged' do before do `git rm sub-repo` end it 'returns the submodule that was removed' do subject.size.should == 1 subject.first.tap do |sub| sub.path.should == 'sub-repo' File.directory?(sub.url).should == true end end end context 'when there are multiple submodule removals staged' do before do another_submodule = repo do `git commit --allow-empty -m "Another submodule"` end `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` end it 'returns all submodules that were removed' do subject.size.should == 2 subject.map(&:path).sort.should == %w[sub-repo yet-another-sub-repo] end end end describe '.branches_containing_commit' do subject { described_class.branches_containing_commit(commit_ref) } around do |example| repo do `git checkout -b master > #{File::NULL} 2>&1` `git commit --allow-empty -m "Initial commit"` `git checkout -b topic > #{File::NULL} 2>&1` `git commit --allow-empty -m "Another commit"` example.run end end context 'when only one branch contains the commit' do let(:commit_ref) { 'topic' } it 'should return only that branch' do subject.size.should == 1 subject[0].should == 'topic' end end context 'when more than one branch contains the commit' do let(:commit_ref) { 'master' } it 'should return all branches containing the commit' do subject.size.should == 2 subject.sort.should == %w[master topic] end end context 'when no branches contain the commit' do let(:commit_ref) { 'HEAD' } before do `git checkout --detach > #{File::NULL} 2>&1` `git commit --allow-empty -m "Detached HEAD"` end it { should be_empty } end end end ================================================ FILE: spec/overcommit/hook/base_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::Base do let(:config) { double('config') } let(:context) { double('context') } let(:hook) { described_class.new(config, context) } describe '#run_and_transform' do let(:var_name) { 'OVERCOMMIT_TEST_HOOK_VAR' } let(:hook_config) { {} } before do config.stub(:for_hook).and_return(hook_config) hook.stub(:run) { ENV[var_name] == 'pass' ? :pass : :fail } end subject { hook.run_and_transform } context 'when no env configuration option is specified' do let(:hook_config) { {} } it 'does not modify the environment' do subject.first.should == :fail end end context 'when env configuration option is specified' do let(:hook_config) { { 'env' => { var_name => 'pass' } } } it 'modifies the environment' do subject.first.should == :pass end end end describe '#run?' do let(:modified_files) { [] } let(:hook_config) do { 'enabled' => enabled, 'requires_files' => requires_files, } end before do config.stub(:for_hook).and_return(hook_config) context.stub(:modified_files).and_return(modified_files) end subject { hook.run? } context 'enabled is true, requires_files is false, modified_files empty' do let(:enabled) { true } let(:requires_files) { false } it { subject.should == true } end context 'enabled is false, requires_files is false, modified_files empty' do let(:enabled) { false } let(:requires_files) { false } it { subject.should == false } end context 'enabled is true, requires_files is true, modified_files is not empty' do let(:enabled) { true } let(:requires_files) { true } let(:modified_files) { ['file1'] } it { subject.should == true } end context 'enabled is true, requires_files is false, modified_files is not empty' do let(:enabled) { true } let(:requires_files) { false } let(:modified_files) { ['file1'] } it { subject.should == true } end context 'with exclude_branches specified' do let(:current_branch) { 'test-branch' } let(:hook_config) do { 'enabled' => true, 'requires_files' => false, 'exclude_branches' => exclude_branches } end before do allow(Overcommit::GitRepo). to receive(:current_branch). and_return(current_branch) end context 'exclude_branches is nil' do let(:exclude_branches) { nil } it { subject.should == true } end context 'exact match between exclude_branches and current_branch' do let(:exclude_branches) { ['test-branch'] } it { subject.should == false } end context 'partial match between exclude_branches and current_branch' do let(:exclude_branches) { ['test-*'] } it { subject.should == false } end context 'non-match between exclude_branches and current_branch' do let(:exclude_branches) { ['no-test-*'] } it { subject.should == true } end end end context '#skip?' do before do config.stub(:for_hook).and_return(hook_config) end subject { hook.skip? } context 'with skip_if not specified' do let(:hook_config) do { 'skip' => skip } end context 'with skip true' do let(:skip) { true } it { subject.should == true } end context 'with skip false' do let(:skip) { false } it { subject.should == false } end end context 'with skip_if specified' do before do result = Overcommit::Subprocess::Result.new(success ? 0 : 1, '', '') allow(Overcommit::Utils).to receive(:execute).and_return(result) end let(:hook_config) do { 'skip' => skip, 'skip_if' => ['bash', '-c', '! which my-executable'] } end context 'with skip true and skip_if returning true' do let(:skip) { true } let(:success) { true } it { subject.should == true } end context 'with skip true and skip_if returning false' do let(:skip) { true } let(:success) { false } it { subject.should == true } end context 'with skip false and skip_if returning true' do let(:skip) { false } let(:success) { true } it { subject.should == true } end context 'with skip false and skip_if returning false' do let(:skip) { false } let(:success) { false } it { subject.should == false } end end end end ================================================ FILE: spec/overcommit/hook/commit_msg/capitalized_subject_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::CapitalizedSubject do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message_lines).and_return(commit_msg.split("\n")) context.stub(:empty_message?).and_return(commit_msg.empty?) end context 'when commit message is empty' do let(:commit_msg) { '' } it { should pass } end context 'when subject starts with a capital letter' do let(:commit_msg) { <<-MSG } Initial commit Mostly cats so far. MSG it { should pass } end context 'when subject starts with a utf-8 capital letter' do let(:commit_msg) { <<-MSG } Årsgång Mostly cats so far. MSG it { should pass } end context 'when subject starts with punctuation and a capital letter' do let(:commit_msg) { <<-MSG } "Initial" commit Mostly cats so far. MSG it { should pass } end context 'when subject starts with a lowercase letter' do let(:commit_msg) { <<-MSG } initial commit I forget about commit message standards and decide to not capitalize my subject. Still mostly cats so far. MSG it { should warn } end context 'when subject starts with a utf-8 lowercase letter' do let(:commit_msg) { <<-MSG } årsgång I forget about commit message standards and decide to not capitalize my subject. Still mostly cats so far. MSG it { should warn } end context 'when subject starts with punctuation and a lowercase letter' do let(:commit_msg) { <<-MSG } "initial" commit I forget about commit message standards and decide to not capitalize my subject. Still mostly cats so far. MSG it { should warn } end context 'when subject starts with special "fixup!" prefix' do let(:commit_msg) { <<-MSG } fixup! commit This was created by running git commit --fixup=... MSG it { should pass } end context 'when subject starts with special "squash!" prefix' do let(:commit_msg) { <<-MSG } squash! commit This was created by running git commit --squash=... MSG it { should pass } end context 'when first line of commit message is an empty line' do let(:commit_msg) { <<-MSG } There was no first line This is a mistake. MSG it { should pass } end end ================================================ FILE: spec/overcommit/hook/commit_msg/empty_message_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::EmptyMessage do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:empty_message?).and_return(commit_msg.strip.empty?) end context 'when commit message is empty' do let(:commit_msg) { '' } it { should fail_hook } end context 'when commit message contains only whitespace' do let(:commit_msg) { ' ' } it { should fail_hook } end context 'when commit message is not empty' do let(:commit_msg) { 'Some commit message' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/commit_msg/gerrit_change_id_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::GerritChangeId do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:commit_msg_file) { Tempfile.new('commit-msg') } before do commit_msg_file.write(commit_msg) commit_msg_file.close context.stub(:commit_message_file).and_return(commit_msg_file.path) end context 'when the commit message contains no Change-Id' do let(:commit_msg) { 'Add code to repo' } it { should pass } it 'adds a Change-Id to the commit message' do subject.run File.open(commit_msg_file.path, 'r').read.should =~ /Change-Id/ end end context 'when the commit message already contains a Change-Id' do let(:commit_msg) { "Add code to repo\n\nChange-Id: I9f2b5528fa20ac91a55bbe9371e76a12dd1cce11" } it { should pass } it 'does nothing' do before = File.open(commit_msg_file.path, 'r').read subject.run after = File.open(commit_msg_file.path, 'r').read before.should == after end end end ================================================ FILE: spec/overcommit/hook/commit_msg/hard_tabs_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::HardTabs do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message).and_return(commit_msg) context.stub(:empty_message?).and_return(commit_msg.empty?) end context 'when commit message is empty' do let(:commit_msg) { '' } it { should pass } end context 'when message contains hard tabs' do let(:commit_msg) { "This is a hard-tab\tcommit message" } it { should warn } end context 'when message does not contain hard tabs' do let(:commit_msg) { 'No hard tabs to be found' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/commit_msg/message_format_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::MessageFormat do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message_lines).and_return(commit_msg.lines.to_a) context.stub(:empty_message?).and_return(commit_msg.empty?) end context 'when pattern is empty' do let(:config) do super().merge(Overcommit::Configuration.new( 'CommitMsg' => { 'MessageFormat' => { 'pattern' => nil } } )) end let(:commit_msg) { 'Some Message' } it { should pass } end context 'when message does not match the pattern' do let(:commit_msg) { 'Some Message' } expected_message = [ 'Commit message pattern mismatch.', 'Expected : | | ', 'Sample : DEFECT-1234 | Refactored Onboarding flow | John Doe' ].join("\n") it { should fail_hook expected_message } end context 'when multiline message matches the pattern' do let(:config) do super().merge(Overcommit::Configuration.new( 'CommitMsg' => { 'MessageFormat' => { 'pattern' => '^Some .* Message$' } } )) end let(:commit_msg) { "Some \n multiline \n Message" } it { should pass } end end ================================================ FILE: spec/overcommit/hook/commit_msg/russian_novel_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::RussianNovel do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message_lines).and_return(commit_msg) end context 'when message contains fewer than 30 lines' do let(:commit_msg) { ['A single line'] * 10 } it { should pass } end context 'when message contains at least 30 lines' do let(:commit_msg) { ['A single line'] * 30 } it { should warn } end end ================================================ FILE: spec/overcommit/hook/commit_msg/single_line_subject_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::SingleLineSubject do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message_lines).and_return(commit_msg.split("\n")) context.stub(:empty_message?).and_return(commit_msg.empty?) end context 'when commit message is empty' do let(:commit_msg) { '' } it { should pass } end context 'when subject is separated from body by a blank line' do let(:commit_msg) { <<-MSG } Initial commit Mostly cats so far. MSG it { should pass } end context 'when subject is not kept to one line' do let(:commit_msg) { <<-MSG } Initial commit where I forget about commit message standards and decide to hard-wrap my subject Still mostly cats so far. MSG it { should warn } end end ================================================ FILE: spec/overcommit/hook/commit_msg/spell_check_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::SpellCheck do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:uncommented_commit_msg_file) subject.stub(:execute).and_return(result) end context 'when hunspell exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) end context 'with no misspellings' do before do result.stub(:stdout).and_return(<<-MSG) @(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3) * * * MSG end it { should pass } end context 'with misspellings' do context 'with suggestions' do before do result.stub(:stdout).and_return(<<-MSG) @(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3) * & msg 10 4: MSG, mag, ms, mg, meg, mtg, mug, mpg, mfg, ms g * MSG end it { should warn(/^Potential misspelling: \w+. Suggestions: .+$/) } end context 'with no suggestions' do before do result.stub(:stdout).and_return(<<-MSG) @(#) International Ispell Version 3.2.06 (but really Hunspell 1.3.3) * # supercalifragilisticexpialidocious 4 * MSG end it { should warn(/^Potential misspelling: \w+.$/) } end end end context 'when hunspell exits unsuccessfully' do let(:result) { double('result') } before do result.stub(success?: false, stderr: <<-MSG) Can't open affix or dictionary files for dictionary named "foo". MSG end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/commit_msg/text_width_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::TextWidth do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message_lines).and_return(commit_msg.lines.to_a) context.stub(:empty_message?).and_return(commit_msg.empty?) end context 'when commit message is empty' do let(:commit_msg) { '' } it { should pass } end context 'when subject is longer than 60 characters' do let(:commit_msg) { 'A' * 61 } it { should warn /subject/ } end context 'when subject is 60 characters or fewer' do let(:commit_msg) { 'A' * 60 } it { should pass } end context 'when subject starts with special "fixup!" and is longer than 60 characters' do let(:commit_msg) { 'fixup! ' + 'A' * 60 } it { should pass } end context 'when subject starts with special "squash!" and is longer than 60 characters' do let(:commit_msg) { 'squash! ' + 'A' * 60 } it { should pass } end context 'when the subject is 60 characters followed by a newline' do let(:commit_msg) { <<-MSG } This is 60 characters, or 61 if the newline is counted A reasonable line. MSG it { should pass } end context 'when a line in the message is 72 characters followed by a newline' do let(:commit_msg) { <<-MSG } Some summary This line has 72 characters, but with newline it has 73 characters That shouldn't be a problem. MSG it { should pass } end context 'when a line in the message is longer than 72 characters' do let(:commit_msg) { <<-MSG } Some summary This line is longer than 72 characters which is clearly be seen by count. MSG it { should warn('Line 3 of commit message has > 72 characters') } end context 'when all lines in the message are fewer than 72 characters' do let(:commit_msg) { <<-MSG } Some summary A reasonable line. Another reasonable line. MSG it { should pass } end context 'when subject and a line in the message is longer than the limits' do let(:commit_msg) { <<-MSG } A subject line that is way too long. A subject line that is way too long. A message line that is way too long. A message line that is way too long. MSG it { should warn /subject.*<= 60.*\n.*line 3.*> 72.*/im } end context 'when custom lengths are specified' do let(:config) do super().merge(Overcommit::Configuration.new( 'CommitMsg' => { 'TextWidth' => { 'max_subject_width' => 70, 'min_subject_width' => 4, 'max_body_width' => 80 } } )) end context 'when subject is longer than 70 characters' do let(:commit_msg) { 'A' * 71 } it { should warn /subject must be <= 70/ } end context 'when subject is less than 4 characters' do let(:commit_msg) { 'A' * 3 } it { should warn /subject must be >= 4/ } end context 'when subject is 70 characters or fewer' do let(:commit_msg) { 'A' * 70 } it { should pass } end context 'when a line in the message is longer than 80 characters' do let(:commit_msg) { <<-MSG } Some summary This line is longer than #{'A' * 80} characters. MSG it { should warn 'Line 3 of commit message has > 80 characters' } end context 'when all lines in the message are fewer than 80 characters' do let(:commit_msg) { <<-MSG } Some summary A reasonable line. Another reasonable line. MSG it { should pass } end context 'when subject and a line in the message is longer than the limits' do let(:commit_msg) { <<-MSG } A subject line that is way too long. A subject line that is way too long. This line is longer than #{'A' * 80} characters. MSG it { should warn /subject.*<= 70.*\n.*line 3.*> 80/im } end end end ================================================ FILE: spec/overcommit/hook/commit_msg/trailing_period_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::CommitMsg::TrailingPeriod do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do context.stub(:commit_message_lines).and_return(commit_msg.split("\n")) context.stub(:empty_message?).and_return(commit_msg.empty?) end context 'when commit message is empty' do let(:commit_msg) { '' } it { should pass } end context 'when subject contains a trailing period' do let(:commit_msg) { 'This subject has a period.' } it { should warn } end context 'when subject does not contain a trailing period' do let(:commit_msg) { 'This subject has no period' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/post_checkout/base_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::Base do let(:config) { double('config') } let(:context) { double('context') } let(:hook) { described_class.new(config, context) } let(:hook_config) { {} } before do config.stub(:for_hook).and_return(hook_config) end describe '#skip_file_checkout?' do subject { hook.skip_file_checkout? } context 'when skip_file_checkout is not set' do it { should == true } end context 'when skip_file_checkout is set to false' do let(:hook_config) { { 'skip_file_checkout' => false } } it { should == false } end context 'when skip_file_checkout is set to true' do let(:hook_config) { { 'skip_file_checkout' => true } } it { should == true } end end describe '#enabled?' do subject { hook.enabled? } shared_examples 'hook enabled' do |enabled, skip_file_checkout, file_checkout, expected| context "when enabled is set to #{enabled}" do context "when skip_file_checkout is set to #{skip_file_checkout}" do context "when file_checkout? is #{file_checkout}" do let(:hook_config) do { 'enabled' => enabled, 'skip_file_checkout' => skip_file_checkout } end before do context.stub(:file_checkout?).and_return(file_checkout) end it { should == expected } end end end end include_examples 'hook enabled', true, true, true, false include_examples 'hook enabled', true, true, false, true include_examples 'hook enabled', true, false, true, true include_examples 'hook enabled', true, false, false, true include_examples 'hook enabled', false, true, true, false include_examples 'hook enabled', false, true, false, false include_examples 'hook enabled', false, false, true, false include_examples 'hook enabled', false, false, false, false end end ================================================ FILE: spec/overcommit/hook/post_checkout/bower_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::BowerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bower install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bower install exits unsuccessfully' do before do result.stub(success?: false, stderr: normalize_indent(<<-OUT)) bower EMALFORMED Failed to read bower.json OUT end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_checkout/bundle_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::BundleInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bundle install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bundle install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Could not locate Gemfile or .bundle/ directory') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_checkout/composer_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::ComposerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when composer install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when composer install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Composer could not find a composer.json file') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_checkout/index_tags_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::IndexTags do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:execute_in_background) end it { should pass } end ================================================ FILE: spec/overcommit/hook/post_checkout/npm_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::NpmInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when npm install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when npm install exits unsuccessfully' do before do result.stub(success?: false, stderr: "npm ERR! install Couldn't read dependencies") end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_checkout/submodule_status_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::SubmoduleStatus do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:submodule_status) { double('submodule_status') } before do submodule_status.stub(:path).and_return('sub') subject.stub(:submodule_statuses).and_return([submodule_status]) end context 'when submodule is up to date' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: false) end it { should pass } end context 'when submodule is uninitialized' do before do submodule_status.stub(uninitialized?: true, outdated?: false, merge_conflict?: false) end it { should warn(/uninitialized/) } end context 'when submodule is outdated' do before do submodule_status.stub(uninitialized?: false, outdated?: true, merge_conflict?: false) end it { should warn(/out of date/) } end context 'when submodule has merge conflicts' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: true) end it { should warn(/merge conflicts/) } end end ================================================ FILE: spec/overcommit/hook/post_checkout/yarn_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCheckout::YarnInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when yarn install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when yarn install exits unsuccessfully' do before do result.stub(success?: false, stderr: %{error An unexpected error occurred: ...}) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_commit/bower_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::BowerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bower install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bower install exits unsuccessfully' do before do result.stub(success?: false, stderr: normalize_indent(<<-OUT)) bower EMALFORMED Failed to read bower.json OUT end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_commit/bundle_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::BundleInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bundle install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bundle install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Could not locate Gemfile or .bundle/ directory') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_commit/commitplease_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::Commitplease do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when commitplease exits successfully' do before do result.stub(:success?).and_return(true) result.stub(:stderr).and_return('') end it { should pass } end context 'when commitplease exits unsuccessfully' do before do result.stub(success?: false, stderr: normalize_indent(<<-OUT)) - First line must be (): Need an opening parenthesis: ( OUT end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_commit/composer_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::ComposerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when composer install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when composer install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Composer could not find a composer.json file') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_commit/git_guilt_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::GitGuilt do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when git-guilt exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(initial_commit?: false, execute: result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'with output' do before do result.stub(:stdout).and_return('GitGuilt Tester +++') end it { should warn } end end context 'when git-guilt exits unsuccessfully' do before do result = double('result') result.stub(success?: false, stderr: '') subject.stub(initial_commit?: false, execute: result) end it { should fail_hook } end context 'when there is no previous commit' do before do context.stub(:initial_commit?).and_return(true) end it { should pass } end end ================================================ FILE: spec/overcommit/hook/post_commit/index_tags_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::IndexTags do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:execute_in_background) end it { should pass } end ================================================ FILE: spec/overcommit/hook/post_commit/npm_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::NpmInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when npm install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when npm install exits unsuccessfully' do before do result.stub(success?: false, stderr: "npm ERR! install Couldn't read dependencies") end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_commit/submodule_status_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::SubmoduleStatus do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:submodule_status) { double('submodule_status') } before do submodule_status.stub(:path).and_return('sub') subject.stub(:submodule_statuses).and_return([submodule_status]) end context 'when submodule is up to date' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: false) end it { should pass } end context 'when submodule is uninitialized' do before do submodule_status.stub(uninitialized?: true, outdated?: false, merge_conflict?: false) end it { should warn(/uninitialized/) } end context 'when submodule is outdated' do before do submodule_status.stub(uninitialized?: false, outdated?: true, merge_conflict?: false) end it { should warn(/out of date/) } end context 'when submodule has merge conflicts' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: true) end it { should warn(/merge conflicts/) } end end ================================================ FILE: spec/overcommit/hook/post_commit/yarn_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostCommit::YarnInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when yarn install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when yarn install exits unsuccessfully' do before do result.stub(success?: false, stderr: %{error An unexpected error occurred: ...}) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_merge/bower_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::BowerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bower install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bower install exits unsuccessfully' do before do result.stub(success?: false, stderr: normalize_indent(<<-OUT)) bower EMALFORMED Failed to read bower.json OUT end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_merge/bundle_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::BundleInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bundle install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bundle install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Could not locate Gemfile or .bundle/ directory') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_merge/composer_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::ComposerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when composer install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when composer install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Composer could not find a composer.json file') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_merge/index_tags_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::IndexTags do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:execute_in_background) end it { should pass } end ================================================ FILE: spec/overcommit/hook/post_merge/npm_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::NpmInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when npm install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when npm install exits unsuccessfully' do before do result.stub(success?: false, stderr: "npm ERR! install Couldn't read dependencies") end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_merge/submodule_status_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::SubmoduleStatus do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:submodule_status) { double('submodule_status') } before do submodule_status.stub(:path).and_return('sub') subject.stub(:submodule_statuses).and_return([submodule_status]) end context 'when submodule is up to date' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: false) end it { should pass } end context 'when submodule is uninitialized' do before do submodule_status.stub(uninitialized?: true, outdated?: false, merge_conflict?: false) end it { should warn(/uninitialized/) } end context 'when submodule is outdated' do before do submodule_status.stub(uninitialized?: false, outdated?: true, merge_conflict?: false) end it { should warn(/out of date/) } end context 'when submodule has merge conflicts' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: true) end it { should warn(/merge conflicts/) } end end ================================================ FILE: spec/overcommit/hook/post_merge/yarn_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::YarnInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when yarn install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when yarn install exits unsuccessfully' do before do result.stub(success?: false, stderr: %{error An unexpected error occurred: ...}) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_rewrite/bower_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostRewrite::BowerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bower install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bower install exits unsuccessfully' do before do result.stub(success?: false, stderr: normalize_indent(<<-OUT)) bower EMALFORMED Failed to read bower.json OUT end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_rewrite/bundle_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostRewrite::BundleInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when bundle install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when bundle install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Could not locate Gemfile or .bundle/ directory') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_rewrite/composer_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostRewrite::ComposerInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when composer install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when composer install exits unsuccessfully' do before do result.stub(success?: false, stdout: 'Composer could not find a composer.json file') end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_rewrite/index_tags_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostMerge::IndexTags do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:execute_in_background) end it { should pass } end ================================================ FILE: spec/overcommit/hook/post_rewrite/npm_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostRewrite::NpmInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when npm install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when npm install exits unsuccessfully' do before do result.stub(success?: false, stderr: "npm ERR! install Couldn't read dependencies") end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/post_rewrite/submodule_status_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostRewrite::SubmoduleStatus do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:submodule_status) { double('submodule_status') } before do submodule_status.stub(:path).and_return('sub') subject.stub(:submodule_statuses).and_return([submodule_status]) end context 'when submodule is up to date' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: false) end it { should pass } end context 'when submodule is uninitialized' do before do submodule_status.stub(uninitialized?: true, outdated?: false, merge_conflict?: false) end it { should warn(/uninitialized/) } end context 'when submodule is outdated' do before do submodule_status.stub(uninitialized?: false, outdated?: true, merge_conflict?: false) end it { should warn(/out of date/) } end context 'when submodule has merge conflicts' do before do submodule_status.stub(uninitialized?: false, outdated?: false, merge_conflict?: true) end it { should warn(/merge conflicts/) } end end ================================================ FILE: spec/overcommit/hook/post_rewrite/yarn_install_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PostRewrite::YarnInstall do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'when yarn install exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when yarn install exits unsuccessfully' do before do result.stub(success?: false, stderr: %{error An unexpected error occurred: ...}) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/author_email_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::AuthorEmail do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } shared_examples_for 'author email check' do context 'when user has no email' do let(:email) { '' } it { should fail_hook } end context 'when user has an invalid email' do let(:email) { 'Invalid Email' } it { should fail_hook } end context 'when user has a valid email' do let(:email) { 'email@example.com' } it { should pass } end context 'when a custom pattern is specified' do let(:config) do super().merge(Overcommit::Configuration.new( 'PreCommit' => { 'AuthorEmail' => { 'pattern' => '^[^@]+@brigade\.com$' } } )) end context 'and the email does not match the pattern' do let(:email) { 'email@example.com' } it { should fail_hook } end context 'and the email matches the pattern' do let(:email) { 'email@brigade.com' } it { should pass } end end end context 'when email is set via config' do before do result.stub(:stdout).and_return(email) subject.stub(:execute).and_return(result) end it_should_behave_like 'author email check' end context 'when email is set via environment variable' do around do |example| Overcommit::Utils.with_environment 'GIT_AUTHOR_EMAIL' => email do example.run end end it_should_behave_like 'author email check' end end ================================================ FILE: spec/overcommit/hook/pre_commit/author_name_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::AuthorName do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } shared_examples_for 'author name check' do context 'when user has no name' do let(:name) { '' } it { should fail_hook } end context 'when user has only a first name' do let(:name) { 'John' } it { should pass } end context 'when user has first and last name' do let(:name) { 'John Doe' } it { should pass } end end context 'when name is set via config' do before do result.stub(:stdout).and_return(name) subject.stub(:execute).and_return(result) end it_should_behave_like 'author name check' end context 'when name is set via environment variable' do around do |example| Overcommit::Utils.with_environment 'GIT_AUTHOR_NAME' => name do example.run end end it_should_behave_like 'author name check' end end ================================================ FILE: spec/overcommit/hook/pre_commit/berksfile_check_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::BerksfileCheck do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when Berksfile.lock is ignored' do around do |example| repo do touch 'Berksfile.lock' echo('Berksfile.lock', '.gitignore') `git add .gitignore` `git commit -m "Ignore Berksfile.lock"` example.run end end it { should pass } end context 'when Berksfile.lock is not ignored' do let(:result) { double('result') } around do |example| repo do example.run end end before do result.stub(success?: success, stderr: 'Berkshelf error message') subject.stub(:execute).and_call_original subject.stub(:execute).with(%w[berks list --quiet]).and_return(result) end context 'and `berks list` exits unsuccessfully' do let(:success) { false } it { should fail_hook } end context 'and `berks list` exits successfully' do let(:success) { true } it { should pass } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/broken_symlinks_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::BrokenSymlinks do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:staged_file) { 'staged-file.txt' } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return([staged_file]) end before do Overcommit::Utils.stub(:broken_symlink?).with(staged_file).and_return(broken) end context 'when the symlink is broken' do let(:broken) { true } it { should fail_hook } end context 'when the symlink is not broken' do let(:broken) { false } it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/bundle_audit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::BundleAudit do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when Gemfile.lock is ignored' do around do |example| repo do touch 'Gemfile.lock' echo('Gemfile.lock', '.gitignore') `git add .gitignore` `git commit -m "Ignore Gemfile.lock"` example.run end end it { should pass } end context 'when Gemfile.lock is not ignored' do around do |example| repo do example.run end end before do subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard -- Gemfile.lock]). and_return(double(stdout: '')) subject.stub(:execute).with(%w[bundle-audit]).and_return(result) end context 'and it reports some outdated gems' do let(:result) do double( success?: false, stdout: <<-MSG Name: rest-client Version: 1.6.9 Advisory: CVE-2015-1820 Criticality: Unknown URL: https://github.com/rest-client/rest-client/issues/369 Title: rubygem-rest-client: session fixation vulnerability via Set-Cookie headers in 30x redirection responses Solution: upgrade to >= 1.8.0 Name: rest-client Version: 1.6.9 Advisory: CVE-2015-3448 Criticality: Unknown URL: http://www.osvdb.org/show/osvdb/117461 Title: Rest-Client Gem for Ruby logs password information in plaintext Solution: upgrade to >= 1.7.3 Vulnerabilities found! MSG ) end it { should warn } end let(:result) do double( success?: false, stdout: <<-MSG Insecure Source URI found: git://github.com/xxx/overcommit.git Vulnerabilities found! MSG ) end it { should warn } context 'and it reports bundle up to date' do let(:result) do double(success?: true, stdout: 'No vulnerabilities found') end it { should pass } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/bundle_check_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::BundleCheck do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when Gemfile.lock is ignored' do around do |example| repo do touch 'Gemfile.lock' echo('Gemfile.lock', '.gitignore') `git add .gitignore` `git commit -m "Ignore Gemfile.lock"` example.run end end it { should pass } end context 'when Gemfile.lock is not ignored' do let(:result) { double('result') } around do |example| repo do example.run end end before do result.stub(success?: success, stdout: 'Bundler error message') subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]). and_return(double(stdout: '')) subject.stub(:execute).with(%w[bundle check]).and_return(result) end context 'and bundle check exits unsuccessfully' do let(:success) { false } it { should fail_hook } end context 'and bundle check exits successfully' do let(:success) { true } it { should pass } context 'and there was a change to the Gemfile.lock' do before do subject.stub(:execute).with(%w[bundle check]) do echo('stuff', 'Gemfile.lock') double(success?: true) end end it { should fail_hook } end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/bundle_outdated_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::BundleOutdated do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when Gemfile.lock is ignored' do around do |example| repo do touch 'Gemfile.lock' echo('Gemfile.lock', '.gitignore') `git add .gitignore` `git commit -m "Ignore Gemfile.lock"` example.run end end it { should pass } end context 'when Gemfile.lock is not ignored' do around do |example| repo do example.run end end before do subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]). and_return(double(stdout: '')) subject.stub(:execute).with(%w[bundle outdated --strict --parseable]). and_return(result) end context 'and it reports some outdated gems' do let(:result) do double(stdout: <<-MSG Warning: the running version of Bundler is older than the version that created the lockfile. We suggest you upgrade to the latest version of Bundler by running `gem install bundler`. airbrake (newest 5.3.0, installed 5.2.3, requested ~> 5.0) aws-sdk (newest 2.3.3, installed 2.3.1, requested ~> 2) font-awesome-rails (newest 4.6.2.0, installed 4.6.1.0) mechanize (newest 2.7.4, installed 2.1.1) minimum-omniauth-scaffold (newest 0.4.3, installed 0.4.1) airbrake-ruby (newest 1.3.0, installed 1.2.4) aws-sdk-core (newest 2.3.3, installed 2.3.1) aws-sdk-resources (newest 2.3.3, installed 2.3.1) config (newest 1.1.1, installed 1.1.0) ruby_parser (newest 3.8.2, installed 3.8.1) MSG ) end it { should warn } end context 'and it reports bundle up to date' do let(:result) do double(stdout: <<-MSG Warning: the running version of Bundler is older than the version that created the lockfile. We suggest you upgrade to the latest version of Bundler by running `gem install bundler`. MSG ) end it { should pass } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/case_conflicts_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::CaseConflicts do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do Overcommit::GitRepo.stub(:initial_commit?).and_return(false) Overcommit::GitRepo.stub(:list_files).and_return(%w[foo]) end context 'when a new file conflicts with an existing file' do before do subject.stub(:applicable_files).and_return(%w[Foo]) end it { should fail_hook } end context 'when a new file conflicts with another new file' do before do subject.stub(:applicable_files).and_return(%w[bar Bar]) end it { should fail_hook } end context 'when there are no conflicts' do before do subject.stub(:applicable_files).and_return(%w[bar baz]) end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/chamber_compare_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ChamberCompare do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(['my_settings.yml']) end context 'when chamber exits successfully' do before do result = double('result') result.stub(:stdout).and_return('') subject.stub(:execute).and_return(result) end it { should pass } end context 'when chamber exits unsucessfully' do before do result = double('result') result.stub(:stdout).and_return('Some error message') subject.stub(:execute).and_return(result) end it { should warn } end end ================================================ FILE: spec/overcommit/hook/pre_commit/chamber_security_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ChamberSecurity do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(['my_settings.yml']) end context 'when chamber exits successfully' do before do result = double('result') result.stub(:stdout).and_return('') subject.stub(:execute).and_return(result) end it { should pass } end context 'when chamber exits unsucessfully' do before do result = double('result') result.stub(:stdout).and_return('Some error message') subject.stub(:execute).and_return(result) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/chamber_verification_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ChamberVerification do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(['my_settings.yml']) end context 'when chamber exits successfully' do before do result = double('result') result.stub(:stdout).and_return('') result.stub(:stderr).and_return('') subject.stub(:execute).and_return(result) end it { should pass } end context 'when chamber exits unsuccessfully but because of missing keys' do before do result = double('result') result.stub(:stdout).and_return('') result.stub(:stderr).and_return('no signature key was found') subject.stub(:execute).and_return(result) end it { should pass } end context 'when chamber exits unsuccessfully via standard out' do before do result = double('result') result.stub(:stdout).and_return('Some error message') result.stub(:stderr).and_return('') subject.stub(:execute).and_return(result) end it { should warn } end context 'when chamber exits unsuccessfully via standard error' do before do result = double('result') result.stub(:stdout).and_return('') result.stub(:stderr).and_return('Some error message') subject.stub(:execute).and_return(result) end it { should warn } end end ================================================ FILE: spec/overcommit/hook/pre_commit/code_spell_check_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::CodeSpellCheck do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when code-spell-check exists successfully' do before do result = double('result') result.stub(:success?).and_return(true) result.stub(:stdout).and_return('') result.stub(:stderr).and_return('') subject.stub(:execute).and_return(result) end it { should pass } end context 'when code-spell-check exists unsuccessfully via standard error' do before do result = double('result') result.stub(:success?).and_return(false) result.stub(:stdout).and_return('') result.stub(:stderr).and_return( "file1.rb:35: inkorrectspelling\n✗ Errors in code spellchecking" ) subject.stub(:execute).and_return(result) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/coffee_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::CoffeeLint 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.coffee file2.coffee]) end context 'when coffeelint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no warnings' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) path,lineNumber,lineNumberEnd,level,message OUT end it { should pass } end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) path,lineNumber,lineNumberEnd,level,message file1.coffee,31,,warn,Comprehensions must have parentheses around them OUT end it { should warn } end end context 'when coffeelint 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(normalize_indent(<<-OUT)) path,lineNumber,lineNumberEnd,level,message file1.coffee,17,,error,Duplicate key defined in object or class OUT end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/cook_style_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::CookStyle do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when cookstyle exits successfully' do let(:result) { double('result') } before do result.stub(success?: true, stderr: '', stdout: '') subject.stub(:execute).and_return(result) end it { should pass } context 'and it printed warnings to stderr' do before do result.stub(:stderr).and_return(normalize_indent(<<-MSG)) warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.8-compliant syntax, but you are running 2.1.1. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. MSG end it { should pass } end end context 'when cookstyle exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.rb:1:1: W: Useless assignment to variable - my_var.', ].join("\n")) result.stub(:stderr).and_return('') end it { should warn } context 'and it printed warnings to stderr' do before do result.stub(:stderr).and_return(normalize_indent(<<-MSG)) warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.8-compliant syntax, but you are running 2.1.1. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. MSG end it { should warn } end end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.rb:1:1: C: Missing top-level class documentation', ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } context 'and it printed warnings to stderr' do before do result.stub(:stderr).and_return(normalize_indent(<<-MSG)) warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.8-compliant syntax, but you are running 2.1.1. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. MSG end it { should fail_hook } end end context 'when a generic error message is written to stderr' do before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return([ 'Could not find cookstyle in any of the sources' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/credo_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Credo do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.ex file2.exs]) end context 'when credo exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when credo exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.ex:1:11: R: Modules should have a @moduledoc tag.', 'file2.ex:1:11: R: Modules should have a @moduledoc tag.' ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/css_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::CssLint 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.css file2.css]) end context 'when csslint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'and it reports a warning' do context 'with a line number' do before do result.stub(:stdout).and_return([ 'file1.css: line 1, col 5, Warning - Use of !important' ].join("\n")) end it { should warn } end context 'with no line number' do before do result.stub(:stdout).and_return([ 'file1.css: Warning - Too many !important declarations (10), try to use less than 10' ].join("\n")) end it { should warn } end end end context 'when csslint 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 context 'with a line number' do before do result.stub(:stdout).and_return([ 'file1.css: line 80, col 5, Error - Use of !important' ].join("\n")) end it { should fail_hook } end context 'with no line number' do before do result.stub(:stdout).and_return([ 'file1.css: Error - Currently no rules report a rollup error, but that may change' ].join("\n")) end it { should fail_hook } end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/dart_analyzer_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::DartAnalyzer do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.dart file2.dart]) end context 'when dartanalyzer exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when dartanalyzer exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'Analyzing file1.dart...', 'error • message_ommitted • lib/file1.dart:35:3 • rule', 'Analyzing file2.dart...', 'hint • message_ommitted • lib/file2.dart:100:13 • rule', 'info • message_ommitted • lib/file2.dart:113:16 • rule', '3 lints found.' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/dogma_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Dogma 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 dogma 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 dogma 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([ '27 files, 3 errors!', '', '== test/support/model_case.ex ==', '47: LineLength: Line length should not exceed 80 chars (was 85).', '', '== test/test_helper.exs ==', '6: TrailingBlankLines: Blank lines detected at end of file', '5: TrailingWhitespace: Trailing whitespace detected', '', ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/erb_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ErbLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.html.erb file2.html.erb]) end context 'when erblint exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when erblint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return(<<-MSG) Linting 1 files with 14 linters... erb interpolation with '<%= (...).html_safe %>' in this context is never safe In file: app/views/posts/show.html.erb:10 MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/es_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::EsLint 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.js file2.js]) end context 'when eslint is unable to run' do let(:result) { double('result') } before do result.stub(:stderr).and_return('SyntaxError: Use of const in strict mode.') result.stub(:stdout).and_return('') result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end it { should fail_hook } end context 'when eslint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.js: line 1, col 0, Warning - Missing "use strict" statement. (strict)', '', '1 problem' ].join("\n")) end it { should warn } end context 'and it doesnt count false positives error messages' do before do result.stub(:stdout).and_return([ '$ yarn eslint --quiet --format=compact /app/project/Error.ts', '$ /app/project/node_modules/.bin/eslint --quiet --format=compact /app/project/Error.ts', '', ].join("\n")) end it { should pass } end end context 'when eslint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.js: line 1, col 0, Error - Missing "use strict" statement. (strict)', '', '1 problem' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/execute_permissions_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ExecutePermissions do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'filename.txt' } def make_executable_and_add(file, exec_bit) if Overcommit::OS.windows? `git update-index --add --chmod=#{exec_bit ? '+' : '-'}x #{file}` else FileUtils.chmod(exec_bit ? 0o755 : 0o644, file) `git add #{file}` end end before do subject.stub(:applicable_files).and_return([staged_file]) end shared_examples_for 'a file permission hook' do context 'when file has execute permissions' do let(:exec_bit) { true } it { should fail_hook } end context 'when file does not have execute permissions' do let(:exec_bit) { false } it { should pass } end end context 'when initial commit' do around do |example| repo do touch staged_file make_executable_and_add(staged_file, exec_bit) example.run end end before do context.stub(:initial_commit?).and_return(true) end it_behaves_like 'a file permission hook' end context 'when not initial commit' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch staged_file make_executable_and_add(staged_file, exec_bit) example.run end end before do context.stub(:initial_commit?).and_return(false) end it_behaves_like 'a file permission hook' end end ================================================ FILE: spec/overcommit/hook/pre_commit/fasterer_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Fasterer do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:applicable_files) { %w[file1.rb file2.rb] } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(applicable_files) end around do |example| repo do example.run end end before do subject.stub(:execute).with(%w[fasterer], args: applicable_files).and_return(result) end context 'and has 2 suggestions for speed improvement' do let(:result) do double( success?: false, stdout: <<-MSG spec/models/product_spec.rb Using each_with_index is slower than while loop. Occurred at lines: 52. 1 files inspected, 1 offense detected spec/models/book_spec.rb Using each_with_index is slower than while loop. Occurred at lines: 32. 1 files inspected, 1 offense detected spec/models/blog_spec.rb Using each_with_index is slower than while loop. Occurred at lines: 12. 2 files inspected, 0 offense detected MSG ) end it { should warn } end context 'and has single suggestion for speed improvement' do let(:result) do double( success?: false, stdout: <<-MSG spec/models/product_spec.rb Using each_with_index is slower than while loop. Occurred at lines: 52. 1 files inspected, 1 offense detected MSG ) end it { should warn } end context 'and does not have any suggestion' do let(:result) do double(success?: true, stdout: '55 files inspected, 0 offenses detected') end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/file_size_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::FileSize do let(:config) do Overcommit::ConfigurationLoader.default_configuration.merge( Overcommit::Configuration.new( 'PreCommit' => { 'FileSize' => { 'size_limit_bytes' => 10 } } ) ) end let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'filename.txt' } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add "#{staged_file}" > #{File::NULL} 2>&1` example.run end end context 'when a big file is committed' do let(:contents) { 'longer than 10 bytes' } it { should fail_hook } end context 'when a small file is committed' do let(:contents) { 'short' } it { should pass } end context 'when a file is removed' do let(:contents) { 'anything' } before do `git commit -m "Add file"` `git rm "#{staged_file}"` end it 'should not raise an exception' do lambda { should pass }.should_not raise_error end end end ================================================ FILE: spec/overcommit/hook/pre_commit/fix_me_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::FixMe do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'filename.txt' } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add #{staged_file}` example.run end end context 'when file contains FIXME' do let(:contents) { 'eval(params[:q]) # FIXME maybe this is a bad idea?' } it { should warn } end context 'when file contains TODO with special chars around it' do let(:contents) { 'users = (1..1000).map { |i| User.find(1) } #TODO: make it better' } it { should warn } end context 'when file does not contain any FixMe words' do let(:contents) { 'if HACKY_CONSTANT.blank?' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/flay_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Flay do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:applicable_files) { %w[file1.rb] } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(applicable_files) end around do |example| repo do example.run end end before do command = %w[flay --mass 16 --fuzzy 1] subject.stub(:execute).with(command, args: applicable_files).and_return(result) end context 'flay discovered two issues' do let(:result) do double( success?: false, stdout: <<-MSG Total score (lower is better) = 268 1) IDENTICAL code found in :defn (mass*2 = 148) app/whatever11.rb:105 app/whatever12.rb:76 2) Similar code found in :defn (mass = 120) app/whatever21.rb:105 app/whatever22.rb:76 MSG ) end it { should fail_hook } end context 'flay discovered no issues' do let(:result) do double( success?: false, stdout: <<-MSG Total score (lower is better) = 0 MSG ) end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/foodcritic_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Foodcritic do let(:context) { double('context') } let(:result) { double(success?: true) } subject { described_class.new(config, context) } before do modified_files = applicable_files.map do |file| File.join(Overcommit::Utils.repo_root, file) end subject.stub(:applicable_files).and_return(modified_files) allow(subject).to receive(:execute).and_return(result) end around do |example| repo do example.run end end context 'when working in a single cookbook repository' do let(:config) { Overcommit::ConfigurationLoader.default_configuration } context 'and files have changed' do let(:applicable_files) do [ 'metadata.rb', File.join('recipes', 'default.rb'), ] end it 'passes the repository root as the cookbook path' do expect(subject).to receive(:execute). with(subject.command, hash_including(args: ['-B', Overcommit::Utils.repo_root])) subject.run end context 'and Foodcritic returns an unsuccessful exit status' do let(:result) do double( success?: false, stderr: '', stdout: <<-MSG, FC023: Prefer conditional attributes: recipes/default.rb:11 FC065: Ensure source_url is set in metadata: metadata.rb:1 MSG ) end it { should warn } end context 'and Foodcritic returns a successful exit status' do it { should pass } end end end context 'when working in a repository with many cookbooks' do let(:config) do Overcommit::ConfigurationLoader.default_configuration.merge( Overcommit::Configuration.new( 'PreCommit' => { 'Foodcritic' => { 'cookbooks_directory' => 'cookbooks', 'environments_directory' => 'environments', 'roles_directory' => 'roles', } } ) ) end context 'and multiple cookbooks, environments, and roles have changed' do let(:applicable_files) do [ File.join('cookbooks', 'cookbook_a', 'metadata.rb'), File.join('cookbooks', 'cookbook_b', 'metadata.rb'), File.join('environments', 'production.json'), File.join('environments', 'staging.json'), File.join('roles', 'role_a.json'), File.join('roles', 'role_b.json'), ] end it 'passes the modified cookbook, environment, and role paths' do expect(subject).to receive(:execute). with(subject.command, hash_including(args: [ '-B', File.join(Overcommit::Utils.repo_root, 'cookbooks', 'cookbook_a'), '-B', File.join(Overcommit::Utils.repo_root, 'cookbooks', 'cookbook_b'), '-E', File.join(Overcommit::Utils.repo_root, 'environments', 'production.json'), '-E', File.join(Overcommit::Utils.repo_root, 'environments', 'staging.json'), '-R', File.join(Overcommit::Utils.repo_root, 'roles', 'role_a.json'), '-R', File.join(Overcommit::Utils.repo_root, 'roles', 'role_b.json'), ])) subject.run end context 'and Foodcritic returns an unsuccessful exit status' do let(:result) do double( success?: false, stderr: '', stdout: <<-MSG, FC023: Prefer conditional attributes: cookbooks/cookbook_a/recipes/default.rb:11 FC065: Ensure source_url is set in metadata: cookbooks/cookbook_b/metadata.rb:1 MSG ) end it { should warn } end context 'and Foodcritic returns a successful exit status' do let(:result) { double(success?: true) } it { should pass } end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/forbidden_branches_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ForbiddenBranches do let(:default_config) { Overcommit::ConfigurationLoader.default_configuration } let(:branch_patterns) { ['master', 'release/*'] } let(:config) do default_config.merge( Overcommit::Configuration.new( 'PreCommit' => { 'ForbiddenBranches' => { 'branch_patterns' => branch_patterns } } ) ) end let(:context) { double('context') } subject { described_class.new(config, context) } around do |example| repo do `git checkout -b #{current_branch} > #{File::NULL} 2>&1` example.run end end context 'when committing to a permitted branch' do let(:current_branch) { 'permitted' } it { should pass } end context 'when committing to a forbidden branch' do context 'when branch name matches a forbidden branch exactly' do let(:current_branch) { 'master' } it { should fail_hook } end context 'when branch name matches a forbidden branch glob pattern' do let(:current_branch) { 'release/1.0' } it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/go_fmt_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::GoFmt do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:files) do %w[ pkg1/file1.go pkg1/file2.go pkg2/file1.go file1.go ] end before do subject.stub(:applicable_files).and_return(files) end context 'when go fmt exits successfully' do let(:result) { double('result') } before do result.stub(success?: true, stderr: '', stdout: '') subject.stub(:execute).and_return(result) end it 'executes go fmt for each file' do files.each do |file| expect(subject).to receive(:execute).with(subject.command, args: [file]).once end subject.run end it 'passes' do expect(subject).to pass end end context 'when go fmt exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'when go fmt returns an error to stdout' do let(:error_message) { 'some go fmt error' } before do result.stub(:stdout).and_return(error_message) result.stub(:stderr).and_return('') end it 'executes go fmt for each file' do files.each do |file| expect(subject).to receive(:execute).with(subject.command, args: [file]).once end subject.run end it 'fails' do expect(subject).to fail_hook end it 'returns errors' do message = subject.run.last expect(message).to eq Array.new(files.count, error_message).join("\n") end end context 'when fo fmt returns an error to stderr' do let(:error_message) { 'go: command not found' } before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return(error_message) end it 'executes go fmt for each file' do files.each do |file| expect(subject).to receive(:execute).with(subject.command, args: [file]).once end subject.run end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message).to eq Array.new(files.count, error_message).join("\n") end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/go_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::GoLint 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.go file2.go]) end context 'when golint exits successfully' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(stdout: '', stderr: '') end it { should pass } end context 'and it reports an error' do context 'on stdout' do before do result.stub( stderr: '', stdout: 'file1.go:1:1: error should be the last type when returning multiple items', ) end it { should fail_hook } end context 'on stderr' do before do result.stub( stdout: '', stderr: "file1.go:1:1: expected 'package', found 'IDENT' foo" ) end it { should fail_hook } end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/go_vet_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::GoVet 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.go file2.go]) end context 'when go vet 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 go vet exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'when go tool vet is not installed' do before do result.stub( stderr: 'go tool: no such tool "vet"; to install:', ) end it { should fail_hook /is not installed/ } end context 'and it reports an error' do before do result.stub( stderr: 'file1.go:1: possible formatting directive in Print call', ) end it { should fail_hook /formatting directive/ } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/golangci_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::GolangciLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:files) do %w[ pkg1/file1.go pkg1/file2.go pkg2/file1.go file1.go ] end let(:packages) { %w[pkg1 pkg2 .] } before do subject.stub(:applicable_files).and_return(files) end context 'when golangci-lint exits successfully' do let(:result) { double('result') } before do result.stub(success?: true, stderr: '', stdout: '') subject.stub(:execute).and_return(result) end it 'passes packages to golangci-lint' do expect(subject).to receive(:execute).with(subject.command, args: packages) subject.run end it 'passes' do expect(subject).to pass end end context 'when golangci-lint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'when golangci-lint returns an error' do let(:error_message) do 'pkg1/file1.go:8:6: exported type `Test` should have comment or be unexported (golint)' end before do result.stub(:stdout).and_return(error_message) result.stub(:stderr).and_return('') end it 'passes packages to golangci-lint' do expect(subject).to receive(:execute).with(subject.command, args: packages) subject.run end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message.file).to eq 'pkg1/file1.go' expect(message.line).to eq 8 expect(message.content).to eq error_message end end context 'when a generic error message is written to stderr' do let(:error_message) { 'golangci-lint: command not found' } before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return(error_message) end it 'passes packages to golangci-lint' do expect(subject).to receive(:execute).with(subject.command, args: packages) subject.run end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message).to eq error_message end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/hadolint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Hadolint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:applicable_files) { %w[Dockerfile Dockerfile.web] } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(applicable_files) end around do |example| repo do example.run end end before do subject.stub(:execute).with(%w[hadolint], args: Array(applicable_files.first)). and_return(result_dockerfile) subject.stub(:execute).with(%w[hadolint], args: Array(applicable_files.last)). and_return(result_dockerfile_web) end context 'and has 2 suggestions' do let(:result_dockerfile) do double( success?: false, stdout: <<-MSG Dockerfile:5 DL3015 Avoid additional packages by specifying `--no-install-recommends` MSG ) end let(:result_dockerfile_web) do double( success?: false, stdout: <<-MSG Dockerfile.web:13 DL3020 Use COPY instead of ADD for files and folders MSG ) end it { should fail_hook } end context 'and has single suggestion for double quote' do let(:result_dockerfile) do double( success?: false, stdout: <<-MSG Dockerfile:11 SC2086 Double quote to prevent globbing and word splitting. MSG ) end let(:result_dockerfile_web) do double(success?: true, stdout: '') end it { should fail_hook } end context 'and does not have any suggestion' do let(:result_dockerfile) do double(success?: true, stdout: '') end let(:result_dockerfile_web) do double(success?: true, stdout: '') end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/haml_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::HamlLint 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.haml file2.haml]) end context 'when haml-lint 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 haml-lint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.haml:1 [W] Prefer single quoted strings', ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.haml:1 [E] Unbalanced brackets.', ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/hard_tabs_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::HardTabs do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'filename.txt' } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add #{staged_file}` example.run end end context 'when file contains hard tabs' do let(:contents) { "Some\thard\ttabs" } it { should fail_hook } end context 'when file has no hard tabs' do let(:contents) { 'Just some text' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/hlint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Hlint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:applicable_files).and_return(%w[file1.hs file2.hs]) subject.stub(:execute).and_return(result) end context 'when hlint exits successfully' do before do result.stub(success?: true, stdout: '') end it { should pass } end context 'when hlint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.hs:22:16: Warning: Use const Found: \\ _ -> False Why not: const False OUT end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.hs:22:5: Error: Redundant lambda Found: nameHack = \\ _ -> Nothing Why not: nameHack _ = Nothing OUT end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/html_hint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::HtmlHint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.html file2.html]) end context 'when htmlhint exits successfully' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'with no errors' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.html:', "\tline 355, col 520: \e[31mId redefinition of [ stats ].\e[39m", '', '', '1 problem.' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/html_tidy_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::HtmlTidy do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.html file2.html]) end context 'when tidy exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no errors' do before do result.stub(:stderr).and_return('') end it { should pass } end end context 'when tidy exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stderr).and_return([ 'line 4 column 24 - Warning: proprietary attribute "class"' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stderr).and_return([ 'line 1 column 1 - Error: is not recognized!' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/image_optim_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ImageOptim 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.jpg file2.png]) end context 'when image_optim exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'and images were optimized' do before do result.stub(:stdout).and_return([ '32.30% 1.2K file1.jpg', 'Total: 32.30% 1.2K', ].join("\n")) end it { should fail_hook } end context 'and no images were optimized' do before do result.stub(:stdout).and_return([ '------ app/assets/images/favicons/favicon-96x96.png', 'Total: ------', ].join("\n")) end it { should pass } end end context 'when image_optim exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) result.stub(:stdout).and_return('An error occurred') result.stub(:stderr).and_return('') subject.stub(:execute).and_return(result) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/java_checkstyle_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::JavaCheckstyle 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.java file2.java]) end context 'when checkstyle exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no errors or warnings' do before do result.stub(:stdout).and_return([ 'Starting audit...', 'Audit done.', ].join("\n")) end it { should pass } end end context 'when checkstyle exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a message with no severity tag' do before do result.stub(:stdout).and_return([ 'Starting audit...', 'file1.java:1: Missing a Javadoc comment.', 'Audit done.' ].join("\n")) end it { should fail_hook } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'Starting audit...', '[ERROR] file1.java:1: Missing a Javadoc comment.', 'Audit done.' ].join("\n")) end it { should fail_hook } end context 'and it reports an warning' do before do result.stub(:stdout).and_return([ 'Starting audit...', '[WARN] file1.java:1: Missing a Javadoc comment.', 'Audit done.' ].join("\n")) end it { should warn } end context 'and it reports an info message' do before do result.stub(:stdout).and_return([ 'Starting audit...', '[INFO] file1.java:1: Missing a Javadoc comment.', 'Audit done.' ].join("\n")) end it { should warn } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/js_hint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::JsHint 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.js file2.js]) end context 'when jshint exits successfully' do before do result = double('result') result.stub(success?: true, stdout: '') subject.stub(:execute).and_return(result) end it { should pass } end context 'when jshint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.js: line 1, col 0, Missing semicolon. (W033)', '', '1 error' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.js: line 1, col 0, Missing "use strict" statement. (E007)', '', '1 error' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/js_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::JsLint 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.js file2.js]) end context 'when jslint 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 jslint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ "file1.js:1:3: Expected ']' at column 9, not column 3." ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/jscs_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Jscs 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.js file2.js]) end context 'when no configuration is found' do before do result = double('result') result.stub(success?: false, status: 4, stdout: '', stderr: 'Configuration file some-path/.jscs.json was not found.') subject.stub(:execute).and_return(result) end it { should fail_hook } end context 'when jscs exits unsuccessfully with status code 2' do let(:result) { double('result') } before do result.stub(success?: false, stderr: '', status: 2) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.js: line 1, col 4, ruleName: Missing space after `if` keyword' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/jsl_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Jsl 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.js file2.js]) end context 'when jsl 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 jsl exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.js(1): lint warning: meaningless block; curly braces have no impact' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.js(1): SyntaxError: invalid label' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/json_syntax_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'json' describe Overcommit::Hook::PreCommit::JsonSyntax do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:staged_file) { 'my_file.json' } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do touch staged_file `git add #{staged_file}` example.run end end context 'when JSON files have no errors' do before do JSON.stub(:parse) end it { should pass } end context 'when JSON file has errors' do before do JSON.stub(:parse).and_raise(JSON::ParserError) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/kt_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::KtLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.kt file2.kt]) end context 'when KtLint exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when KtLint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.kt:12:10: error message' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/license_finder_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::LicenseFinder do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when license_finder exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when license_finder runs unsucessfully' do before do result = double('result') result.stub(:success?).and_return(false) result.stub(:stdout).and_return('Some error message') result.stub(:stderr).and_return('') subject.stub(:execute).and_return(result) end it { should fail_hook 'Some error message' } end end ================================================ FILE: spec/overcommit/hook/pre_commit/license_header_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::LicenseHeader do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } around do |example| repo do example.run end end context 'when license file is missing' do it { should fail_hook } end context 'when license file exists' do let(:license_contents) { <<-LICENSE } Look at me I'm a license LICENSE let(:file) { 'some-file.txt' } before do File.open('LICENSE.txt', 'w') { |f| f.write(license_contents) } subject.stub(:applicable_files).and_return([file]) end context 'when all files contain the license header' do before do File.open(file, 'w') do |f| license_contents.split("\n").each do |line| f.puts("// #{line}") end f.write('And some text') end end it { should pass } end context 'when a file is missing a license header' do before do File.open(file, 'w') { |f| f.write('Some text without a license') } end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/line_endings_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::LineEndings do let(:config) do Overcommit::ConfigurationLoader.default_configuration.merge( Overcommit::Configuration.new( 'PreCommit' => { 'LineEndings' => { 'eol' => eol } } ) ) end let(:context) { double('context') } subject { described_class.new(config, context) } let(:eol) { "\n" } let(:staged_file) { 'filename.txt' } before do skip('Skip LineEndings tests for Git < 2.10') if Overcommit::GIT_VERSION < '2.10' subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add "#{staged_file}" > #{File::NULL} 2>&1` example.run end end context 'when file path contains spaces' do let!(:staged_file) { 'a file with spaces.txt' } let(:contents) { "test\n" } it { should_not fail_hook } end context 'when enforcing \n' do context 'when file contains \r\n line endings' do let(:contents) { "CR-LF\r\nline\r\nendings\r\n" } it { should fail_hook } end context 'when file contains \n endings' do let(:contents) { "LF\nline\nendings\n" } it { should pass } end end context 'when enforcing \r\n' do let(:eol) { "\r\n" } context 'when file contains \r\n line endings' do let(:contents) { "CR-LF\r\nline\r\nendings\r\n" } it { should pass } end context 'when file contains \n line endings' do let(:contents) { "LF\nline\nendings\n" } it { should fail_hook } end end unless Overcommit::OS.windows? context 'when attempting to check a binary file' do let(:contents) { "\xFF\xD8\xFF\xE0\u0000\u0010JFIF" } it { should warn } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/local_paths_in_gemfile_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::LocalPathsInGemfile do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:staged_file) { 'Gemfile' } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add #{staged_file}` example.run end end context 'when file contains a local path in Ruby 1.8 hash syntax format' do let(:contents) { "gem 'fuubar', :path => '../fuubar'" } it { should warn } end context 'when file contains a local path on its own line in Ruby 1.8 hash syntax format' do let(:contents) { ":path => '../fuubar'" } it { should warn } end context 'when file contains a local path starting with leading spaces in Ruby 1.8 hash format' do let(:contents) { " :path => '../fuubar'" } it { should warn } end context 'when file contains a local path in Ruby 1.9 hash syntax format' do let(:contents) { "gem 'fuubar', path: '../fuubar'" } it { should warn } end context 'when file contains local path on its own line in Ruby 1.9 hash syntax format' do let(:contents) { "path: '../fuubar'" } it { should warn } end context 'when file contains local path starting with leading spaces in Ruby 1.9 hash format' do let(:contents) { " path: '../fuubar'" } it { should warn } end context 'when the file does not contain a local path' do let(:contents) { "gem 'fuubar'" } it { should pass } end context 'when the file contains local paths, but only in comments' do let(:contents) do [ "# gem 'fuubar', :path => '../fuubar'", "# :path => '../fuubar'", "# gem 'fuubar', path: '../fuubar'", "# path: '../fuubar'", ].join("\n") end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/mdl_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Mdl do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do result.stub(success?: success, stdout: stdout, stderr: stderr) subject.stub(:applicable_files).and_return(%w[file1.md file2.md]) subject.stub(:execute).and_return(result) end context 'when mdl exits successfully' do let(:success) { true } let(:stdout) { '' } let(:stderr) { '' } it { should pass } end context 'when mdl exits unsuccessfully' do let(:success) { false } let(:message) { subject.run.last } context 'and it reports an error' do let(:stdout) do '[{"filename":"file1.md","line":1,"rule":"MD013","aliases":["line-length"],'\ '"description":"Line length"}]' end let(:stderr) { '' } it { should fail_hook } it { expect(message.file).to eq 'file1.md' } it { expect(message.line).to eq 1 } it { expect(message.content).to eq 'file1.md:1 MD013 Line length' } end context 'when there is an error running mdl' do let(:stdout) { '' } let(:stderr) { 'Some runtime error' } it { should fail_hook } it { expect(message).to eq 'Some runtime error' } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/merge_conflicts_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::MergeConflicts do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:staged_file) { 'filename.txt' } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add #{staged_file}` example.run end end context 'when file contains a merge conflict marker' do let(:contents) { "Just\n<<<<<<< HEAD:filename.txt\nconflicting text" } it { should fail_hook } end context 'when file does not have any merge conflict markers' do let(:contents) { 'Just some text' } it { should pass } end context "when file contains characters that aren't conflict markers" do let(:contents) { 'Just some <<<<<<< arrows' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/mix_format_spec.rb ================================================ # 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 ================================================ FILE: spec/overcommit/hook/pre_commit/nginx_test_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::NginxTest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:applicable_files).and_return(%w[nginx.conf]) subject.stub(:execute).and_return(result) end context 'when nginx -t exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when nginx -t exits unsuccessfully' do let(:result) { double('result') } before do result.stub(success?: false, stderr: normalize_indent(<<-OUT)) nginx: [emerg] unknown directive "erver" in nginx.conf:2 nginx: configuration file nginx.conf test failed OUT end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/pep257_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pep257 do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when pep257 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 pep257 exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stderr).and_return([ 'file1.py:1 in public method `foo`:', ' D102: Docstring missing' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/pep8_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pep8 do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when pep8 exits successfully' do before do result = double('result') result.stub(success?: true, stdout: '') subject.stub(:execute).and_return(result) end it { should pass } end context 'when pep8 exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.py:1:1: W391 blank line at end of file' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.py:1:80: E501 line too long (80 > 79 characters)' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/php_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PhpLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(['sample.php']) end context 'when php lint exits successfully' do before do result = double('result') result.stub(:status).and_return(0) result.stub(:success?).and_return(true) result.stub(:stdout).and_return('No syntax errors detected in sample.php') subject.stub(:execute).and_return(result) end it { should pass } end context 'when php lint exits unsuccessfully' do before do # php -l prints the same to both stdout and stderr # rubocop:disable Layout/LineLength sample_output = [ '', "Parse error: syntax error, unexpected '0' (T_LNUMBER), expecting variable (T_VARIABLE) or '{' or '$' in sample.php on line 3 ", 'Errors parsing invalid.php', ].join("\n") # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(255) result.stub(:success?).and_return(false) result.stub(:stdout).and_return(sample_output) result.stub(:stderr).and_return(sample_output) subject.stub(:execute).and_return(result) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/php_stan_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PhpStan do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[sample.php]) end context 'when phpstan exits successfully' do before do sample_output = '' result = double('result') result.stub(:success?).and_return(true) result.stub(:stdout).and_return(sample_output) result.stub(:status).and_return(0) subject.stub(:execute).and_return(result) end it { should pass } end context 'when phpstan exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) result.stub(:status).and_return(2) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do sample_output = [ '/sample1.php:14:Call to an undefined static method Sample1::where()', '/sample2.php:17:Anonymous function has an unused use $myVariable.' ].join("\n") result.stub(:stdout).and_return(sample_output) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/phpcs_fixer_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PhpCsFixer do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[sample.php]) end context 'when phpcs fixer exits successfully with fixed file' do before do # rubocop:disable Layout/LineLength sample_output = [ 'Loaded config default.', 'Using cache file ".php_cs.cache".', 'F', 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error', ' 1) foo/fixable.php (braces)', '', 'Fixed all files in 0.001 seconds, 10.000 MB memory used', '', ].join("\n") # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(0) result.stub(:success?).and_return(true) result.stub(:stdout).and_return(sample_output) subject.stub(:execute).and_return(result) end it { should warn } end context 'when phpcs fixer exits successfully with no file to fix' do before do # rubocop:disable Layout/LineLength sample_output = [ 'Loaded config default.', 'Using cache file ".php_cs.cache".', 'S', 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error', '', ].join("\n") # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(0) result.stub(:success?).and_return(true) result.stub(:stdout).and_return(sample_output) subject.stub(:execute).and_return(result) end it { should pass } end context 'when phpcs exits unsuccessfully' do before do # rubocop:disable Layout/LineLength sample_output = [ 'Loaded config default.', 'Using cache file ".php_cs.cache".', 'I', 'Legend: ?-unknown, I-invalid file syntax, file ignored, S-Skipped, .-no changes, F-fixed, E-error', 'Fixed all files in 0.001 seconds, 10.000 MB memory used', '', 'Files that were not fixed due to errors reported during linting before fixing:', ' 1) /home/damien/Code/Rezdy/php/foo/broken.php', '', ].join("\n") # rubocop:enable Layout/LineLength result = double('result') result.stub(:status).and_return(1) result.stub(:success?).and_return(false) result.stub(:stdout).and_return(sample_output) result.stub(:stderr).and_return(sample_output) subject.stub(:execute).and_return(result) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/phpcs_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PhpCs do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[sample.php]) end context 'when phpcs exits successfully' do before do sample_output = [ 'File,Line,Column,Type,Message,Source,Severity,Fixable', '' ].join("\n") result = double('result') result.stub(:success?).and_return(true) result.stub(:stdout).and_return(sample_output) result.stub(:status).and_return(0) subject.stub(:execute).and_return(result) end it { should pass } end context 'when phpcs exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) result.stub(:status).and_return(2) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do # rubocop:disable Layout/LineLength sample_output = [ 'File,Line,Column,Type,Message,Source,Severity,Fixable', '"/Users/craig/HelpScout/overcommit-testing/invalid.php",5,1,warning,"Possible parse error: FOREACH has no AS statement",Squiz.ControlStructures.ForEachLoopDeclaration.MissingAs,5,0' ].join("\n") # rubocop:enable Layout/LineLength result.stub(:stdout).and_return(sample_output) end it { should warn } end context 'and it reports an error' do before do # rubocop:disable Layout/LineLength sample_output = [ 'File,Line,Column,Type,Message,Source,Severity,Fixable', '"/Users/craig/HelpScout/overcommit-testing/invalid.php",5,1,error,"Inline control structures are not allowed",Generic.ControlStructures.InlineControlStructure.NotAllowed,5,1' ].join("\n") # rubocop:enable Layout/LineLength result.stub(:stdout).and_return(sample_output) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/pronto_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pronto do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when pronto exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when pronto exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stderr).and_return('') result.stub(:stdout).and_return([ 'file2.rb:10 E: IDENTICAL code found in :iter.', ].join("\n")) end it { should fail_hook } end context 'and it reports a warning' do before do result.stub(:stderr).and_return('') result.stub(:stdout).and_return <<~MESSAGE Running Pronto::Rubocop file1.rb:12 W: Line is too long. [107/80] file2.rb:14 I: Prefer single-quoted strings ```suggestion x = 'x' ``` MESSAGE end it { should warn } end context 'and it has a generic error message written to stderr' do before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return([ 'Could not find pronto in any of the sources' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/puppet_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PuppetLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.pp file2.pp]) end context 'when puppet-lint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.pp:2:10:WARNING: selector inside resource block (selector_inside_resource)' OUT end it { should warn } end end context 'when puppet-lint exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.pp:2:10:WARNING: selector inside resource block (selector_inside_resource)' OUT end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.pp:2:10:ERROR: trailing whitespace (trailing_whitespace)' OUT end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/puppet_metadata_json_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PuppetMetadataJsonLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.pp file2.pp metadata.json]) end context 'when metadata-json-lint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) (WARN) requirements: The 'pe' requirement is no longer supported by the Forge. OUT end it { should warn } end end context 'when metadata-json-lint exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) (WARN) requirements: The 'pe' requirement is no longer supported by the Forge. OUT end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) (ERR) requirements: The 'pe' requirement is no longer supported by the Forge. OUT end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/pycodestyle_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pycodestyle do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when pycodestyle exits successfully' do before do result = double('result') result.stub(success?: true, stdout: '') subject.stub(:execute).and_return(result) end it { should pass } end context 'when pycodestyle exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.py:1:1: W391 blank line at end of file' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.py:1:80: E501 line too long (80 > 79 characters)' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/pydocstyle_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pydocstyle do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when pydocstyle exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when pydocstyle exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stderr).and_return([ 'file1.py:1 in public method `foo`:', ' D102: Docstring missing' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/pyflakes_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pyflakes do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when pyflakes exits successfully' do before do result = double('result') result.stub(success?: true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when pyflakes exits unsucessfully' do let(:result) { double('result') } before do result.stub(success?: false, stdout: '', stderr: '') subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ "file1.py:1: local variable 'x' is assigned to but never used" ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stderr).and_return([ 'file1.py:1:1: invalid syntax' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/pylint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Pylint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when pylint 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 pylint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.py:2:C: Missing function docstring (missing-docstring)' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ "file1.py:2:E: Instance of 'Foo' has no 'bar' member (no-member)" ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/python_flake8_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::PythonFlake8 do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.py file2.py]) end context 'when flake8 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 flake8 exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.py:1:1: W292 no newline at end of file' ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.py:2:13: F812 list comprehension redefines name from line 1' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/r_spec_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RSpec do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when rspec exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } it { expect(subject).to receive(:execute).with(['rspec']).and_return(result) subject.run } end context 'with included files set' do let(:result) { double('result') } let(:config) do super().merge(Overcommit::Configuration.new( 'PreCommit' => { 'RSpec' => { 'include' => ['**/*_spec.rb'], } } )) end let(:context) { double('context') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) subject.stub(:applicable_files).and_return('spec/test_spec.rb') end it { should pass } it { expect(subject).to receive(:execute).with(['rspec'], args: 'spec/test_spec.rb').and_return(result) subject.run } end context 'when rspec exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'with a runtime error' do before do result.stub(stdout: '', stderr: <<-MSG) /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `load': /home/user/dev/github/overcommit/spec/overcommit/hook/pre_push/rspec_spec.rb:49: can't find string "EOS" anywhere before EOF (SyntaxError) /home/user/dev/overcommit/spec/overcommit/hook/pre_push/rspec_spec.rb:29: syntax error, unexpected end-of-input from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `block in load_spec_files' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `each' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `load_spec_files' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:97:in `setup' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:85:in `run' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:70:in `run' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:38:in `invoke' from /home/user/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.2/exe/rspec:4:in `' from /home/user/.rbenv/versions/2.2.1/bin/rspec:23:in `load' from /home/user/.rbenv/versions/2.2.1/bin/rspec:23:in `
' MSG end it { should fail_hook } end context 'with a test failure' do before do result.stub(stderr: '', stdout: <<-MSG) .FF Failures: 1) Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a runtime error should fail Failure/Error: it { should fail_hook } expected that the hook would fail # ./spec/overcommit/hook/pre_push/rspec_spec.rb:45:in `block (4 levels) in ' 2) Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a test failure should fail Failure/Error: it { should fail_hook } expected that the hook would fail # ./spec/overcommit/hook/pre_push/rspec_spec.rb:57:in `block (4 levels) in ' Finished in 0.00505 seconds (files took 0.27437 seconds to load) 3 examples, 2 failures Failed examples: rspec ./spec/overcommit/hook/pre_push/rspec_spec.rb:45 # Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a runtime error should fail rspec ./spec/overcommit/hook/pre_push/rspec_spec.rb:57 # Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a test failure should fail MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/rails_best_practices_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RailsBestPractices 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 rails_best_practices 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 rails_best_practices exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.rb:7 - simplify render in controllers', ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } end context 'when there is an error running rails_best_practices' do before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return([ 'Something went wrong with rails_best_practices' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/rails_schema_up_to_date_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RailsSchemaUpToDate do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:ruby_schema_file) { 'db/schema.rb' } let(:sql_schema_file) { 'db/structure.sql' } let(:migration_files) do %w[ db/migrate/20140304042233_some_migration.rb db/migrate/20140305123456_some_migration.rb ] end subject { described_class.new(config, context) } context "when a migration was added but the schema wasn't updated" do before do subject.stub(:applicable_files).and_return(migration_files) end around do |example| repo do FileUtils.mkdir_p('db/migrate') migration_files.each do |migration_file| File.open(migration_file, 'w') { |f| f.write('migration') } `git add #{migration_file}` end example.run end end it { should fail_hook } end context 'when a Ruby schema file was added but no migration files were' do before do subject.stub(:applicable_files).and_return([ruby_schema_file]) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(ruby_schema_file, 'w') { |f| f.write('version: 20160904205635') } `git add #{ruby_schema_file}` example.run end end it { should fail_hook } end context 'when a SQL schema file was added but no migration files were' do 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("VALUES ('20151214213046')") } `git add #{sql_schema_file}` example.run end end it { should fail_hook } context 'when non ASCII encoding is required' do let!(:config) do super().merge(Overcommit::Configuration.new( 'PreCommit' => { 'RailsSchemaUpToDate' => { 'encoding' => 'utf-8' } } )) end before do subject.stub(:applicable_files).and_return([sql_schema_file]) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(sql_schema_file, 'w') { |f| f.write("version: 12345678901234\nVALUES ('字')") } `git add #{sql_schema_file}` example.run end end it { should fail_hook } end end context 'when a Ruby schema file with the latest version and migrations are added' do before do subject.stub(:applicable_files).and_return(migration_files << ruby_schema_file) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(ruby_schema_file, 'w') { |f| f.write('20140305123456') } `git add #{ruby_schema_file}` migration_files.each do |migration_file| File.open(migration_file, 'w') { |f| f.write('migration') } `git add #{migration_file}` end example.run end end it { should pass } end context 'when a Ruby schema generated by Rails 5.2+ format and migrations are added' do before do subject.stub(:applicable_files).and_return(migration_files << ruby_schema_file) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(ruby_schema_file, 'w') { |f| f.write('2014_03_05_123456') } `git add #{ruby_schema_file}` migration_files.each do |migration_file| File.open(migration_file, 'w') { |f| f.write('migration') } `git add #{migration_file}` end example.run end end it { should pass } end context 'when a Ruby schema file which is not at the latest version and migrations are added' do before do subject.stub(:applicable_files).and_return(migration_files << ruby_schema_file) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(ruby_schema_file, 'w') { |f| f.write('20140205123456') } `git add #{ruby_schema_file}` migration_files.each do |migration_file| File.open(migration_file, 'w') { |f| f.write('migration') } `git add #{migration_file}` end example.run end end it { should fail_hook } end context 'when a SQL schema file with the latest version and migrations are added' do before do subject.stub(:applicable_files).and_return(migration_files << sql_schema_file) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(sql_schema_file, 'w') { |f| f.write('20140305123456') } `git add #{sql_schema_file}` migration_files.each do |migration_file| File.open(migration_file, 'w') { |f| f.write('migration') } `git add #{migration_file}` end example.run end end it { should pass } end context 'when schema file w/ latest version and migrations are added in dir w/ number' do let(:user_dir) { 'hdd418/' } before do files = (migration_files << sql_schema_file).map do |file| "#{user_dir}#{file}" end subject.stub(:applicable_files).and_return(files) end around do |example| repo do FileUtils.mkdir_p('hdd418/db/migrate') File.open(user_dir + sql_schema_file, 'w') { |f| f.write('20140305123456') } `git add #{user_dir}#{sql_schema_file}` migration_files.each do |migration_file| File.open(user_dir + migration_file, 'w') { |f| f.write('migration') } `git add #{user_dir}#{migration_file}` end example.run end end it { should pass } end context 'when a SQL schema file which is not at the latest version and migrations are added' do before do subject.stub(:applicable_files).and_return(migration_files << sql_schema_file) end around do |example| repo do FileUtils.mkdir_p('db/migrate') File.open(sql_schema_file, 'w') { |f| f.write('20140205123456') } `git add #{sql_schema_file}` migration_files.each do |migration_file| File.open(migration_file, 'w') { |f| f.write('migration') } `git add #{migration_file}` end example.run end end it { should fail_hook } end context 'when the schema file is at version 0 and there are no migrations' do before do subject.stub(:applicable_files).and_return([ruby_schema_file]) end around do |example| repo do FileUtils.mkdir_p('db') File.open(ruby_schema_file, 'w') { |f| f.write('version: 0') } `git add #{ruby_schema_file}` example.run end end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/rake_target_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RakeTarget do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'without targets parameters' do let(:result) { double('result') } it 'raises' do expect { subject.run }.to raise_error( RuntimeError, /RakeTarget: targets parameter is empty.*/ ) end end context 'with targets parameter set' do let(:config) do super().merge(Overcommit::Configuration.new( 'PreCommit' => { 'RakeTarget' => { 'targets' => ['test'], } } )) end let(:result) { double('result') } context 'when rake exits successfully' do before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) result.stub(:stdout).and_return('ANYTHING') end it { should pass } end context 'when rake exits unsuccessfully' do before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) result.stub(:stdout).and_return('ANYTHING') end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/reek_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Reek 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 reek 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 reek 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 warnings' do context 'in old format' do before do result.stub(:stdout).and_return([ 'file1.rb -- 1 warning:', 'file1.rb:1: MyClass#my_method performs a nil-check. (NilCheck)' ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } it 'parses the right file' do subject.run.map(&:file).should == ['file1.rb'] end end context 'in new format' do before do result.stub(:stdout).and_return([ 'file1.rb -- 1 warning:', ' file1.rb:1: MyClass#my_method performs a nil-check. (NilCheck)' ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } it 'parses the right file' do subject.run.map(&:file).should == ['file1.rb'] end end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/rst_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RstLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do result.stub(success?: success, stdout: stdout, stderr: stderr) subject.stub(:applicable_files).and_return(%w[file1.rst file2.rst]) subject.stub(:execute).and_return(result) end context 'when rst-lint exits successfully' do let(:success) { true } let(:stdout) { '' } let(:stderr) { '' } it { should pass } end context 'when rst-lint exits unsuccessfully' do let(:success) { false } context 'and it reports an error' do let(:stdout) { 'WARNING file1.rst:7 Title underline too short.' } let(:stderr) { '' } it { should fail_hook } end context 'when there is an error running rst-lint' do let(:stdout) { '' } let(:stderr) { 'Some runtime error' } it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/rubo_cop_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RuboCop 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 rubocop exits successfully' do let(:result) { double('result') } before do result.stub(success?: true, stderr: '', stdout: '') subject.stub(:execute).and_return(result) end it { should pass } context 'and it printed warnings to stderr' do before do result.stub(:stderr).and_return(normalize_indent(<<-MSG)) warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.8-compliant syntax, but you are running 2.1.1. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. MSG end it { should pass } end end context 'when rubocop exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.rb:1:1: W: Useless assignment to variable - my_var.', ].join("\n")) result.stub(:stderr).and_return('') end it { should warn } context 'and it printed warnings to stderr' do before do result.stub(:stderr).and_return(normalize_indent(<<-MSG)) warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.8-compliant syntax, but you are running 2.1.1. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. MSG end it { should warn } end end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.rb:1:1: C: Missing top-level class documentation', ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } context 'and it printed warnings to stderr' do before do result.stub(:stderr).and_return(normalize_indent(<<-MSG)) warning: parser/current is loading parser/ruby21, which recognizes warning: 2.1.8-compliant syntax, but you are running 2.1.1. warning: please see https://github.com/whitequark/parser#compatibility-with-ruby-mri. MSG end it { should fail_hook } end end context 'when a generic error message is written to stderr' do before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return([ 'Could not find rubocop in any of the sources' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/ruby_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RubyLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when ruby-lint 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 ruby-lint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.rb:W:1:1: unused argument foo', ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.rb:E:1:1: undefined constant Foo', ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/ruby_syntax_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::RubySyntax do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when ruby_syntax exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no errors' do before do result.stub(:stderr).and_return('') end it { should pass } end end context 'when ruby_syntax exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stderr).and_return([ "file1.rb:2: syntax error, unexpected '^'" ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/scalariform_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Scalariform 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.scala file2.scala]) end context 'when there were no failures or errors' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) result.stub(:stdout).and_return([ 'Assuming source is Scala 2.10.4', 'Formatting with default preferences.', '[OK] file1.scala', '[OK] file2.scala' ].join("\n")) end it { should pass } end context 'when there were failures' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) result.stub(:stdout).and_return([ 'Assuming source is Scala 2.10.4', 'Formatting with default preferences.', '[OK] file1.scala', '[FAILED] file2.scala' ].join("\n")) end it { should warn } end context 'when there were errors' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) result.stub(:stdout).and_return([ 'Assuming source is Scala 2.10.4', 'Formatting with default preferences.', '[ERROR] file1.scala', '[OK] file2.scala' ].join("\n")) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/scalastyle_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Scalastyle 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.scala file2.scala]) end context 'when scalastyle exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no errors or warnings' do before do result.stub(stderr: '', stdout: normalize_indent(<<-OUT)) Processed 1 file(s) Found 0 errors Found 0 warnings Finished in 490 ms OUT end it { should pass } end context 'and it reports a warning' do before do result.stub(stderr: '', stdout: normalize_indent(<<-OUT)) warning file=file1.scala message=Use : Unit = for procedures line=1 column=15 Processed 1 file(s) Found 0 errors Found 1 warnings Finished in 490 ms OUT end it { should warn(/Use : Unit = for procedures/) } end context 'and it reports a warning with no line' do before do result.stub(stderr: '', stdout: normalize_indent(<<-OUT)) warning file=file1.scala message=File must end with newline character Processed 1 file(s) Found 0 errors Found 1 warnings Finished in 490 ms OUT end it { should warn(/File must end with newline character/) } end end context 'when scalastyle exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(stderr: '', stdout: normalize_indent(<<-OUT)) error file=file1.scala message=Use : Unit = for procedures line=1 column=15 Processed 1 file(s) Found 1 errors Found 0 warnings Finished in 490 ms OUT end it { should fail_hook(/Use : Unit = for procedures/) } end context 'and it reports an error with no line' do before do result.stub(stderr: '', stdout: normalize_indent(<<-OUT)) error file=file1.scala message=File must end with newline character Processed 1 file(s) Found 1 errors Found 0 warnings Finished in 490 ms OUT end it { should fail_hook(/File must end with newline character/) } end context 'with a usage message' do before do result.stub(stderr: '', stdout: normalize_indent(<<-OUT)) scalastyle 0.7.0 Usage: scalastyle [options] -c, --config FILE configuration file (required) -v, --verbose true|false verbose output -q, --quiet true|false be quiet --xmlOutput FILE write checkstyle format output to this file --xmlEncoding STRING encoding to use for the xml file --inputEncoding STRING encoding for the source files -w, --warnings true|false fail if there are warnings -e, --externalJar FILE jar containing custom rules OUT end it { should fail_hook(/Usage/) } end context 'with a runtime error' do before do result.stub(stdout: '', stderr: normalize_indent(<<-ERR)) Exception in thread "main" java.io.FileNotFoundException: scalastyle-config.xml (No such file or directory) at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at java.io.FileInputStream.(FileInputStream.java:93) at scala.xml.Source$.fromFile(XML.scala:22) at scala.xml.factory.XMLLoader$class.loadFile(XMLLoader.scala:50) at scala.xml.XML$.loadFile(XML.scala:60) at org.scalastyle.ScalastyleConfiguration$.readFromXml(ScalastyleConfiguration.scala:87) at org.scalastyle.Main$.execute(Main.scala:106) at org.scalastyle.Main$.main(Main.scala:95) at org.scalastyle.Main.main(Main.scala) ERR end it { should fail_hook(/Exception/) } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/scss_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ScssLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.scss file2.scss]) end context 'when scss-lint exits successfully' do before do result = double('result') result.stub(:status).and_return(0) result.stub(:stdout).and_return('') subject.stub(:execute).and_return(result) end it { should pass } end context 'when scss-lint exits unsucessfully' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:status).and_return(1) result.stub(:stderr).and_return('') result.stub(:stdout).and_return(<<-JSON) { "test.scss": [ { "line": 1, "column": 1, "length": 2, "severity": "warning", "reason": "Empty rule", "linter": "EmptyRule" } ] } JSON end it { should warn } end context 'and it reports an error' do before do result.stub(:status).and_return(2) result.stub(:stderr).and_return('') result.stub(:stdout).and_return(<<-JSON) { "test.scss": [ { "line": 1, "column": 1, "length": 2, "severity": "error", "reason": "Syntax error", } ] } JSON end it { should fail_hook } end context 'and it returns invalid JSON' do before do result.stub(:status).and_return(1) result.stub(:stderr).and_return('') result.stub(:stdout).and_return('This is not JSON') end it { should fail_hook /Unable to parse JSON returned by SCSS-Lint/ } end context 'and it returns status code indicating all files were filtered' do before do result.stub(:status).and_return(81) result.stub(:stderr).and_return('') result.stub(:stdout).and_return('') end it { should pass } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/semi_standard_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::SemiStandard 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.js file2.js]) end context 'when semistandard exits successfully' do before do result = double('result') result.stub(success?: true, stdout: '') subject.stub(:execute).and_return(result) end it { should pass } end context 'when semistandard 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([ 'semistandard: Use Semicolons For All! (https://github.com/Flet/semistandard)', ' file1.js:1:1: Extra semicolon. (eslint/semi)' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/shell_check_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::ShellCheck 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.sh file2.sh]) end context 'when shellcheck 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 shellcheck exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a note' do before do result.stub(:stdout).and_return([ "file1.sh:1:1: note: Use ./*.ogg so names with dashes won't become \ options. [SC2035]", ].join("\n")) result.stub(:stderr).and_return('') end it { should warn } end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ "file1.sh:1:1: warning: Quote the parameter to -name so the shell \ won't interpret it. [SC2061]", ].join("\n")) result.stub(:stderr).and_return('') end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/slim_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::SlimLint 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.slim file2.slim]) end context 'when slim-lint 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 slim-lint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports a warning' do before do result.stub(:stdout).and_return([ 'file1.slim:1 [W] Prefer single quoted strings', ].join("\n")) end it { should warn } end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.slim:1 [E] Unbalanced brackets', ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/solargraph_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Solargraph do let(:config) do Overcommit::ConfigurationLoader.default_configuration.merge( Overcommit::Configuration.new( 'PreCommit' => { 'Solargraph' => { 'problem_on_unmodified_line' => problem_on_unmodified_line } } ) ) end let(:problem_on_unmodified_line) { 'ignore' } let(:context) { double('context') } let(:messages) { subject.run } let(:result) { double('result') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) result.stub(:stderr).and_return(stderr) result.stub(:stdout).and_return(stdout) end context 'when Solargraph exits successfully' do before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'and it printed a message to stderr' do let(:stderr) { 'stderr unexpected message that must be fine since command successful' } let(:stdout) { '' } it { should pass } end context 'and it printed a message to stdout' do let(:stderr) { '' } let(:stdout) { 'stdout message that must be fine since command successful' } it { should pass } end end context 'when Solargraph exits unsucessfully' do before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports typechecking issues' do let(:stdout) do normalize_indent(<<-MSG) /home/username/src/solargraph-rails/file1.rb:36 - Unresolved constant Solargraph::Parser::Legacy::NodeChainer /home/username/src/solargraph-rails/file2.rb:44 - Unresolved call to [] /home/username/src/solargraph-rails/file2.rb:99 - Unresolved call to [] Typecheck finished in 8.921023999806494 seconds. 189 problems found in 14 of 16 files. MSG end ['', 'unexpected output'].each do |stderr_string| context "with stderr output of #{stderr_string.inspect}" do let(:stderr) { stderr_string } it { should fail_hook } it 'reports only three errors and assumes stderr is harmless' do expect(messages.size).to eq 3 end it 'parses filename' do expect(messages.first.file).to eq '/home/username/src/solargraph-rails/file1.rb' end it 'parses line number of messages' do expect(messages.first.line).to eq 36 end it 'parses and returns error message content' do msg = '/home/username/src/solargraph-rails/file1.rb:36 - Unresolved constant Solargraph::Parser::Legacy::NodeChainer' expect(messages.first.content).to eq msg end end end end context 'but it reports no typechecking issues' do let(:stdout) do normalize_indent(<<-MSG) Typecheck finished in 8.095239999704063 seconds. 0 problems found in 0 of 16 files. MSG end context 'with no stderr output' do let(:stderr) { '' } it 'should return no messages' do expect(messages).to eq([:fail, 'Solargraph failed to run']) end end context 'with stderr output' do let(:stderr) { 'something' } it 'should raise' do expect { messages }.to raise_error(Overcommit::Exceptions::MessageProcessingError) end end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/sorbet_spec.rb ================================================ # 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 ================================================ FILE: spec/overcommit/hook/pre_commit/sqlint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Sqlint 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.sql file2.sql]) end context 'when sqlint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end context 'and it reports a warning' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.sql:2:10:WARNING some warning OUT end it { should warn } end end context 'when sqlint exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return(normalize_indent(<<-OUT)) file1.sql:2:10:ERROR syntax error at or near "USE" OUT end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/standard_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Standard 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.js file2.js]) end context 'when standard exits successfully' do before do result = double('result') result.stub(success?: true, stdout: '') subject.stub(:execute).and_return(result) end it { should pass } end context 'when standard 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([ 'standard: Use JavaScript Standard Style (https://github.com/feross/standard)', ' file1.js:1:1: Extra semicolon. (eslint/semi)' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/stylelint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Stylelint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.scss file2.scss]) end context 'when stylelint exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) result.stub(:stderr).and_return('') result.stub(:stdout).and_return('') subject.stub(:execute).and_return(result) end it { should pass } end context 'when stylelint exits unsucessfully with messages on stdout (stylelint < 16)' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:success?).and_return(false) result.stub(:stderr).and_return('') result.stub(:stdout).and_return([ 'index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation)', 'form.css: line 10, col 6, error - Expected indentation of 4 spaces (indentation)', ].join("\n")) end it { should fail_hook } it 'extracts lines numbers correctly from output' do expect(subject.run.map(&:line)).to eq([4, 10]) end end end context 'when stylelint exits unsucessfully with messages on stderr (stylelint >= 16)' do let(:result) { double('result') } before do subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:success?).and_return(false) result.stub(:stdout).and_return('') result.stub(:stderr).and_return([ 'index.css: line 4, col 4, error - Expected indentation of 2 spaces (indentation)', 'form.css: line 10, col 6, error - Expected indentation of 4 spaces (indentation)', ].join("\n")) end it { should fail_hook } it 'extracts lines numbers correctly from output' do expect(subject.run.map(&:line)).to eq([4, 10]) end end end end ================================================ FILE: spec/overcommit/hook/pre_commit/swift_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::SwiftLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.swift file2.swift]) end context 'when SwiftLint exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when SwiftLint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return([ 'file1.swift:12: warning: message: details' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/terraform_format_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::TerraformFormat do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.tf file2.tf]) end context 'when Terraform exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when Terraform exits unsucessfully' do let(:result_ok) { double('result') } let(:result_bad) { double('result') } let(:cmdline) { %w[terraform fmt -check=true -diff=false] } before do result_ok.stub(:success?).and_return(true) result_bad.stub(:success?).and_return(false) subject.stub(:execute).with(cmdline, args: ['file1.tf']).and_return(result_ok) subject.stub(:execute).with(cmdline, args: ['file2.tf']).and_return(result_bad) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/trailing_whitespace_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::TrailingWhitespace do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'filename.txt' } before do subject.stub(:applicable_files).and_return([staged_file]) end around do |example| repo do File.open(staged_file, 'w') { |f| f.write(contents) } `git add #{staged_file}` example.run end end context 'when file contains trailing whitespace' do let(:contents) { 'Some trailing whitespace ' } it { should fail_hook } end context 'when file contains trailing tabs' do let(:contents) { "Some trailing tabs\t\t" } it { should fail_hook } end context 'when file has no invalid whitespace' do let(:contents) { 'Just some text' } it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/travis_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::TravisLint 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(['.travis.yml']) end context 'when travis-lint 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 travis-lint 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 ================================================ FILE: spec/overcommit/hook/pre_commit/ts_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::TsLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[anotherfile.ts]) end context 'when tslint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no output' do before do result.stub(:stdout).and_return('') end it { should pass } end end context 'when tslint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return( 'src/file/anotherfile.ts[298, 1]: exceeds maximum line length of 140' ) end it { should fail_hook } end context 'and it reports an error with an "ERROR" severity' do before do result.stub(:stdout).and_return( 'ERROR: src/AccountController.ts[4, 28]: expected call-signature to have a typedef' ) end it { should fail_hook } end context 'and it reports an warning' do before do result.stub(:stdout).and_return( 'WARNING: src/AccountController.ts[4, 28]: expected call-signature to have a typedef' ) end it { should warn } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/vint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::Vint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:result) { double('result') } before do subject.stub(:applicable_files).and_return(%w[file1.vim file2.vim]) subject.stub(:execute).and_return(result) end context 'when vint exits successfully' do before do result.stub(:success?).and_return(true) end it { should pass } end context 'when vint exits unsucessfully' do before do result.stub(:success?).and_return(false) end context 'and it reports an error' do before do result.stub(stderr: '', stdout: [ 'file1.vim:1:0: autocmd should execute in augroup or execute with a group', ].join("\n")) end it { should fail_hook } end context 'with a runtime error' do before do result.stub(:stderr).and_return([ 'vint ERROR: no such file or directory: `foo.vim`' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/w3c_css_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::W3cCss do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:fake_exception) { Class.new(StandardError) } before do subject.stub(:applicable_files).and_return(%w[file1.css file2.css]) stub_const('W3CValidators::ValidatorUnavailable', fake_exception) stub_const('W3CValidators::ParsingError', fake_exception) end context 'when w3c_validators exits with an exception' do let(:validator) { double('validator') } before do subject.stub(:validator).and_return(validator) end context 'when the validator is not available' do before do validator.stub(:validate_file).and_raise(W3CValidators::ValidatorUnavailable) end it { should fail_hook } end context 'when the validator response cannot be parsed' do before do validator.stub(:validate_file).and_raise(W3CValidators::ParsingError) end it { should fail_hook } end end context 'when w3c_validators exits without an exception' do let(:validator) { double('validator') } let(:results) { double('results') } let(:message) { double('message') } before do validator.stub(:validate_file).and_return(results) subject.stub(:validator).and_return(validator) end context 'with no errors or warnings' do before do results.stub(errors: [], warnings: []) end it { should pass } end context 'with a warning' do before do message.stub(type: :warning, line: '1', message: '') results.stub(errors: [], warnings: [message]) end it { should warn } end context 'with an error' do before do message.stub(type: :error, line: '1', message: '') results.stub(errors: [message], warnings: []) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/w3c_html_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::W3cHtml do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:fake_exception) { Class.new(StandardError) } before do subject.stub(:applicable_files).and_return(%w[file1.html file2.html]) stub_const('W3CValidators::ValidatorUnavailable', fake_exception) stub_const('W3CValidators::ParsingError', fake_exception) end context 'when w3c_validators exits with an exception' do let(:validator) { double('validator') } before do subject.stub(:validator).and_return(validator) end context 'when the validator is not available' do before do validator.stub(:validate_file).and_raise(W3CValidators::ValidatorUnavailable) end it { should fail_hook } end context 'when the validator response cannot be parsed' do before do validator.stub(:validate_file).and_raise(W3CValidators::ParsingError) end it { should fail_hook } end end context 'when w3c_validators exits without an exception' do let(:validator) { double('validator') } let(:results) { double('results') } let(:message) { double('message') } before do validator.stub(:validate_file).and_return(results) subject.stub(:validator).and_return(validator) end context 'with no errors or warnings' do before do results.stub(errors: [], warnings: []) end it { should pass } end context 'with a warning' do before do message.stub(type: :warning, line: '1', message: '') results.stub(errors: [], warnings: [message]) end it { should warn } end context 'with an error' do before do message.stub(type: :error, line: '1', message: '') results.stub(errors: [message], warnings: []) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/xml_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::XmlLint 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.xml file2.xml]) end context 'when xmllint exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end context 'with no errors' do before do result.stub(:stderr).and_return('') end it { should pass } end end context 'when xmllint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stderr).and_return([ "file1.xml:1: parser error : expected '='" ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/xml_syntax_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'rexml/document' describe Overcommit::Hook::PreCommit::XmlSyntax do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'file1.xml' } before do IO.stub(:read).with(staged_file) subject.stub(:applicable_files).and_return([staged_file]) end context 'when XML files have no errors' do before do REXML::Document.stub(:new) end it { should pass } end context 'when XML file has errors' do before do REXML::Document.stub(:new).and_raise(REXML::ParseException.new('')) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/yaml_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::YamlLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } let(:applicable_files) { %w[file1.yaml file2.yml] } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(applicable_files) end around do |example| repo do example.run end end before do subject.stub(:execute).with(%w[yamllint --format=parsable --strict], args: applicable_files). and_return(result) end context 'and has 2 suggestions for line length' do let(:result) do double( success?: false, stdout: <<-MSG file1.yaml:3:81: [error] line too long (253 > 80 characters) (line-length) file2.yml:41:81: [error] line too long (261 > 80 characters) (line-length) MSG ) end it { should fail_hook } end context 'and has 1 error and 1 warning' do let(:result) do double( success?: false, stdout: <<-MSG file1.yaml:3:81: [error] line too long (253 > 80 characters) (line-length) file2.yml:41:81: [warning] missing document start "---" (document-start) MSG ) end it { should fail_hook } end context 'and has single suggestion for missing file header' do let(:result) do double( success?: false, stdout: <<-MSG file1.yaml:1:1: [warning] missing document start "---" (document-start) MSG ) end it { should warn } end context 'and does not have any suggestion' do let(:result) do double(success?: true, stdout: '') end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_commit/yaml_syntax_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::YamlSyntax do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:staged_file) { 'file1.yml' } before do subject.stub(:applicable_files).and_return([staged_file]) end context 'when YAML files have no errors' do before do YAML.stub(:load_file) end it { should pass } end context 'when YAML file has errors' do before do YAML.stub(:load_file).with(staged_file, { aliases: true }).and_raise(ArgumentError) YAML.stub(:load_file).with(staged_file).and_raise(ArgumentError) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_commit/yard_coverage_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::YardCoverage do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when yard exits successfully' do before do result = double('result') result.stub(:stdout).and_return( <<-HEREDOC Files: 72 Modules: 12 ( 0 undocumented) Classes: 63 ( 0 undocumented) Constants: 91 ( 0 undocumented) Attributes: 11 ( 0 undocumented) Methods: 264 ( 0 undocumented) 100.0% documented HEREDOC ) subject.stub(:execute).and_return(result) end it { should pass } end context 'when somehow yard exits a non-stats output' do before do result = double('result') result.stub(:stdout).and_return( <<-HEREDOC WHATEVER OUTPUT THAT IS NOT YARD STATS ONE HEREDOC ) subject.stub(:execute).and_return(result) end it { should warn } end context 'when somehow yard coverage is not a valid value' do before do result = double('result') result.stub(:stdout).and_return( <<-HEREDOC Files: 72 Modules: 12 ( 0 undocumented) Classes: 63 ( 0 undocumented) Constants: 91 ( 0 undocumented) Attributes: 11 ( 0 undocumented) Methods: 264 ( 0 undocumented) AAAAAA documented HEREDOC ) subject.stub(:execute).and_return(result) end it { should warn } end context 'when yard exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stdout).and_return( <<-HEREDOC Files: 72 Modules: 12 ( 3 undocumented) Classes: 63 ( 15 undocumented) Constants: 91 ( 79 undocumented) Attributes: 11 ( 0 undocumented) Methods: 264 ( 55 undocumented) 65.53% documented Undocumented Objects: ApplicationCable (app/channels/application_cable/channel.rb:1) ApplicationCable::Channel (app/channels/application_cable/channel.rb:2) ApplicationCable::Connection (app/channels/application_cable/connection.rb:2) HEREDOC ) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_commit/yarn_check_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreCommit::YarnCheck do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when yarn.lock is ignored' do around do |example| repo do touch 'yarn.lock' echo('yarn.lock', '.gitignore') `git add .gitignore` `git commit -m "Ignore yarn.lock"` example.run end end it { should pass } end context 'when yarn.lock is not ignored' do let(:result) { double('result') } around do |example| repo do example.run end end before do result.stub(stderr: stderr) subject.stub(:execute).with(%w[git ls-files -o -i --exclude-standard]). and_return(double(stdout: '')) subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]). and_return(result) end context 'and yarn check reports no errors' do let(:stderr) { '' } it { should pass } context 'and there was a change to the yarn.lock' do before do subject.stub(:execute).with(%w[yarn check --silent --no-progress --non-interactive]) do echo('stuff', 'yarn.lock') double(stderr: '') end end it { should fail_hook } end end context 'and yarn check contains only warnings' do let(:stderr) do < exclude_remotes } end context 'exclude_remotes is nil' do let(:exclude_remotes) { nil } it { subject.should == true } end context 'exclude_remotes includes the remote' do let(:exclude_remotes) { [remote_name] } it { subject.should == false } end context 'exclude_remotes does not include the remote' do let(:exclude_remotes) { ['heroku'] } it { subject.should == true } end end context 'with include_remote_ref_deletions specified' do let(:hook_config) do { 'include_remote_ref_deletions' => include_remote_ref_deletions } end let(:remote_ref_deletion?) { false } let(:include_remote_ref_deletions) { false } context 'when remote branch is not being deleted' do let(:remote_ref_deletion?) { false } context 'when include_remote_ref_deletions is not specified' do let(:include_remote_ref_deletions) { nil } it { subject.should == true } end context 'when include_remote_ref_deletions is false' do let(:include_remote_ref_deletions) { false } it { subject.should == true } end context 'when include_remote_ref_deletions is true' do let(:include_remote_ref_deletions) { true } it { subject.should == true } end end context 'when remote branch is being deleted' do let(:remote_ref_deletion?) { true } context 'when include_remote_ref_deletions is not specified' do let(:include_remote_ref_deletions) { nil } it { subject.should == false } end context 'when include_remote_ref_deletions is false' do let(:include_remote_ref_deletions) { false } it { subject.should == false } end context 'when include_remote_ref_deletions is true' do let(:include_remote_ref_deletions) { true } it { subject.should == true } end end end end end ================================================ FILE: spec/overcommit/hook/pre_push/brakeman_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::Brakeman do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when brakeman 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 brakeman exits unsucessfully' do before do result = double('result') result.stub(:success?).and_return(false) result.stub(:stdout).and_return('Some error message') subject.stub(:execute).and_return(result) end it { should fail_hook 'Some error message' } end end ================================================ FILE: spec/overcommit/hook/pre_push/cargo_test_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::CargoTest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when all tests succeed' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when one test fails' do before do result = double('result') result.stub(:success?).and_return(false) result.stub(stdout: <<-ERRORMSG) running 2 tests test tests::foo ... ok test tests::bar ... FAILED failures: ---- tests::bar stdout ---- thread 'tests::bar' panicked at 'assertion failed: `(left == right)` left: `None`, right: `Some(Bar)`', src/foobar.rs:88:9 note: Run with `RUST_BACKTRACE=1` for a backtrace. failures: tests::bar test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out ERRORMSG subject.stub(:execute).and_return(result) end it { should fail_hook } end end ================================================ FILE: spec/overcommit/hook/pre_push/flutter_test_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::FlutterTest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when flutter test exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when flutter test exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'with a runtime error' do before do result.stub(stdout: '', stderr: <<-MSG) 0:03 +0: Counter increments smoke test ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following _Exception was thrown running a test: Exception When the exception was thrown, this was the stack: #0 main. (file:///Users/user/project/test/widget_test.dart:18:5) #1 main. (file:///Users/user/project/test/widget_test.dart) #2 testWidgets.. (package:flutter_test/src/widget_tester.dart:146:29) #3 testWidgets.. (package:flutter_test/src/widget_tester.dart) #4 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:784:19) #7 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:764:14) #8 AutomatedTestWidgetsFlutterBinding.runTest. (package:flutter_test/src/binding.dart:1173:24) #9 FakeAsync.run.. (package:fake_async/fake_async.dart:178:54) #14 withClock (package:clock/src/default.dart:48:10) #15 FakeAsync.run. (package:fake_async/fake_async.dart:178:22) #20 FakeAsync.run (package:fake_async/fake_async.dart:178:7) #21 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1170:15) #22 testWidgets. (package:flutter_test/src/widget_tester.dart:138:24) #23 Declarer.test.. (package:test_api/src/backend/declarer.dart:175:19) #24 Declarer.test.. (package:test_api/src/backend/declarer.dart) #29 Declarer.test. (package:test_api/src/backend/declarer.dart:173:13) #30 Invoker.waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:231:15) #35 Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:228:5) #36 Invoker._onRun... (package:test_api/src/backend/invoker.dart:383:17) #37 Invoker._onRun... (package:test_api/src/backend/invoker.dart) #42 Invoker._onRun.. (package:test_api/src/backend/invoker.dart:370:9) #43 Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:415:15) #44 Invoker._onRun. (package:test_api/src/backend/invoker.dart:369:7) #51 Invoker._onRun (package:test_api/src/backend/invoker.dart:368:11) #52 LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11) #53 RemoteListener._runLiveTest. (package:test_api/src/remote_listener.dart:256:16) #58 RemoteListener._runLiveTest (package:test_api/src/remote_listener.dart:255:5) #59 RemoteListener._serializeTest. (package:test_api/src/remote_listener.dart:208:7) #77 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12) #78 new _MultiChannel. (package:stream_channel/src/multi_channel.dart:159:31) #82 CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11) #116 new _WebSocketImpl._fromSocket. (dart:_http/websocket_impl.dart:1145:21) #124 _WebSocketProtocolTransformer._messageFrameEnd (dart:_http/websocket_impl.dart:338:23) #125 _WebSocketProtocolTransformer.add (dart:_http/websocket_impl.dart:232:46) #135 _Socket._onData (dart:io-patch/socket_patch.dart:2044:41) #144 new _RawSocket. (dart:io-patch/socket_patch.dart:1580:33) #145 _NativeSocket.issueReadEvent.issue (dart:io-patch/socket_patch.dart:1076:14) (elided 111 frames from dart:async and package:stack_trace) The test description was: Counter increments smoke test ════════════════════════════════════════════════════════════════════════════════════════════════════ 00:03 +0 -1: Counter increments smoke test [E] Test failed. See exception logs above. The test description was: Counter increments smoke test 00:03 +0 -1: Some tests failed. MSG end it { should fail_hook } end context 'with a test failure' do before do result.stub(stderr: '', stdout: <<-MSG) 00:02 +0: Counter increments smoke test ══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════ The following TestFailure object was thrown running a test: Expected: exactly one matching node in the widget tree Actual: _TextFinder: Which: means none were found but one was expected When the exception was thrown, this was the stack: #4 main. (file:///Users/user/project/test/widget_test.dart:19:5) #5 main. (file:///Users/user/project/test/widget_test.dart) #6 testWidgets.. (package:flutter_test/src/widget_tester.dart:146:29) #7 testWidgets.. (package:flutter_test/src/widget_tester.dart) #8 TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:784:19) #11 TestWidgetsFlutterBinding._runTest (package:flutter_test/src/binding.dart:764:14) #12 AutomatedTestWidgetsFlutterBinding.runTest. (package:flutter_test/src/binding.dart:1173:24) #13 FakeAsync.run.. (package:fake_async/fake_async.dart:178:54) #18 withClock (package:clock/src/default.dart:48:10) #19 FakeAsync.run. (package:fake_async/fake_async.dart:178:22) #24 FakeAsync.run (package:fake_async/fake_async.dart:178:7) #25 AutomatedTestWidgetsFlutterBinding.runTest (package:flutter_test/src/binding.dart:1170:15) #26 testWidgets. (package:flutter_test/src/widget_tester.dart:138:24) #27 Declarer.test.. (package:test_api/src/backend/declarer.dart:175:19) #28 Declarer.test.. (package:test_api/src/backend/declarer.dart) #33 Declarer.test. (package:test_api/src/backend/declarer.dart:173:13) #34 Invoker.waitForOutstandingCallbacks. (package:test_api/src/backend/invoker.dart:231:15) #39 Invoker.waitForOutstandingCallbacks (package:test_api/src/backend/invoker.dart:228:5) #40 Invoker._onRun... (package:test_api/src/backend/invoker.dart:383:17) #41 Invoker._onRun... (package:test_api/src/backend/invoker.dart) #46 Invoker._onRun.. (package:test_api/src/backend/invoker.dart:370:9) #47 Invoker._guardIfGuarded (package:test_api/src/backend/invoker.dart:415:15) #48 Invoker._onRun. (package:test_api/src/backend/invoker.dart:369:7) #55 Invoker._onRun (package:test_api/src/backend/invoker.dart:368:11) #56 LiveTestController.run (package:test_api/src/backend/live_test_controller.dart:153:11) #57 RemoteListener._runLiveTest. (package:test_api/src/remote_listener.dart:256:16) #62 RemoteListener._runLiveTest (package:test_api/src/remote_listener.dart:255:5) #63 RemoteListener._serializeTest. (package:test_api/src/remote_listener.dart:208:7) #81 _GuaranteeSink.add (package:stream_channel/src/guarantee_channel.dart:125:12) #82 new _MultiChannel. (package:stream_channel/src/multi_channel.dart:159:31) #86 CastStreamSubscription._onData (dart:_internal/async_cast.dart:85:11) #120 new _WebSocketImpl._fromSocket. (dart:_http/websocket_impl.dart:1145:21) #128 _WebSocketProtocolTransformer._messageFrameEnd (dart:_http/websocket_impl.dart:338:23) #129 _WebSocketProtocolTransformer.add (dart:_http/websocket_impl.dart:232:46) #139 _Socket._onData (dart:io-patch/socket_patch.dart:2044:41) #148 new _RawSocket. (dart:io-patch/socket_patch.dart:1580:33) #149 _NativeSocket.issueReadEvent.issue (dart:io-patch/socket_patch.dart:1076:14) (elided 111 frames from dart:async and package:stack_trace) This was caught by the test expectation on the following line: file:///Users/user/project/test/widget_test.dart line 19 The test description was: Counter increments smoke test ════════════════════════════════════════════════════════════════════════════════════════════════════ 00:02 +0 -1: Counter increments smoke test [E] Test failed. See exception logs above. The test description was: Counter increments smoke test 00:02 +0 -1: Some tests failed. MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_push/go_test_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::GoTest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when go test exits successfully' do let(:result) { double('result') } before do result.stub(success?: true, stderr: '', stdout: '') subject.stub(:execute).and_return(result) end it 'passes' do expect(subject).to pass end end context 'when go test exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'when go test returns an error' do let(:error_message) { "--- FAIL: Test1 (0.00s)\nFAIL" } before do result.stub(:stdout).and_return(error_message) result.stub(:stderr).and_return('') end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message).to eq error_message end end context 'when a generic error message is written to stderr' do let(:error_message) { 'go: command not found' } before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return(error_message) end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message).to eq error_message end end end end ================================================ FILE: spec/overcommit/hook/pre_push/golangci_lint_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::GolangciLint do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when golangci-lint exits successfully' do let(:result) { double('result') } before do result.stub(success?: true, stderr: '', stdout: '') subject.stub(:execute).and_return(result) end it 'passes' do expect(subject).to pass end end context 'when golangci-lint exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'when golangci-lint returns an error' do let(:error_message) do 'pkg1/file1.go:8:6: exported type `Test` should have comment or be unexported (golint)' end before do result.stub(:stdout).and_return(error_message) result.stub(:stderr).and_return('') end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message).to eq error_message end end context 'when a generic error message is written to stderr' do let(:error_message) { 'golangci-lint: command not found' } before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return(error_message) end it 'fails' do expect(subject).to fail_hook end it 'returns valid message' do message = subject.run.last expect(message).to eq error_message end end end end ================================================ FILE: spec/overcommit/hook/pre_push/minitest_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::Minitest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context', all_files: ['test/test_foo.rb']) } subject { described_class.new(config, context) } context 'when minitest exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when minitest 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) 1) Error: FooTest#test_: foo should bar. : RuntimeError: test/model/foo_test.rb:1:in `block (2 levels) in ' MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_push/mix_test_spec.rb ================================================ # 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 ================================================ FILE: spec/overcommit/hook/pre_push/php_unit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::PhpUnit do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when phpunit 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 phpunit 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 ================================================ FILE: spec/overcommit/hook/pre_push/pronto_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::Pronto do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } before do subject.stub(:applicable_files).and_return(%w[file1.rb file2.rb]) end context 'when pronto exits successfully' do before do result = double('result') result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when pronto exits unsucessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'and it reports an error' do before do result.stub(:stderr).and_return('') result.stub(:stdout).and_return([ 'file2.rb:10 E: IDENTICAL code found in :iter.', ].join("\n")) end it { should fail_hook } end context 'and it reports a warning' do before do result.stub(:stderr).and_return('') result.stub(:stdout).and_return <<~MESSAGE Running Pronto::Rubocop file1.rb:12 W: Line is too long. [107/80] file2.rb:14 I: Prefer single-quoted strings ```suggestion x = 'x' ``` MESSAGE end it { should warn } end context 'and it has a generic error message written to stderr' do before do result.stub(:stdout).and_return('') result.stub(:stderr).and_return([ 'Could not find pronto in any of the sources' ].join("\n")) end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_push/protected_branches_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/pre_push' describe Overcommit::Hook::PrePush::ProtectedBranches do let(:hook_config) { {} } let(:config) do Overcommit::ConfigurationLoader.default_configuration.merge( Overcommit::Configuration.new( 'PrePush' => { 'ProtectedBranches' => hook_config } ) ) end let(:context) { double('context') } subject { described_class.new(config, context) } let(:branch_configurations) do ['master', 'release/*', { 'destructive_only_branch' => nil, 'destructive_only' => true }] end let(:pushed_ref) do instance_double(Overcommit::HookContext::PrePush::PushedRef) end before do subject.stub(branch_configurations: branch_configurations) pushed_ref.stub(:remote_ref).and_return("refs/heads/#{pushed_ref_name}") context.stub(:pushed_refs).and_return([pushed_ref]) end context 'when pushing to unprotected branch' do let(:pushed_ref_name) { 'unprotected-branch' } context 'when push is not destructive' do before do pushed_ref.stub(:destructive?).and_return(false) end it { should pass } end context 'when push is destructive' do before do pushed_ref.stub(:destructive?).and_return(true) end it { should pass } end end shared_examples_for 'protected branch' do context 'when push is not destructive' do before do pushed_ref.stub(:destructive?).and_return(false) end context 'and destructive_only set to false' do let(:hook_config) { { 'destructive_only' => false } } it { should fail_hook } end context 'and destructive_only set to true' do let(:hook_config) { { 'destructive_only' => true } } it { should pass } end end context 'when push is destructive' do before do pushed_ref.stub(:destructive?).and_return(true) end context 'when destructive_only is set to true' do let(:hook_config) { { 'destructive_only' => true } } it { should fail_hook } end context 'when destructive_only is set to false' do let(:hook_config) { { 'destructive_only' => false } } it { should fail_hook } end end end context 'when pushing to protected branch' do context 'when branch name matches a protected branch exactly' do let(:pushed_ref_name) { 'master' } include_examples 'protected branch' end context 'when branch name matches a protected branch glob pattern' do let(:pushed_ref_name) { 'release/0.1.0' } include_examples 'protected branch' end context 'when branch overwrites global destructive_only' do before do pushed_ref.stub(:destructive?).and_return(true) end let(:pushed_ref_name) { 'destructive_only_branch' } let(:hook_config) { { 'destructive_only' => false } } it { should fail_hook } end end context 'when pushing tags' do let(:pushed_ref_name) { 'redundant' } before do pushed_ref.stub(:remote_ref).and_return("refs/tags/#{pushed_ref_name}") end it { should pass } end end ================================================ FILE: spec/overcommit/hook/pre_push/pub_test_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::PubTest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when pub test exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when pub test exits unsuccessfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'with a runtime error' do before do result.stub(stdout: '', stderr: <<-MSG) 00:01 +0 -1: test/test_test.dart: String.split() splits the string on the delimiter [E] Exception test/test_test.dart 6:5 main. 00:01 +1 -1: Some tests failed. MSG end it { should fail_hook } end context 'with a test failure' do before do result.stub(stderr: '', stdout: <<-MSG) 00:01 +0 -1: test/test_test.dart: String.split() splits the string on the delimiter [E] Expected: ['fooo', 'bar', 'baz'] Actual: ['foo', 'bar', 'baz'] Which: at location [0] is 'foo' instead of 'fooo' package:test_api expect test/test_test.dart 6:5 main. 00:01 +1 -1: Some tests failed. MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_push/pytest_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::Pytest do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when pytest 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 pytest 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 ================================================ FILE: spec/overcommit/hook/pre_push/python_nose_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::PythonNose do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'when nose 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 nose 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 ================================================ FILE: spec/overcommit/hook/pre_push/r_spec_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::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( '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 let(:result) { double('result') } before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) end context 'with a runtime error' do before do result.stub(stdout: '', stderr: <<-MSG) /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `load': /home/user/dev/github/overcommit/spec/overcommit/hook/pre_push/rspec_spec.rb:49: can't find string "EOS" anywhere before EOF (SyntaxError) /home/user/dev/overcommit/spec/overcommit/hook/pre_push/rspec_spec.rb:29: syntax error, unexpected end-of-input from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `block in load_spec_files' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `each' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `load_spec_files' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:97:in `setup' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:85:in `run' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:70:in `run' from /home/user/.rbenv/gems/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:38:in `invoke' from /home/user/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/rspec-core-3.2.2/exe/rspec:4:in `' from /home/user/.rbenv/versions/2.2.1/bin/rspec:23:in `load' from /home/user/.rbenv/versions/2.2.1/bin/rspec:23:in `
' MSG end it { should fail_hook } end context 'with a test failure' do before do result.stub(stderr: '', stdout: <<-MSG) .FF Failures: 1) Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a runtime error should fail Failure/Error: it { should fail_hook } expected that the hook would fail # ./spec/overcommit/hook/pre_push/rspec_spec.rb:45:in `block (4 levels) in ' 2) Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a test failure should fail Failure/Error: it { should fail_hook } expected that the hook would fail # ./spec/overcommit/hook/pre_push/rspec_spec.rb:57:in `block (4 levels) in ' Finished in 0.00505 seconds (files took 0.27437 seconds to load) 3 examples, 2 failures Failed examples: rspec ./spec/overcommit/hook/pre_push/rspec_spec.rb:45 # Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a runtime error should fail rspec ./spec/overcommit/hook/pre_push/rspec_spec.rb:57 # Overcommit::Hook::PrePush::RSpec when rspec exits unsuccessfully with a test failure should fail MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_push/rake_target_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::RakeTarget do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } context 'without targets parameters' do let(:result) { double('result') } it 'raises' do expect { subject.run }.to raise_error( RuntimeError, /RakeTarget: targets parameter is empty.*/ ) end end context 'with targets parameter set' do let(:config) do super().merge(Overcommit::Configuration.new( 'PrePush' => { 'RakeTarget' => { 'targets' => ['test'], } } )) end let(:result) { double('result') } context 'when rake exits successfully' do before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) result.stub(:stdout).and_return('ANYTHING') end it { should pass } end context 'when rake exits unsuccessfully' do before do result.stub(:success?).and_return(false) subject.stub(:execute).and_return(result) result.stub(:stdout).and_return('ANYTHING') end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_push/test_unit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PrePush::TestUnit do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context', all_files: ['test/foo_test.rb']) } subject { described_class.new(config, context) } context 'when test-unit exits successfully' do let(:result) { double('result') } before do result.stub(:success?).and_return(true) subject.stub(:execute).and_return(result) end it { should pass } end context 'when test-unit 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) 1) Error: FooTest#test_: foo should bar. : RuntimeError: test/model/foo_test.rb:1:in `block (2 levels) in ' MSG end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/pre_rebase/merged_commits_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Hook::PreRebase::MergedCommits do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { double('context') } subject { described_class.new(config, context) } let(:master_branch) { 'master' } before do subject.stub(:branches) { [master_branch] } end context 'when rebasing a detached HEAD' do before do context.stub(:detached_head?) { true } end it { should pass } end context 'when there are no commits to rebase' do before do context.stub(:detached_head?) { false } context.stub(:rebased_commits) { [] } end it { should pass } end context 'when there are commits to rebase' do let(:commit_sha1) { random_hash } let(:rebased_branch) { 'topic' } before do context.stub(:detached_head?) { false } context.stub(:rebased_commits) { [commit_sha1] } end context 'when commits have not yet been merged' do before do Overcommit::GitRepo.stub(:branches_containing_commit). with(commit_sha1) { [rebased_branch] } end it { should pass } end context 'when commits have already been merged' do before do Overcommit::GitRepo.stub(:branches_containing_commit). with(commit_sha1) { [rebased_branch, master_branch] } end it { should fail_hook } end end end ================================================ FILE: spec/overcommit/hook/prepare_commit_msg/base_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/prepare_commit_msg' describe Overcommit::Hook::PrepareCommitMsg::Base do let(:config) { Overcommit::ConfigurationLoader.default_configuration } let(:context) { Overcommit::HookContext::PrepareCommitMsg.new(config, [], StringIO.new) } let(:printer) { double('printer') } context 'when multiple hooks run simultaneously' do let(:hook_1) { described_class.new(config, context) } let(:hook_2) { described_class.new(config, context) } let(:tempfile) { 'test-prepare-commit-msg.txt' } let(:initial_content) { "This is a test\n" } before do File.open(tempfile, 'w') do |f| f << initial_content end end after do File.delete(tempfile) end it 'works well with concurrency' do allow(context).to receive(:commit_message_filename).and_return(tempfile) allow(hook_1).to receive(:run) do hook_1.modify_commit_message do |contents| "alpha\n" + contents end end allow(hook_2).to receive(:run) do hook_2.modify_commit_message do |contents| contents + "bravo\n" end end 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 end ================================================ FILE: spec/overcommit/hook/prepare_commit_msg/replace_branch_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/prepare_commit_msg' describe Overcommit::Hook::PrepareCommitMsg::ReplaceBranch do def checkout_branch(branch) allow(Overcommit::GitRepo).to receive(:current_branch).and_return(branch) end def new_config(opts = {}) default = Overcommit::ConfigurationLoader.default_configuration return default if opts.empty? default.merge( Overcommit::Configuration.new( 'PrepareCommitMsg' => { 'ReplaceBranch' => opts.merge('enabled' => true) } ) ) end def new_context(config, argv) Overcommit::HookContext::PrepareCommitMsg.new(config, argv, StringIO.new) end def hook_for(config, context) described_class.new(config, context) end def add_file(name, contents) File.open(name, 'w') { |f| f.puts contents } end def remove_file(name) File.delete(name) end before { allow(Overcommit::Utils).to receive_message_chain(:log, :debug) } let(:config) { new_config } let(:normal_context) { new_context(config, ['COMMIT_EDITMSG']) } let(:message_context) { new_context(config, %w[COMMIT_EDITMSG message]) } let(:commit_context) { new_context(config, %w[COMMIT_EDITMSG commit HEAD]) } let(:merge_context) { new_context(config, %w[MERGE_MSG merge]) } let(:squash_context) { new_context(config, %w[SQUASH_MSG squash]) } let(:template_context) { new_context(config, ['template.txt', 'template']) } subject(:hook) { hook_for(config, normal_context) } describe '#run' do before { add_file 'COMMIT_EDITMSG', '' } after { remove_file 'COMMIT_EDITMSG' } context 'when the checked out branch matches the pattern' do before { checkout_branch '123-topic' } before { hook.run } it { is_expected.to pass } 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 before { checkout_branch 'topic-123' } before { hook.run } context 'with the default `skipped_commit_types`' do it { is_expected.to warn } end context 'when merging, and `skipped_commit_types` includes `merge`' do let(:config) { new_config('skipped_commit_types' => ['merge']) } subject(:hook) { hook_for(config, merge_context) } it { is_expected.to pass } end context 'when merging, and `skipped_commit_types` includes `template`' do let(:config) { new_config('skipped_commit_types' => ['template']) } subject(:hook) { hook_for(config, template_context) } it { is_expected.to pass } end context 'when merging, and `skipped_commit_types` includes `message`' do let(:config) { new_config('skipped_commit_types' => ['message']) } subject(:hook) { hook_for(config, message_context) } it { is_expected.to pass } end context 'when merging, and `skipped_commit_types` includes `commit`' do let(:config) { new_config('skipped_commit_types' => ['commit']) } subject(:hook) { hook_for(config, commit_context) } it { is_expected.to pass } end context 'when merging, and `skipped_commit_types` includes `squash`' do let(:config) { new_config('skipped_commit_types' => ['squash']) } subject(:hook) { hook_for(config, squash_context) } it { is_expected.to pass } end end context 'when the replacement text points to a valid filename' do before { checkout_branch '123-topic' } before { add_file 'replacement_text.txt', 'FOO' } after { remove_file 'replacement_text.txt' } let(:config) { new_config('replacement_text' => 'replacement_text.txt') } let(:normal_context) { new_context(config, ['COMMIT_EDITMSG']) } subject(:hook) { hook_for(config, normal_context) } before { hook.run } it { is_expected.to pass } let(:commit_msg) { File.read('COMMIT_EDITMSG') } it 'uses the file contents as the replacement text' do expect(commit_msg).to eq(File.read('replacement_text.txt')) end end end end ================================================ FILE: spec/overcommit/hook_context/base_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::HookContext::Base do let(:config) { double('config') } let(:args) { [] } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#hook_class_name' do subject { context.hook_class_name } it 'returns the short class name of the context' do subject.should == 'Base' end end describe '#input_lines' do subject { context.input_lines } before do input.stub(:read).and_return("line 1\nline 2\n") end it { should == ['line 1', 'line 2'] } end describe '#post_fail_message' do subject { context.post_fail_message } it { should be_nil } end end ================================================ FILE: spec/overcommit/hook_context/commit_msg_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/commit_msg' describe Overcommit::HookContext::CommitMsg do let(:comment_char) { '#' } let(:config) { double('config') } let(:args) { [commit_message_file] } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } before do Overcommit::GitConfig.stub(:comment_character).and_return(comment_char) end let(:commit_msg) do [ '# Please enter the commit message for your changes.', 'Some commit message', '# On branch master', 'diff --git a/file b/file', 'index 4ae1030..342a117 100644', '--- a/file', '+++ b/file', ] end let(:commit_message_file) do Tempfile.new('commit-message').tap do |file| file.write(commit_msg.join("\n")) file.fsync end.path end describe '#commit_message' do subject { context.commit_message } it 'strips comments and trailing diff' do subject.should == "Some commit message\n" end context 'with alternate comment character' do let(:comment_char) { '!' } let(:commit_msg) do [ '! Please enter the commit message for your changes.', 'Some commit message', '! On branch master', 'diff --git a/file b/file', 'index 4ae1030..342a117 100644', '--- a/file', '+++ b/file', ] end it 'strips comments and trailing diff' do subject.should == "Some commit message\n" end end end describe '#commit_message_lines' do subject { context.commit_message_lines } it 'strips comments and trailing diff' do subject.should == ["Some commit message\n"] end end describe '#empty_message?' do subject { context.empty_message? } context 'when commit message is empty' do let(:commit_msg) { [] } it { should == true } end context 'when commit message contains only whitespace' do let(:commit_msg) { [' '] } it { should == true } end context 'when commit message is not empty' do let(:commit_msg) { ['Some commit message'] } it { should == false } end end describe '#post_fail_message' do subject { context.post_fail_message } it 'returns printable log of commit message' do subject.should start_with "Failed commit message:\nSome commit message\n" end it 'returns the command to run to restore the commit message' do subject.should end_with "git commit --edit --file=#{commit_message_file}" end end describe '#amendment?' do subject { context.amendment? } before do Overcommit::Utils.stub(:parent_command).and_return(command) end context 'when amending a commit using `git commit --amend`' do let(:command) { 'git commit --amend' } it { should == true } end context 'when the parent command contains invalid byte sequence' do let(:command) { "git commit --amend -m \xE3M^AM^B" } it { should == true } end context 'when amending a commit using a git alias' do around do |example| repo do `git config alias.amend "commit --amend"` `git config alias.other-amend "commit --amend"` example.run end end context 'when using one of multiple aliases' do let(:command) { 'git amend' } it { should == true } end context 'when using another of multiple aliases' do let(:command) { 'git other-amend' } it { should == true } end end context 'when not amending a commit' do context 'using `git commit`' do let(:command) { 'git commit' } it { should == false } end context 'using a git alias containing "--amend"' do let(:command) { 'git no--amend' } around do |example| repo do `git config alias.no--amend commit` example.run end end it { should == false } end end end describe '#setup_environment' do subject { context.setup_environment } context 'when there are no staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'other-tracked-file', append: true) example.run end end it 'keeps already-committed files' do subject File.open('tracked-file', 'r').read.should == "Hello World\n" end it 'does not keep unstaged changes' do subject File.open('other-tracked-file', 'r').read.should == "Hello Other World\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when there are staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'tracked-file', append: true) echo('Some more text', 'other-tracked-file', append: true) `git add tracked-file` echo('Yet some more text', 'tracked-file', append: true) example.run end end it 'keeps staged changes' do subject File.open('tracked-file', 'r').read.should == "Hello World\nSome more text\n" end it 'does not keep unstaged changes' do subject File.open('other-tracked-file', 'r').read.should == "Hello Other World\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when all changes have been staged' do around do |example| repo do echo('Hello World', 'tracked-file') `git add tracked-file` `git commit -m "Add tracked-file"` echo('Hello Other World', 'other-tracked-file') `git add other-tracked-file` example.run end end it 'does not stash changes' do expect(context.private_methods).to include :stash_changes expect(context).not_to receive(:stash_changes) subject end end context 'when renaming a file during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch 'some-file' `git add some-file` `git commit -m "Add file"` `git mv some-file renamed-file` example.run end end before do context.stub(:amendment?).and_return(true) end it 'does not try to update modification time of the old non-existent file' do File.should_receive(:mtime).with(/renamed-file/) File.should_not_receive(:mtime).with(/some-file/) subject end end context 'when only a submodule change is staged' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` `git submodule foreach "git config --local commit.gpgsign false"` `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` `git add sub` example.run end end it 'keeps staged submodule change' do `git config diff.submodule short` expect { subject }.to_not change { (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? }.from(false) end end # Git cannot track Windows symlinks unless Overcommit::OS.windows? context 'when a broken symlink is staged' do around do |example| repo do Overcommit::Utils::FileUtils.symlink('non-existent-file', 'symlink') `git add symlink` example.run end end it 'does not attempt to update/restore the modification time of the file' do File.should_not_receive(:mtime) File.should_not_receive(:utime) subject end end end end describe '#cleanup_environment' do subject { context.cleanup_environment } before do context.setup_environment end context 'when there were no staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'other-tracked-file', append: true) example.run end end it 'restores the unstaged changes' do subject File.open('other-tracked-file', 'r').read. should == "Hello Other World\nSome more text\n" end it 'keeps already-committed files' do subject File.open('tracked-file', 'r').read.should == "Hello World\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when there were staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'tracked-file', append: true) echo('Some more text', 'other-tracked-file', append: true) `git add tracked-file` echo('Yet some more text', 'tracked-file', append: true) example.run end end it 'restores the unstaged changes' do subject File.open('tracked-file', 'r').read. should == "Hello World\nSome more text\nYet some more text\n" end it 'keeps staged changes' do subject `git show :tracked-file`.should == "Hello World\nSome more text\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when all changes were staged' do around do |example| repo do echo('Hello World', 'tracked-file') `git add tracked-file` `git commit -m "Add tracked-file"` echo('Hello Other World', 'other-tracked-file') `git add other-tracked-file` example.run end end it 'does not touch the working tree' do expect(context.private_methods).to include :clear_working_tree expect(context.private_methods).to include :restore_working_tree expect(context).not_to receive(:clear_working_tree) expect(context).not_to receive(:restore_working_tree) subject end end context 'when there were deleted files' do around do |example| repo do echo('Hello World', 'tracked-file') `git add tracked-file` `git commit -m "Add tracked-file"` `git rm tracked-file` example.run end end it 'deletes the file' do subject File.exist?('tracked-file').should == false end end context 'when only a submodule change was staged' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` `git submodule foreach "git config --local commit.gpgsign false"` `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` `git add sub` example.run end end it 'keeps staged submodule change' do `git config diff.submodule short` expect { subject }.to_not change { (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? }.from(false) end end context 'when submodule changes were staged along with other changes' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` `git submodule foreach "git config --local commit.gpgsign false"` `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` echo('Hello Again', 'tracked-file') `git add sub tracked-file` example.run end end it 'keeps staged submodule change' do `git config diff.submodule short` expect { subject }.to_not change { (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? }.from(false) end it 'keeps staged file change' do subject `git show :tracked-file`.should == "Hello Again\n" end end context 'when a submodule removal was staged' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` `git rm sub` example.run end end it 'does not leave behind an empty submodule directory' do subject File.exist?('sub').should == false end end end describe '#modified_files' do subject { context.modified_files } before do context.stub(:amendment?).and_return(false) end it 'does not include submodules' do submodule = repo do touch 'foo' `git add foo` `git commit -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` expect(subject).to_not include File.expand_path('test-sub') end end context 'when no files were staged' do around do |example| repo do example.run end end it { should be_empty } end context 'when files were added' do around do |example| repo do touch('some-file') `git add some-file` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were modified' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` echo('Hello', 'some-file') `git add some-file` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were deleted' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git rm some-file` example.run end end it { should be_empty } end context 'when amending last commit' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` touch('other-file') `git add other-file` example.run end end before do context.stub(:amendment?).and_return(true) end it { should =~ [File.expand_path('some-file'), File.expand_path('other-file')] } end context 'when renaming a file during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch 'some-file' `git add some-file` `git commit -m "Add file"` `git mv some-file renamed-file` example.run end end before do context.stub(:amendment?).and_return(true) end it 'does not include the old file name in the list of modified files' do subject.should_not include File.expand_path('some-file') end end # Git cannot track Windows symlinks unless Overcommit::OS.windows? context 'when changing a symlink to a directory during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` FileUtils.mkdir 'some-directory' symlink('some-directory', 'some-symlink') `git add some-symlink some-directory` `git commit -m "Add file"` `git rm some-symlink` FileUtils.mkdir 'some-symlink' touch File.join('some-symlink', 'another-file') `git add some-symlink` example.run end end before do context.stub(:amendment?).and_return(true) end it 'does not include the directory in the list of modified files' do subject.should_not include File.expand_path('some-symlink') end end context 'when breaking a symlink during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` FileUtils.mkdir 'some-directory' touch File.join('some-directory', 'some-file') symlink('some-directory', 'some-symlink') `git add some-symlink some-directory` `git commit -m "Add file"` `git rm -rf some-directory` example.run end end before do context.stub(:amendment?).and_return(true) end it 'still includes the broken symlink in the list of modified files' do subject.should include File.expand_path('some-symlink') end end end end describe '#modified_lines_in_file' do let(:modified_file) { 'some-file' } subject { context.modified_lines_in_file(modified_file) } before do context.stub(:amendment?).and_return(false) end context 'when file contains a trailing newline' do around do |example| repo do File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` example.run end end it { should == Set.new(1..3) } end context 'when file does not contain a trailing newline' do around do |example| repo do File.open(modified_file, 'w') do |f| (1..2).each { |i| f.write("#{i}\n") } f.write(3) end `git add #{modified_file}` example.run end end it { should == Set.new(1..3) } end context 'when amending last commit' do around do |example| repo do File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` `git commit -m "Add files"` File.open(modified_file, 'a') { |f| f.puts 4 } `git add #{modified_file}` example.run end end before do context.stub(:amendment?).and_return(true) end it { should == Set.new(1..4) } end end end ================================================ FILE: spec/overcommit/hook_context/diff_spec.rb ================================================ # 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 ================================================ FILE: spec/overcommit/hook_context/post_checkout_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/post_checkout' describe Overcommit::HookContext::PostCheckout do let(:config) { double('config') } let(:args) { [previous_head, new_head, branch_flag] } let(:input) { double('input') } let(:previous_head) { random_hash } let(:new_head) { random_hash } let(:branch_flag) { '1' } let(:context) { described_class.new(config, args, input) } describe '#previous_head' do subject { context.previous_head } it { should == previous_head } end describe '#new_head' do subject { context.new_head } it { should == new_head } end describe '#branch_checkout?' do subject { context.branch_checkout? } context 'when the flag is 0' do let(:branch_flag) { '0' } it { should == false } end context 'when the flag is 1' do it { should == true } end end describe '#file_checkout?' do subject { context.file_checkout? } context 'when the flag is 0' do let(:branch_flag) { '0' } it { should == true } end context 'when the flag is 1' do it { should == false } end end describe '#modified_files' do subject { context.modified_files } let(:new_head) { 'HEAD' } let(:previous_head) { 'HEAD~' } it 'does not include submodules' do submodule = repo do touch 'foo' `git add foo` `git commit -m "Initial commit"` end repo do `git commit --allow-empty -m "Initial commit"` `git submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` `git commit -m "Add submodule"` expect(subject).to_not include File.expand_path('test-sub') end end context 'when no files were modified' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git commit --allow-empty -m "Another commit"` example.run end end it { should be_empty } end context 'when files were added' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch('some-file') `git add some-file` `git commit -m "Add file"` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were modified' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` echo('Hello', 'some-file') `git add some-file` `git commit -m "Modify file"` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were deleted' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git rm some-file` `git commit -m "Delete file"` example.run end end it { should be_empty } end context 'when files were renamed' do around do |example| repo do touch 'some-file' `git add some-file` `git commit -m "Add file"` `git mv some-file renamed-file` `git commit -m "Rename file"` example.run end end it { should == [File.expand_path('renamed-file')] } end end end ================================================ FILE: spec/overcommit/hook_context/post_commit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/post_commit' describe Overcommit::HookContext::PostCommit do let(:config) { double('config') } let(:args) { [] } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#modified_files' do subject { context.modified_files } it 'does not include submodules' do 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 -m "Initial commit"` expect(subject).to_not include File.expand_path('test-sub') end end context 'when no files were staged' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` example.run end end it { should be_empty } end context 'when files were added' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were modified' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` echo('Hello', 'some-file') `git add some-file` `git commit -m "Modify some-file"` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were deleted' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git rm some-file` `git commit -m "Delete some-file"` example.run end end it { should be_empty } 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 File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` `git commit -m "Add files"` example.run end end it { should == Set.new(1..3) } end context 'when file does not contain a trailing newline' do around do |example| repo do File.open(modified_file, 'w') do |f| (1..2).each { |i| f.write("#{i}\n") } f.write(3) end `git add #{modified_file}` `git commit -m "Add files"` example.run end end it { should == Set.new(1..3) } end end describe '#initial_commit?' do subject { context.initial_commit? } context 'when a previous commit exists' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git commit --allow-empty -m "Another commit"` example.run end end it { should == false } end context 'when no previous commit exists' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` example.run end end it { should == true } end end end ================================================ FILE: spec/overcommit/hook_context/post_merge_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/post_merge' describe Overcommit::HookContext::PostMerge do let(:config) { double('config') } let(:args) { [] } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#squash?' do subject { context.squash? } context 'when the merge is made using --squash' do let(:args) { [1] } around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git commit --allow-empty -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --squash child` example.run end end it { should == true } end context 'when the merge is made without --squash' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git commit --allow-empty -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should == false } end end describe '#merge_commit?' do subject { context.merge_commit? } context 'when the merge is made using --squash' do let(:args) { [1] } around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git commit --allow-empty -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --squash child` example.run end end it { should == false } end context 'when the merge is made without --squash' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git commit --allow-empty -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should == true } end end describe '#modified_files' do subject { context.modified_files } it 'does not include submodules' do submodule = repo do touch 'foo' `git add foo` `git commit -m "Initial commit"` end repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` `git commit -m "Add submodule"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` expect(subject).to_not include File.expand_path('test-sub') end end context 'when no files were staged' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git commit --allow-empty -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should be_empty } end context 'when files were added' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` touch('some-file') `git add some-file` `git commit -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were modified' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` echo('Hello', 'some-file') `git add some-file` `git commit -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were deleted' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` `git rm some-file` `git commit -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should be_empty } end context 'when the merge is made using --squash' do let(:args) { [1] } around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` touch('some-file') `git add some-file` `git commit -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --squash child` example.run end end it { should == [File.expand_path('some-file')] } 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 `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` `git commit -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` 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 `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 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 "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --no-ff --no-edit child` example.run end end it { should == Set.new(1..3) } end context 'when the merge is made using --squash' do let(:args) { [1] } around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git checkout -b child > #{File::NULL} 2>&1` File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` `git commit -m "Branch commit"` `git checkout master > #{File::NULL} 2>&1` `git merge --squash child` example.run end end it { should == Set.new(1..3) } end end end ================================================ FILE: spec/overcommit/hook_context/post_rewrite_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/post_rewrite' describe Overcommit::HookContext::PostRewrite do let(:config) { double('config') } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#amend?' do subject { context.amend? } context 'when rewrite was triggered by amend' do let(:args) { ['amend'] } it { should == true } end context 'when rewrite was triggered by rebase' do let(:args) { ['rebase'] } it { should == false } end end describe '#rebase?' do subject { context.rebase? } context 'when rewrite was triggered by amend' do let(:args) { ['amend'] } it { should == false } end context 'when rewrite was triggered by rebase' do let(:args) { ['rebase'] } it { should == true } end end describe '#rewritten_commits' do subject(:rewritten_commits) { context.rewritten_commits } let(:old_hash_1) { random_hash } let(:new_hash_1) { random_hash } let(:old_hash_2) { random_hash } let(:new_hash_2) { random_hash } context 'when rewrite was triggered by amend' do let(:args) { ['amend'] } before do input.stub(:read).and_return("#{old_hash_1} #{new_hash_1}\n") end it 'should parse rewritten commit info from the input' do rewritten_commits.length.should == 1 rewritten_commits[0].old_hash.should == old_hash_1 rewritten_commits[0].new_hash.should == new_hash_1 end end context 'when rewrite was triggered by rebase' do let(:args) { ['rebase'] } before do input.stub(:read).and_return([ "#{old_hash_1} #{new_hash_1}", "#{old_hash_2} #{new_hash_2}" ].join("\n")) end it 'should parse rewritten commit info from the input' do rewritten_commits.length.should == 2 rewritten_commits[0].old_hash.should == old_hash_1 rewritten_commits[0].new_hash.should == new_hash_1 rewritten_commits[1].old_hash.should == old_hash_2 rewritten_commits[1].new_hash.should == new_hash_2 end end end describe '#modified_files' do subject { context.modified_files } before do context.stub(:rewritten_commits).and_return(rewritten_commits) end context 'when rewrite was triggered by amend' do let(:args) { ['amend'] } let(:rewritten_commits) { [double(old_hash: 'HEAD@{1}', new_hash: 'HEAD')] } it 'does not include submodules' do submodule = repo do touch 'foo' `git add foo` `git commit -m "Initial commit"` end repo do `git commit --allow-empty -m "Initial commit"` `git submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` `git commit --amend -m "Add submodule"` expect(subject).to_not include File.expand_path('test-sub') end end context 'when no files were modified' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` `git commit --amend --allow-empty -m "Another commit"` example.run end end it { should be_empty } end context 'when files were added' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch('some-file') `git add some-file` `git commit --amend -m "Add file"` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were modified' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` echo('Hello', 'some-file') `git add some-file` `git commit --amend -m "Modify file"` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were deleted' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git rm some-file` `git commit --amend --allow-empty -m "Delete file"` example.run end end it { should be_empty } end context 'when files were renamed' do around do |example| repo do touch 'some-file' `git add some-file` `git commit -m "Add file"` `git mv some-file renamed-file` `git commit --amend -m "Rename file"` example.run end end it { should == [File.expand_path('renamed-file')] } end # Git cannot track Windows symlinks unless Overcommit::OS.windows? context 'when changing a symlink to a directory during an amendment' do around do |example| repo do FileUtils.mkdir 'some-directory' symlink('some-directory', 'some-symlink') `git add some-symlink some-directory` `git commit -m "Add file"` `git rm some-symlink` FileUtils.mkdir 'some-symlink' touch File.join('some-symlink', 'another-file') `git add some-symlink` `git commit --amend -m "Change symlink to directory"` example.run end end it 'does not include the directory in the list of modified files' do subject.should_not include File.expand_path('some-symlink') end end context 'when breaking a symlink during an amendment' do around do |example| repo do FileUtils.mkdir 'some-directory' touch File.join('some-directory', 'some-file') symlink('some-directory', 'some-symlink') `git add some-symlink some-directory` `git commit -m "Add file"` `git rm -rf some-directory` `git commit --amend -m "Remove directory to break symlink"` example.run end end it 'does not include the broken symlink in the list of modified files' do subject.should_not include File.expand_path('some-symlink') end end end end end end ================================================ FILE: spec/overcommit/hook_context/pre_commit_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/pre_commit' describe Overcommit::HookContext::PreCommit do let(:config) { double('config') } let(:args) { [] } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#amendment?' do subject { context.amendment? } before do Overcommit::Utils.stub(:parent_command).and_return(command) end context 'when amending a commit using `git commit --amend`' do let(:command) { 'git commit --amend' } it { should == true } end context 'when the parent command contains invalid byte sequence' do let(:command) { "git commit --amend -m \xE3M^AM^B" } it { should == true } end context 'when amending a commit using a git alias' do around do |example| repo do `git config alias.amend "commit --amend"` `git config alias.other-amend "commit --amend"` example.run end end context 'when using one of multiple aliases' do let(:command) { 'git amend' } it { should == true } end context 'when using another of multiple aliases' do let(:command) { 'git other-amend' } it { should == true } end end context 'when not amending a commit' do context 'using `git commit`' do let(:command) { 'git commit' } it { should == false } end context 'using a git alias containing "--amend"' do let(:command) { 'git no--amend' } around do |example| repo do `git config alias.no--amend commit` example.run end end it { should == false } end end end describe '#setup_environment' do subject { context.setup_environment } context 'when there are no staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'other-tracked-file', append: true) example.run end end it 'keeps already-committed files' do subject File.open('tracked-file', 'r').read.should == "Hello World\n" end it 'does not keep unstaged changes' do subject File.open('other-tracked-file', 'r').read.should == "Hello Other World\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when there are staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'tracked-file', append: true) echo('Some more text', 'other-tracked-file', append: true) `git add tracked-file` echo('Yet some more text', 'tracked-file', append: true) example.run end end it 'keeps staged changes' do subject File.open('tracked-file', 'r').read.should == "Hello World\nSome more text\n" end it 'does not keep unstaged changes' do subject File.open('other-tracked-file', 'r').read.should == "Hello Other World\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when all changes have been staged' do around do |example| repo do echo('Hello World', 'tracked-file') `git add tracked-file` `git commit -m "Add tracked-file"` echo('Hello Other World', 'other-tracked-file') `git add other-tracked-file` example.run end end it 'does not stash changes' do expect(context.private_methods).to include :stash_changes expect(context).not_to receive(:stash_changes) subject end end context 'when renaming a file during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch 'some-file' `git add some-file` `git commit -m "Add file"` `git mv some-file renamed-file` example.run end end before do context.stub(:amendment?).and_return(true) end it 'does not try to update modification time of the old non-existent file' do File.should_receive(:mtime).with(/renamed-file/) File.should_not_receive(:mtime).with(/some-file/) subject end end context 'when only a submodule change is staged' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` `git submodule foreach "git config --local commit.gpgsign false"` `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` `git add sub` example.run end end it 'keeps staged submodule change' do `git config diff.submodule short` expect { subject }.to_not change { (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? }.from(false) end end # Git cannot track Windows symlinks unless Overcommit::OS.windows? context 'when a broken symlink is staged' do around do |example| repo do Overcommit::Utils::FileUtils.symlink('non-existent-file', 'symlink') `git add symlink` example.run end end it 'does not attempt to update/restore the modification time of the file' do File.should_not_receive(:mtime) File.should_not_receive(:utime) subject end end end end describe '#cleanup_environment' do subject { context.cleanup_environment } before do context.setup_environment end context 'when there were no staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'other-tracked-file', append: true) example.run end end it 'restores the unstaged changes' do subject File.open('other-tracked-file', 'r').read. should == "Hello Other World\nSome more text\n" end it 'keeps already-committed files' do subject File.open('tracked-file', 'r').read.should == "Hello World\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when there were staged changes' do around do |example| repo do echo('Hello World', 'tracked-file') echo('Hello Other World', 'other-tracked-file') `git add tracked-file other-tracked-file` `git commit -m "Add tracked-file and other-tracked-file"` echo('Hello Again', 'untracked-file') echo('Some more text', 'tracked-file', append: true) echo('Some more text', 'other-tracked-file', append: true) `git add tracked-file` echo('Yet some more text', 'tracked-file', append: true) example.run end end it 'restores the unstaged changes' do subject File.open('tracked-file', 'r').read. should == "Hello World\nSome more text\nYet some more text\n" end it 'keeps staged changes' do subject `git show :tracked-file`.should == "Hello World\nSome more text\n" end it 'keeps untracked files' do subject File.open('untracked-file', 'r').read.should == "Hello Again\n" end it 'keeps modification times the same' do sleep 1 expect { subject }.to_not change { [ File.mtime('tracked-file'), File.mtime('other-tracked-file'), File.mtime('untracked-file') ] } end end context 'when all changes were staged' do around do |example| repo do echo('Hello World', 'tracked-file') `git add tracked-file` `git commit -m "Add tracked-file"` echo('Hello Other World', 'other-tracked-file') `git add other-tracked-file` example.run end end it 'does not touch the working tree' do expect(context.private_methods).to include :clear_working_tree expect(context.private_methods).to include :restore_working_tree expect(context).not_to receive(:clear_working_tree) expect(context).not_to receive(:restore_working_tree) subject end end context 'when there were deleted files' do around do |example| repo do echo('Hello World', 'tracked-file') `git add tracked-file` `git commit -m "Add tracked-file"` `git rm tracked-file` example.run end end it 'deletes the file' do subject File.exist?('tracked-file').should == false end end context 'when only a submodule change was staged' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` `git submodule foreach "git config --local commit.gpgsign false"` `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` `git add sub` example.run end end it 'keeps staged submodule change' do `git config diff.submodule short` expect { subject }.to_not change { (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? }.from(false) end end context 'when submodule changes were staged along with other changes' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` echo('Hello World', 'sub/submodule-file') `git submodule foreach "git add submodule-file" < #{File::NULL}` `git submodule foreach "git config --local commit.gpgsign false"` `git submodule foreach "git commit -m \\"Another commit\\"" < #{File::NULL}` echo('Hello Again', 'tracked-file') `git add sub tracked-file` example.run end end it 'keeps staged submodule change' do `git config diff.submodule short` expect { subject }.to_not change { (`git diff --cached` =~ /-Subproject commit[\s\S]*\+Subproject commit/).nil? }.from(false) end it 'keeps staged file change' do subject `git show :tracked-file`.should == "Hello Again\n" end end context 'when a submodule removal was staged' do around do |example| submodule = repo do `git commit --allow-empty -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} sub > #{File::NULL} 2>&1` `git commit -m "Add submodule"` `git rm sub` example.run end end it 'does not leave behind an empty submodule directory' do subject File.exist?('sub').should == false end end end describe '#modified_files' do subject { context.modified_files } before do context.stub(:amendment?).and_return(false) end it 'does not include submodules' do submodule = repo do touch 'foo' `git add foo` `git commit -m "Initial commit"` end repo do `git -c protocol.file.allow=always submodule add #{submodule} test-sub 2>&1 > #{File::NULL}` expect(subject).to_not include File.expand_path('test-sub') end end context 'when no files were staged' do around do |example| repo do example.run end end it { should be_empty } end context 'when files were added' do around do |example| repo do touch('some-file') `git add some-file` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were modified' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` echo('Hello', 'some-file') `git add some-file` example.run end end it { should == [File.expand_path('some-file')] } end context 'when files were deleted' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` `git rm some-file` example.run end end it { should be_empty } end context 'when amending last commit' do around do |example| repo do touch('some-file') `git add some-file` `git commit -m "Initial commit"` touch('other-file') `git add other-file` example.run end end before do context.stub(:amendment?).and_return(true) end it { should =~ [File.expand_path('some-file'), File.expand_path('other-file')] } end context 'when renaming a file during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` touch 'some-file' `git add some-file` `git commit -m "Add file"` `git mv some-file renamed-file` example.run end end before do context.stub(:amendment?).and_return(true) end it 'does not include the old file name in the list of modified files' do subject.should_not include File.expand_path('some-file') end end # Git cannot track Windows symlinks unless Overcommit::OS.windows? context 'when changing a symlink to a directory during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` FileUtils.mkdir 'some-directory' symlink('some-directory', 'some-symlink') `git add some-symlink some-directory` `git commit -m "Add file"` `git rm some-symlink` FileUtils.mkdir 'some-symlink' touch File.join('some-symlink', 'another-file') `git add some-symlink` example.run end end before do context.stub(:amendment?).and_return(true) end it 'does not include the directory in the list of modified files' do subject.should_not include File.expand_path('some-symlink') end end context 'when breaking a symlink during an amendment' do around do |example| repo do `git commit --allow-empty -m "Initial commit"` FileUtils.mkdir 'some-directory' touch File.join('some-directory', 'some-file') symlink('some-directory', 'some-symlink') `git add some-symlink some-directory` `git commit -m "Add file"` `git rm -rf some-directory` example.run end end before do context.stub(:amendment?).and_return(true) end it 'still includes the broken symlink in the list of modified files' do subject.should include File.expand_path('some-symlink') end end end end describe '#modified_lines_in_file' do let(:modified_file) { 'some-file' } subject { context.modified_lines_in_file(modified_file) } before do context.stub(:amendment?).and_return(false) end context 'when file contains a trailing newline' do around do |example| repo do File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` example.run end end it { should == Set.new(1..3) } end context 'when file does not contain a trailing newline' do around do |example| repo do File.open(modified_file, 'w') do |f| (1..2).each { |i| f.write("#{i}\n") } f.write(3) end `git add #{modified_file}` example.run end end it { should == Set.new(1..3) } end context 'when amending last commit' do around do |example| repo do File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` `git commit -m "Add files"` File.open(modified_file, 'a') { |f| f.puts 4 } `git add #{modified_file}` example.run end end before do context.stub(:amendment?).and_return(true) end it { should == Set.new(1..4) } end end end ================================================ FILE: spec/overcommit/hook_context/pre_push_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/pre_push' describe Overcommit::HookContext::PrePush do let(:config) { double('config') } let(:args) { [remote_name, remote_url] } let(:input) { double('input') } let(:remote_name) { 'origin' } let(:remote_url) { 'git@github.com:brigade/overcommit.git' } let(:context) { described_class.new(config, args, input) } describe '#remote_name' do subject { context.remote_name } it { should == remote_name } end describe '#remote_url' do subject { context.remote_url } it { should == remote_url } end describe '#remote_ref_deletion?' do subject { context.remote_ref_deletion? } let(:standard_input) { "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}\n" } before do input.stub(:read).and_return(standard_input) end context 'when pushing new branch to remote ref' do let(:local_ref) { 'refs/heads/test' } let(:local_sha1) { '' } let(:remote_ref) { 'refs/heads/test' } let(:remote_sha1) { '0' * 40 } it { should == false } end context 'when pushing update to remote ref' do let(:local_ref) { 'refs/heads/test' } let(:local_sha1) { '' } let(:remote_ref) { 'refs/heads/test' } let(:remote_sha1) { random_hash } it { should == false } end context 'when deleting remote ref' do let(:local_ref) { '(deleted)' } let(:local_sha1) { '' } let(:remote_ref) { 'refs/heads/test' } let(:remote_sha1) { random_hash } it { should == true } end context 'when no standard input is provided' do let(:standard_input) { '' } it { should == false } end end describe '#pushed_refs' do subject(:pushed_refs) { context.pushed_refs } let(:local_ref) { 'refs/heads/master' } let(:local_sha1) { random_hash } let(:remote_ref) { 'refs/heads/master' } let(:remote_sha1) { random_hash } before do input.stub(:read).and_return("#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}\n") end it 'parses commit info from the input' do pushed_refs.length.should == 1 pushed_refs.each do |pushed_ref| pushed_ref.local_ref.should == local_ref pushed_ref.local_sha1.should == local_sha1 pushed_ref.remote_ref.should == remote_ref pushed_ref.remote_sha1.should == remote_sha1 end end end describe '#modified_files' do subject { context.modified_files } let(:remote_repo) do repo do touch 'update-me' echo 'update', 'update-me' touch 'delete-me' echo 'delete', 'delete-me' `git add . 2>&1 > #{File::NULL}` `git commit -m "Initial commit" 2>&1 > #{File::NULL}` end end context 'when current branch has tracking branch' do let(:local_ref) { 'refs/heads/project-branch' } let(:local_sha1) { get_sha1(local_ref) } let(:remote_ref) { 'refs/remotes/origin/master' } let(:remote_sha1) { get_sha1(remote_ref) } let(:input) do double('input', read: "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}\n") end it 'has modified files based on tracking branch' do repo do `git remote add origin file://#{remote_repo}` `git fetch origin 2>&1 > #{File::NULL} && git reset --hard origin/master` `git checkout -b project-branch 2>&1 > #{File::NULL}` `git push -u origin project-branch 2>&1 > #{File::NULL}` touch 'added-1' echo 'add', 'added-1' echo 'append', 'update-me' FileUtils.rm 'delete-me' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1" 2>&1 > #{File::NULL}` touch 'added-2' echo 'add', 'added-2' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 2" 2>&1 > #{File::NULL}` should == %w[added-1 added-2 update-me].map { |file| File.expand_path(file) } should_not include(*%w[delete-me].map { |file| File.expand_path(file) }) end end end context 'when pushing multiple branches at once' do let(:local_ref_1) { 'refs/heads/project-branch-1' } let(:local_sha1_1) { get_sha1(local_ref_1) } let(:local_ref_2) { 'refs/heads/project-branch-2' } let(:local_sha1_2) { get_sha1(local_ref_2) } let(:remote_ref) { 'refs/remotes/origin/master' } let(:remote_sha1) { get_sha1(remote_ref) } let(:input) do double('input', read: ref_ranges) end let(:ref_ranges) do [ "#{local_ref_1} #{local_sha1_1} #{remote_ref} #{remote_sha1}\n", "#{local_ref_2} #{local_sha1_2} #{remote_ref} #{remote_sha1}\n" ].join end it 'has modified files based on multiple tracking branches' do repo do `git remote add origin file://#{remote_repo}` `git fetch origin 2>&1 > #{File::NULL} && git reset --hard origin/master` `git checkout -b project-branch-1 2>&1 > #{File::NULL}` `git push -u origin project-branch-1 2>&1 > #{File::NULL}` touch 'added-1' echo 'add', 'added-1' echo 'append', 'update-me' FileUtils.rm 'delete-me' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1" 2>&1 > #{File::NULL}` `git checkout master 2>&1 > #{File::NULL}` `git checkout -b project-branch-2 2>&1 > #{File::NULL}` `git push -u origin project-branch-2 2>&1 > #{File::NULL}` echo 'append', 'update-me' touch 'added-2' echo 'add', 'added-2' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 2" 2>&1 > #{File::NULL}` should == %w[added-1 update-me added-2].map { |file| File.expand_path(file) } should_not include(*%w[delete-me].map { |file| File.expand_path(file) }) end end end context 'when current branch has no tracking branch' do let(:local_ref) { 'refs/heads/project-branch' } let(:local_sha1) { get_sha1(local_ref) } let(:remote_ref) { 'refs/heads/master' } let(:remote_sha1) { get_sha1(remote_ref) } let(:input) do double('input', read: "#{local_ref} #{local_sha1} #{remote_ref} #{remote_sha1}\n") end it 'has modified files based on parent branch' do repo do `git remote add origin file://#{remote_repo}` `git fetch origin 2>&1 > #{File::NULL} && git reset --hard origin/master` `git checkout -b project-branch 2>&1 > #{File::NULL}` touch 'added-1' echo 'add', 'added-1' echo 'append', 'update-me' FileUtils.rm 'delete-me' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1" 2>&1 > #{File::NULL}` touch 'added-2' echo 'add', 'added-2' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 2" 2>&1 > #{File::NULL}` should == %w[added-1 added-2 update-me].map { |file| File.expand_path(file) } should_not include(*%w[delete-me].map { |file| File.expand_path(file) }) end end end end describe '#modified_lines_in_file' do subject { context.modified_lines_in_file(file) } let(:local_ref_1) { 'refs/heads/project-branch-1' } let(:local_sha1_1) { get_sha1(local_ref_1) } let(:local_ref_2) { 'refs/heads/project-branch-2' } let(:local_sha1_2) { get_sha1(local_ref_2) } let(:remote_ref) { 'refs/remotes/origin/master' } let(:remote_sha1) { get_sha1(remote_ref) } let(:input) do double('input', read: ref_ranges) end let(:ref_ranges) do [ "#{local_ref_1} #{local_sha1_1} #{remote_ref} #{remote_sha1}\n", "#{local_ref_2} #{local_sha1_2} #{remote_ref} #{remote_sha1}\n" ].join end let(:remote_repo) do repo do touch 'initial_file' echo 'initial', 'initial_file' `git add . 2>&1 > #{File::NULL}` `git commit -m "Initial commit" 2>&1 > #{File::NULL}` end end context 'when updating a file' do let(:file) { File.expand_path('initial_file') } it 'has modified lines in file' do repo do `git remote add origin file://#{remote_repo}` `git fetch origin 2>&1 > #{File::NULL} && git reset --hard origin/master` `git checkout -b project-branch-1 2>&1 > #{File::NULL}` `git push -u origin project-branch-1 2>&1 > #{File::NULL}` echo 'append-1', 'initial_file', append: true `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1 Commit 1" 2>&1 > #{File::NULL}` echo 'append-2', 'initial_file', append: true `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1 Commit 2" 2>&1 > #{File::NULL}` `git checkout -b project-branch-2 2>&1 > #{File::NULL}` `git push -u origin project-branch-2 2>&1 > #{File::NULL}` echo 'append-3', 'initial_file', append: true `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 2 Commit 1" 2>&1 > #{File::NULL}` should == [2, 3, 4].to_set end end end context 'when adding a file' do let(:file) { File.expand_path('new_file') } it 'has modified lines in file' do repo do `git remote add origin file://#{remote_repo}` `git fetch origin 2>&1 > #{File::NULL} && git reset --hard origin/master` `git checkout -b project-branch-1 2>&1 > #{File::NULL}` `git push -u origin project-branch-1 2>&1 > #{File::NULL}` touch 'new_file' echo 'append-1', 'new_file', append: true `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1 Commit 1" 2>&1 > #{File::NULL}` echo 'append-2', 'new_file', append: true `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1 Commit 2" 2>&1 > #{File::NULL}` `git checkout -b project-branch-2 2>&1 > #{File::NULL}` `git push -u origin project-branch-2 2>&1 > #{File::NULL}` echo 'append-3', 'new_file', append: true `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 2 Commit 1" 2>&1 > #{File::NULL}` should == [1, 2, 3].to_set end end end context 'when deleting a file' do let(:file) { File.expand_path('initial_file') } let(:ref_ranges) do "#{local_ref_1} #{local_sha1_1} #{remote_ref} #{remote_sha1}\n" end it 'has modified lines in file' do repo do `git remote add origin file://#{remote_repo}` `git fetch origin 2>&1 > #{File::NULL} && git reset --hard origin/master` `git checkout -b project-branch-1 2>&1 > #{File::NULL}` `git push -u origin project-branch-1 2>&1 > #{File::NULL}` FileUtils.rm 'initial_file' `git add . 2>&1 > #{File::NULL}` `git commit -m "Update Branch 1" 2>&1 > #{File::NULL}` should == [].to_set end end end end describe Overcommit::HookContext::PrePush::PushedRef do let(:local_ref) { 'refs/heads/master' } let(:remote_ref) { 'refs/heads/master' } let(:local_sha1) { random_hash } let(:remote_sha1) { random_hash } let(:pushed_ref) { described_class.new(local_ref, local_sha1, remote_ref, remote_sha1) } describe '#forced?' do subject { pushed_ref.forced? } context 'when creating a ref' do before do pushed_ref.stub(created?: true, deleted?: false) end it { should == false } end context 'when deleting a ref' do before do pushed_ref.stub(created?: false, deleted?: true) end it { should == false } end context 'when remote commits are not overwritten' do before do pushed_ref.stub(created?: false, deleted?: false, overwritten_commits: []) end it { should == false } end context 'when remote commits are overwritten' do before do pushed_ref.stub(created?: false, deleted?: false, overwritten_commits: [random_hash]) end it { should == true } end context 'when remote ref head does not exist locally' do let(:git_error_msg) { "fatal: bad object #{remote_sha1}" } before do pushed_ref.stub(created?: false, deleted?: false) result = double(success?: false, stderr: git_error_msg) Overcommit::Subprocess.stub(:spawn).and_return(result) end it 'should raise' do expect { subject }.to raise_error(Overcommit::Exceptions::GitRevListError, /#{git_error_msg}/) end end end describe '#created?' do subject { pushed_ref.created? } context 'when creating a ref' do before do pushed_ref.stub(:remote_sha1).and_return('0' * 40) end it { should == true } end context 'when not creating a ref' do before do pushed_ref.stub(:remote_sha1).and_return(random_hash) end it { should == false } end end describe '#deleted?' do subject { pushed_ref.deleted? } context 'when deleting a ref' do before do pushed_ref.stub(:local_sha1).and_return('0' * 40) end it { should == true } end context 'when not deleting a ref' do before do pushed_ref.stub(:local_sha1).and_return(random_hash) end it { should == false } end end describe '#destructive?' do subject { pushed_ref.destructive? } context 'when deleting a ref' do before do pushed_ref.stub(:deleted?).and_return(true) end it { should == true } end context 'when force-pushing a ref' do before do pushed_ref.stub(deleted?: false, forced?: true) end it { should == true } end context 'when not deleting or force-pushing a ref' do before do pushed_ref.stub(deleted?: false, forced?: false) end it { should == false } end end end end ================================================ FILE: spec/overcommit/hook_context/pre_rebase_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/pre_rebase' describe Overcommit::HookContext::PreRebase do let(:config) { double('config') } let(:args) { [upstream_branch, rebased_branch] } let(:upstream_branch) { 'master' } let(:rebased_branch) { 'topic' } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#upstream_branch' do subject { context.upstream_branch } it { should == upstream_branch } end describe '#rebased_branch' do subject { context.rebased_branch } it { should == rebased_branch } context 'when rebasing current branch' do let(:rebased_branch) { nil } let(:current_branch) { 'master' } around do |example| repo do `git checkout -b #{current_branch} > #{File::NULL} 2>&1` example.run end end it { should == current_branch } end end describe '#fast_forward?' do subject { context.fast_forward? } context 'when upstream branch is descendent from rebased branch' do before do context.stub(:rebased_commits).and_return([]) end it { should == true } end context 'when upstream branch is not descendent from rebased branch' do before do context.stub(:rebased_commits).and_return([random_hash]) end it { should == false } end end describe '#detached_head?' do subject { context.detached_head? } context 'when rebasing a detached HEAD' do let(:rebased_branch) { '' } it { should == true } end context 'when rebasing a branch' do let(:rebased_branch) { 'topic' } it { should == false } end end describe '#rebased_commits' do subject { context.rebased_commits } let(:base_branch) { 'master' } let(:topic_branch_1) { 'topic-1' } let(:topic_branch_2) { 'topic-2' } around do |example| repo do `git checkout -b #{base_branch} > #{File::NULL} 2>&1` `git commit --allow-empty -m "Initial Commit"` `git checkout -b #{topic_branch_1} > #{File::NULL} 2>&1` `git commit --allow-empty -m "Hello World"` `git checkout -b #{topic_branch_2} #{base_branch} > #{File::NULL} 2>&1` `git commit --allow-empty -m "Hello Again"` example.run end end context 'when upstream branch is descendent from rebased branch' do let(:upstream_branch) { topic_branch_1 } let(:rebased_branch) { base_branch } it { should be_empty } end context 'when upstream branch is not descendent from rebased branch' do let(:upstream_branch) { topic_branch_1 } let(:rebased_branch) { topic_branch_2 } it { should_not be_empty } end end end ================================================ FILE: spec/overcommit/hook_context/prepare_commit_msg_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/prepare_commit_msg' describe Overcommit::HookContext::PrepareCommitMsg do let(:config) { double('config') } let(:args) { [commit_message_filename, commit_message_source] } let(:commit_message_filename) { 'message-template.txt' } let(:commit_message_source) { :file } let(:commit) { 'SHA-1 here' } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#commit_message_filename' do subject { context.commit_message_filename } it { should == commit_message_filename } end describe '#commit_message_source' do subject { context.commit_message_source } it { should == commit_message_source } end end ================================================ FILE: spec/overcommit/hook_context/run_all_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'overcommit/hook_context/run_all' describe Overcommit::HookContext::RunAll do let(:config) { double('config') } let(:args) { [] } let(:input) { double('input') } let(:context) { described_class.new(config, args, input) } describe '#modified_files' do subject { context.modified_files } context 'when repo contains no files' do around do |example| repo do example.run end end it { should be_empty } end context 'when repo contains files' 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"` example.run end end it { should == %w[some-file some-other-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}` 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 File.open(modified_file, 'w') { |f| (1..3).each { |i| f.write("#{i}\n") } } `git add #{modified_file}` `git commit -m "Add files"` example.run end end it { should == Set.new(1..3) } end context 'when file does not contain a trailing newline' do around do |example| repo do File.open(modified_file, 'w') do |f| (1..2).each { |i| f.write("#{i}\n") } f.write(3) end `git add #{modified_file}` `git commit -m "Add files"` example.run end end it { should == Set.new(1..3) } end end end ================================================ FILE: spec/overcommit/hook_signer_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::HookSigner do describe '#signature_changed?' do let(:config) { double('config') } let(:context) { double('context') } let(:signer) do described_class.new('.git-hooks/pre_commit/some_path.rb', config, context) end let(:hook_config) { { 'enabled' => false } } let(:modified_hook_config) { hook_config } let(:hook_contents) { <<-RUBY } module Overcommit::Hook::PreCommit class SomeHook def run :pass end end end RUBY let(:modified_hook_contents) { hook_contents } subject { signer.signature_changed? } around do |example| repo do example.run end end before do Overcommit::GitRepo.stub(:tracked?).and_return(true) context.stub(:hook_class_name).and_return('PreCommit') context.stub(:hook_type_name).and_return('pre-commit') config.stub(:verify_signatures?).and_return(true) config.stub(:for_hook).and_return(hook_config) config.stub(:plugin_directory).and_return(Dir.pwd) signer.stub(:hook_contents).and_return(hook_contents) signer.update_signature! config.stub(:for_hook).and_return(modified_hook_config) signer.stub(:hook_contents).and_return(modified_hook_contents) end context 'when the hook code and config are the same' do it { should == false } context 'and the user has specified they wish to skip the hook' do let(:modified_hook_config) { hook_config.merge('skip' => true) } it { should == false } end end context 'when the hook code has changed' do let(:modified_hook_contents) { <<-RUBY } module Overcommit::Hook::PreCommit class SomeHook def run :fail # This line changed end end end RUBY it { should == true } end context 'when the hook config has changed' do let(:modified_hook_config) { { 'enabled' => true } } it { should == true } end end end ================================================ FILE: spec/overcommit/installer_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Installer do let(:logger) { Overcommit::Logger.silent } let(:installer) { described_class.new(logger) } def hook_files_installed?(hooks_dir) Overcommit::Utils.supported_hook_types.all? do |hook_type| hook_file = File.join(hooks_dir, hook_type) master_hook = File.join(hooks_dir, 'overcommit-hook') File.exist?(hook_file) && File.exist?(master_hook) && File.read(hook_file) == File.read(master_hook) end end describe '#run' do let(:options) { { action: :install } } subject { installer.run(target.to_s, options) } context 'when the target is not a directory' do let(:target) { Tempfile.new('some-file') } it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::InvalidGitRepo end end context 'when the target is not a git repo' do let(:target) { directory } it 'raises an error' do expect { subject }.to raise_error Overcommit::Exceptions::InvalidGitRepo end end context 'when the target is a git repo' do let(:target) { repo } let(:hooks_dir) { File.join(target, '.git', 'hooks') } let(:old_hooks_dir) { File.join(hooks_dir, 'old-hooks') } let(:master_hook) { File.join(hooks_dir, 'overcommit-hook') } context 'and an install is requested' do context 'and Overcommit hooks were not previously installed' do it 'installs the master hook into the hooks directory' do expect { subject }.to change { File.file?(master_hook) }.from(false).to(true) end it 'copies all supported hooks from the master hook' do expect { subject }.to change { hook_files_installed?(hooks_dir) }.from(false).to(true) end end context 'and Overcommit hooks were previously installed' do before do installer.run(target, action: :install) end it 'keeps the master hook' do expect { subject }.to_not change { File.file?(master_hook) }.from(true) end it 'maintains all copies of the master hook' do expect { subject }.to_not change { hook_files_installed?(hooks_dir) }.from(true) end end context 'and non-Overcommit hooks were previously installed' do let(:old_hooks) { %w[commit-msg pre-commit] } before do FileUtils.mkdir_p(hooks_dir) Dir.chdir(hooks_dir) do old_hooks.each { |hook_type| touch(hook_type) } end end it 'does not raise an error' do expect { subject }.to_not raise_error end it 'moves them to a subdirectory' do expect { subject }.to change { old_hooks.all? do |hook_type| File.exist?(File.join(old_hooks_dir, hook_type)) end }.from(false).to(true) end context 'and the force option is specified' do let(:options) { super().merge(force: true) } it 'does not raise an error' do expect { subject }.to_not raise_error end it 'copies all supported hooks from the master hook' do expect { subject }.to change { hook_files_installed?(hooks_dir) }.from(false).to(true) end end end context 'and a repo configuration file is already present' do let(:existing_content) { '# Hello World' } around do |example| Dir.chdir(target) do File.open('.overcommit.yml', 'w') { |f| f.write(existing_content) } example.run end end it 'does not overwrite the existing configuration' do expect { subject }.to_not change { File.open('.overcommit.yml').read }.from(existing_content) end end context 'and a repo configuration file is not present' do around do |example| Dir.chdir(target) do example.run end end it 'creates a starter configuration file' do expect { subject }.to change { File.exist?('.overcommit.yml') && FileUtils.compare_file( '.overcommit.yml', File.join(Overcommit::HOME, 'config', 'starter.yml') ) }.from(false).to(true) end end context 'and a custom core.hooksPath directory is set' do around do |example| Dir.chdir(target) do FileUtils.mkdir 'my-hooks' `git config core.hooksPath my-hooks` example.run end end it 'installs the hooks in the custom directory' do expect { subject }.to change { hook_files_installed?(File.join(target, 'my-hooks')) }. from(false). to(true) end end end context 'and an uninstall is requested' do let(:options) { { action: :uninstall } } context 'and Overcommit hooks were previously installed' do before do installer.run(target, action: :install) end it 'removes the master hook from the hooks directory' do expect { subject }.to change { File.exist?(master_hook) }.from(true).to(false) end it 'removes all hook files from the hooks directory' do expect { subject }.to change { hook_files_installed?(hooks_dir) }.from(true).to(false) end end context 'and Overcommit hooks were not previously installed' do it 'does not raise an error' do expect { subject }.to_not raise_error end end context 'and non-Overcommit hooks were previously installed' do let(:old_hooks) { %w[commit-msg pre-commit] } context 'before installing Overcommit hooks' do before do FileUtils.mkdir_p(old_hooks_dir) Dir.chdir(old_hooks_dir) do old_hooks.each { |hook_type| touch(hook_type) } end end it 'restores the previously existing hooks' do expect { subject }.to change { old_hooks.all? do |hook_type| File.exist?(File.join(hooks_dir, hook_type)) end }.from(false).to(true) end end context 'after installing Overcommit hooks' do before do FileUtils.mkdir_p(hooks_dir) Dir.chdir(hooks_dir) do old_hooks.each { |hook_type| touch(hook_type) } end end it 'does not remove the previously existing hooks' do expect { subject }.to_not change { old_hooks.all? do |hook_type| File.exist?(File.join(hooks_dir, hook_type)) end }.from(true) end end end end context 'which has an external git dir' do let(:submodule) { File.join(target, 'submodule') } before do 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 File.expand_path(File.read(submodule_git_file)[/gitdir: (.*)/, 1], submodule) end let(:submodule_hooks_dir) { File.join(submodule_git_dir, 'hooks') } subject { installer.run(submodule, options) } it 'installs hooks into the correct external directory' do expect { subject }.to change { hook_files_installed?(submodule_hooks_dir) }.from(false).to(true) end end end end end ================================================ FILE: spec/overcommit/logger_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::Logger do let(:io) { StringIO.new } let(:output) { io.string } subject { described_class.new(io) } describe '.silent' do subject { described_class.silent } it 'does not output anything' do capture_stdout { subject.log('Something') }.should be_empty end end describe '#partial' do subject { super().partial('Hello') } it 'writes to the output stream' do subject output.should_not be_empty end it 'does not append a newline' do subject output[-1].should_not == "\n" end end describe '#log' do subject { super().log('Hello') } it 'writes to the output stream' do subject output.should_not be_empty end it 'appends a newline' do subject output[-1, 1].should == "\n" end end shared_examples_for 'colorized output' do subject { super().send(method, 'Hello') } it 'writes to the output stream' do subject output.should_not be_empty end it 'appends a newline' do subject output[-1, 1].should == "\n" end context 'when the output stream is a TTY' do before do io.stub(:tty?).and_return(true) end it 'includes the color escape sequence' do subject output.should include "\033[#{color_code}m" end it 'ends with the color reset sequence' do subject output.should end_with "[0m\n" end context 'and colorization is disabled' do around do |example| Overcommit::Utils.with_environment 'OVERCOMMIT_COLOR' => '0' do example.run end end it 'omits the color escape sequence' do subject output.should_not include "\033" end end end context 'when the output stream is not a TTY' do before do io.stub(:tty?).and_return(false) end it 'omits the color escape sequence' do subject output.should_not include "\033" end context 'and colorization is enabled' do around do |example| Overcommit::Utils.with_environment 'OVERCOMMIT_COLOR' => '1' do example.run end end it 'includes the color escape sequence' do subject output.should include "\033[#{color_code}m" end it 'ends with the color reset sequence' do subject output.should end_with "[0m\n" end end end end describe '#debug' do context 'when debug mode is enabled' do around do |example| Overcommit::Utils.with_environment 'OVERCOMMIT_DEBUG' => '1' do example.run end end it_behaves_like 'colorized output' do let(:method) { :debug } let(:color_code) { '35' } end end context 'when debug mode is not enabled' do subject { super().debug('Hello') } it 'does not write to the output stream' do subject output.should be_empty end end end describe '#bold' do it_behaves_like 'colorized output' do let(:method) { :bold } let(:color_code) { '1' } end end describe '#error' do it_behaves_like 'colorized output' do let(:method) { :error } let(:color_code) { 31 } end end describe '#bold_error' do it_behaves_like 'colorized output' do let(:method) { :bold_error } let(:color_code) { '1;31' } end end describe '#success' do it_behaves_like 'colorized output' do let(:method) { :success } let(:color_code) { 32 } end end describe '#warning' do it_behaves_like 'colorized output' do let(:method) { :warning } let(:color_code) { 33 } end end describe '#bold_warning' do it_behaves_like 'colorized output' do let(:method) { :bold_warning } let(:color_code) { '1;33' } end end end ================================================ FILE: spec/overcommit/message_processor_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' describe Overcommit::MessageProcessor do # Shorthand to make writing these tests a little more sane EMH = Overcommit::MessageProcessor::ERRORS_MODIFIED_HEADER + "\n" WMH = Overcommit::MessageProcessor::WARNINGS_MODIFIED_HEADER + "\n" EUH = Overcommit::MessageProcessor::ERRORS_UNMODIFIED_HEADER + "\n" WUH = Overcommit::MessageProcessor::WARNINGS_UNMODIFIED_HEADER + "\n" EGH = Overcommit::MessageProcessor::ERRORS_GENERIC_HEADER + "\n" WGH = Overcommit::MessageProcessor::WARNINGS_GENERIC_HEADER + "\n" let(:config) { double('config') } let(:context) { double('context') } let(:hook) { Class.new(Overcommit::Hook::Base).new(config, context) } subject { described_class.new(hook, setting) } describe '#hook_result' do let(:modified_lines) { {} } subject { super().hook_result(messages) } before do config.stub(:for_hook).and_return({}) modified_lines.each do |file, lines| hook.stub(:modified_lines_in_file). with(file). and_return(lines.to_set) end end def error(file = nil, line = nil) Overcommit::Hook::Message.new(:error, file, line, 'Error') end def warning(file = nil, line = nil) Overcommit::Hook::Message.new(:warning, file, line, 'Warning') end context 'when there are no messages' do let(:messages) { [] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:pass, ''] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:pass, ''] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:pass, ''] } end end context 'when there are errors on modified lines' do let(:modified_lines) { { 'a.txt' => [2] } } let(:messages) { [error('a.txt', 2)] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:fail, "#{EMH}Error\n"] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:fail, "#{EMH}Error\n"] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:fail, "#{EMH}Error\n"] } end end context 'when there are errors on unmodified lines' do let(:modified_lines) { { 'a.txt' => [3] } } let(:messages) { [error('a.txt', 2)] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:fail, "#{EUH}Error\n"] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:warn, "#{EUH}Error\n"] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:pass, ''] } end end context 'when there are warnings on modified lines' do let(:modified_lines) { { 'a.txt' => [2] } } let(:messages) { [warning('a.txt', 2)] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:warn, "#{WMH}Warning\n"] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:warn, "#{WMH}Warning\n"] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:warn, "#{WMH}Warning\n"] } end end context 'when there are warnings on unmodified lines' do let(:modified_lines) { { 'a.txt' => [3] } } let(:messages) { [warning('a.txt', 2)] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:warn, "#{WUH}Warning\n"] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:warn, "#{WUH}Warning\n"] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:pass, ''] } end end context 'when there are errors and warnings on modified lines' do let(:modified_lines) { { 'a.txt' => [2], 'b.txt' => [3, 4] } } let(:messages) { [warning('a.txt', 2), error('b.txt', 4)] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:fail, "#{EMH}Error\n#{WMH}Warning\n"] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:fail, "#{EMH}Error\n#{WMH}Warning\n"] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:fail, "#{EMH}Error\n#{WMH}Warning\n"] } end end context 'when there are errors and warnings on unmodified lines' do let(:modified_lines) { { 'a.txt' => [2], 'b.txt' => [3, 4] } } let(:messages) { [warning('a.txt', 3), error('b.txt', 5)] } context 'and setting is `report`' do let(:setting) { 'report' } it { should == [:fail, "#{EUH}Error\n#{WUH}Warning\n"] } end context 'and setting is `warn`' do let(:setting) { 'warn' } it { should == [:warn, "#{EUH}Error\n#{WUH}Warning\n"] } end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:pass, ''] } end end context 'when there are errors and warnings on modified/unmodified lines' do let(:modified_lines) { { 'a.txt' => [2], 'b.txt' => [3, 4] } } let(:messages) do [ warning('a.txt', 3), warning('b.txt', 3), error('a.txt', 2), error('b.txt', 5), ] end context 'and setting is `report`' do let(:setting) { 'report' } it do should == [:fail, "#{EMH}Error\n#{WMH}Warning\n" \ "#{EUH}Error\n#{WUH}Warning\n"] end end context 'and setting is `warn`' do let(:setting) { 'warn' } it do should == [:fail, "#{EMH}Error\n#{WMH}Warning\n" \ "#{EUH}Error\n#{WUH}Warning\n"] end end context 'and setting is `ignore`' do let(:setting) { 'ignore' } it { should == [:fail, "#{EMH}Error\n#{WMH}Warning\n"] } end end context 'when there are generic errors' do let(:messages) { [error] * 2 } let(:setting) { 'report' } it { should == [:fail, "Error\nError\n"] } end context 'when there are generic warnings' do let(:messages) { [warning] * 2 } let(:setting) { 'report' } it { should == [:warn, "Warning\nWarning\n"] } end context 'when there are generic errors and warnings' do let(:messages) { [warning, error] * 2 } let(:setting) { 'report' } it { should == [:fail, "Warning\nError\nWarning\nError\n"] } end context 'when there are errors and warnings on modified/unmodified lines' do let(:modified_lines) { { 'a.txt' => [2], 'b.txt' => [3, 4] } } let(:setting) { 'report' } let(:messages) do [ warning('a.txt', 3), warning('b.txt', 3), error('a.txt', 2), error('b.txt', 5), ] end context 'and there are generic errors before them' do let(:messages) { [error] * 2 + super() } it do should == [:fail, "#{EGH}Error\nError\n" \ "#{EMH}Error\n#{WMH}Warning\n" \ "#{EUH}Error\n#{WUH}Warning\n"] end end context 'and there are generic warnings before them' do let(:messages) { [warning] * 2 + super() } it do should == [:fail, "#{WGH}Warning\nWarning\n" \ "#{EMH}Error\n#{WMH}Warning\n" \ "#{EUH}Error\n#{WUH}Warning\n"] end end context 'and there are generic errors after them' do let(:messages) { super() + [error] * 2 } it do should == [:fail, "#{EGH}Error\nError\n" \ "#{EMH}Error\n#{WMH}Warning\n" \ "#{EUH}Error\n#{WUH}Warning\n"] end end context 'and there are generic warnings after them' do let(:messages) { super() + [warning] * 2 } it do should == [:fail, "#{WGH}Warning\nWarning\n" \ "#{EMH}Error\n#{WMH}Warning\n" \ "#{EUH}Error\n#{WUH}Warning\n"] end end end end end ================================================ FILE: spec/overcommit/utils_spec.rb ================================================ # frozen_string_literal: true require 'spec_helper' require 'securerandom' describe Overcommit::Utils do describe '.script_path' do subject { described_class.script_path('some-script') } it 'points to the libexec scripts directory' do subject.should end_with File.join('libexec', 'some-script') end end describe '.repo_root' do let(:repo_dir) { repo } subject { described_class.repo_root } around do |example| Dir.chdir(repo_dir) do example.run end end it 'returns the path to the repository root' do # realpath is so spec passes on Mac OS X subject.should == File.realpath(repo_dir) end context 'when there is no .git directory' do before do FileUtils.rm_rf('.git', secure: true) end it 'raises an exception' do expect { subject }.to raise_error Overcommit::Exceptions::InvalidGitRepo end end end describe '.git_dir' do let(:repo_dir) { repo } subject { described_class.git_dir } around do |example| Dir.chdir(repo_dir) do example.run end end context 'when .git is a directory' do it 'returns the path to the directory' do subject.should end_with File.join(repo_dir, '.git') end end context 'when .git directory is not located in the repository' do let(:git_dir) { Dir.mktmpdir } let(:repo_dir) { repo(git_dir: git_dir) } it 'returns the path of the external Git directory' do # realpath is so spec passes on Mac OS X subject.should == File.realpath(git_dir) end end end describe '.strip_color_codes' do subject { described_class.strip_color_codes(text) } context 'with an empty string' do let(:text) { '' } it { should == '' } end context 'with a string with no escape sequences' do let(:text) { 'A normal string' } it { should == text } end context 'with a string with escape sequences' do let(:text) { "A \e[31mcolored string\e[39m" } it { should == 'A colored string' } end end describe '.snake_case' do it 'converts camel case to underscores' do described_class.snake_case('HelloWorld').should == 'hello_world' end it 'leaves underscored strings as is' do described_class.snake_case('hello_world').should == 'hello_world' end it 'converts namespaced class names to paths' do described_class.snake_case('SomeModule::SomeOtherModule::SomeClass'). should == 'some_module/some_other_module/some_class' end end describe '.camel_case' do it 'converts underscored strings to camel case' do described_class.camel_case('hello_world').should == 'HelloWorld' end it 'leaves already camel-cased strings as is' do described_class.camel_case('HelloWorld').should == 'HelloWorld' end it 'converts hyphenated strings to camel case' do described_class.camel_case('hello-world').should == 'HelloWorld' end end describe '.supported_hook_types' do subject { described_class.supported_hook_types } # rubocop:disable Layout/LineLength it { should =~ %w[commit-msg pre-commit post-checkout post-commit post-merge post-rewrite pre-push pre-rebase prepare-commit-msg] } # rubocop:enable Layout/LineLength end describe '.supported_hook_type_classes' do subject { described_class.supported_hook_type_classes } # rubocop:disable Layout/LineLength it { should =~ %w[CommitMsg PreCommit PostCheckout PostCommit PostMerge PostRewrite PrePush PreRebase PrepareCommitMsg] } # rubocop:enable Layout/LineLength end describe '.parent_command' do subject { described_class.parent_command } before do Process.stub(:ppid) { Process.pid } end it { should =~ /rspec/ } context 'when there is no parent' do before do Process.stub(:ppid) { 0 } end it { should be_nil } end end describe '.execute' do let(:arguments) { %w[echo Hello World] } subject { described_class.execute(arguments) } it 'returns result with the output' do subject.stdout.should == "Hello World\n" end it 'returns result with the exit status' do subject.status.should == 0 end context 'when one of the arguments is a lone pipe character' do let(:arguments) { %w[ps aux | grep bash] } it 'raises an exception' do expect { subject }.to raise_error Overcommit::Exceptions::InvalidCommandArgs end end context 'when given an input stream' do let(:arguments) { ['cat', '-'] } let(:input) { 'Hello world' } subject { described_class.execute(arguments, input: input) } it 'passes the input to the standard input stream' do subject.stdout.should == "Hello world\n" end end context 'when given a list of arguments to execute in chunks' do let(:arguments) { ['echo'] } let(:splittable_args) { %w[1 2 3] } subject { described_class.execute(arguments, args: splittable_args) } it 'invokes CommandSplitter.execute' do Overcommit::CommandSplitter. should_receive(:execute). with(arguments, { args: splittable_args }). and_return(double(status: 0, stdout: '', stderr: '')) subject end it 'returns a result' do subject.should be_success subject.stdout.should == "1 2 3\n" subject.stderr.should == '' end end end describe '.execute_in_background' do let(:arguments) { %w[touch some-file] } subject { described_class.execute_in_background(arguments) } around do |example| directory do example.run end end it 'executes the command' do wait_until(timeout: 5) { subject.exited? } # Make sure process terminated before checking File.exist?('some-file').should == true end end describe '.with_environment' do let(:var_name) { "OVERCOMMIT_TEST_VAR_#{SecureRandom.hex}" } shared_examples_for 'with_environment' do it 'sets the value of the variable within the block' do described_class.with_environment var_name => 'modified_value' do ENV[var_name].should == 'modified_value' end end end context 'when setting an environment variable that was not already set' do it_should_behave_like 'with_environment' it 'deletes the value once the block has exited' do described_class.with_environment var_name => 'modified_value' do # Do something... end ENV[var_name].should be_nil end end context 'when setting an environment variable that was already set' do around do |example| ENV[var_name] = 'previous_value' example.run ENV.delete(var_name) end it_should_behave_like 'with_environment' it 'restores the old value once the block has exited' do described_class.with_environment var_name => 'modified_value' do # Do something... end ENV[var_name].should == 'previous_value' end end end end ================================================ FILE: spec/spec_helper.rb ================================================ # frozen_string_literal: true require 'bundler' require 'simplecov' SimpleCov.start do add_filter 'bin/' add_filter 'libexec/' add_filter 'spec/' add_filter 'template-dir/' if ENV['CI'] require 'simplecov-lcov' SimpleCov::Formatter::LcovFormatter.config do |c| c.report_with_single_file = true c.single_report_path = 'coverage/lcov.info' end formatter SimpleCov::Formatter::LcovFormatter end end require 'overcommit' require 'tempfile' hook_types = Dir[File.join(Overcommit::HOOK_DIRECTORY, '*')]. select { |f| File.directory?(f) }. reject { |f| File.basename(f) == 'shared' }. sort hook_types.each do |hook_type| require File.join(hook_type, 'base.rb') Dir[File.join(hook_type, '**/*.rb')]. select { |f| File.file?(f) && File.basename(f, '.rb') != 'base' }. sort. each { |f| require f } end Dir[File.dirname(__FILE__) + '/support/**/*.rb'].sort.each { |f| require f } RSpec.configure do |config| config.include GitSpecHelpers config.include OutputHelpers config.include ShellHelpers # Continue to enable the older `should` syntax for expectations config.expect_with :rspec do |c| c.syntax = [:expect, :should] end config.mock_with :rspec do |c| c.syntax = [:expect, :should] end config.around(:each) do |example| # Most tests don't deal with verification, so disable it by default env = {} unless respond_to?(:enable_verification) && enable_verification env['OVERCOMMIT_NO_VERIFY'] = '1' end Overcommit::Utils.with_environment env do example.run end end # Much of Overcommit depends on these helpers, so they are aggressively # cached. Unset them before each example so we always get fresh values. config.before(:each) do %w[git_dir repo_root].each do |var| Overcommit::Utils.instance_variable_set(:"@#{var}", nil) end end end ================================================ FILE: spec/support/git_spec_helpers.rb ================================================ # frozen_string_literal: true require 'tmpdir' # Helpers for creating temporary repositories and directories for testing. module GitSpecHelpers module_function # Creates an empty git repository, allowing you to execute a block where # the current working directory is set to that repository's root directory. # # @param options [Hash] # @return [String] path of the repository def repo(options = {}) directory('some-repo') do create_cmd = %w[git init --initial-branch=master] create_cmd += ['--template', options[:template_dir]] if options[:template_dir] create_cmd += ['--separate-git-dir', options[:git_dir]] if options[:git_dir] result = Overcommit::Utils.execute(create_cmd) raise "Unable to create repo: #{result.stderr}" unless result.success? # Need to define user info since some CI contexts don't have defaults set `git config --local user.name "Overcommit Tester"` `git config --local user.email "overcommit@example.com"` `git config --local rerere.enabled 0` # Don't record resolutions in tests `git config --local commit.gpgsign false` yield if block_given? end end # Retrieve sha1 based on git ref # # @param ref [String] git ref # @return [String] ref's sha1 def get_sha1(ref) `git rev-parse #{ref}`.chomp end # Creates a directory (with an optional specific name) in a temporary # directory which will automatically be destroyed. # # @param name [String] base name of the directory # @return [String] path of the directory that was created def directory(name = 'some-dir', &block) tmpdir = Dir.mktmpdir.tap do |path| Dir.chdir(path) do Dir.mkdir(name) Dir.chdir(name, &block) if block_given? end end File.join(tmpdir, name) end # Returns a random git object hash. # # @return [String] def random_hash Array.new(40) { rand(65..90).chr }.join end end ================================================ FILE: spec/support/matchers/hook.rb ================================================ # frozen_string_literal: true # General spec matcher logic for checking hook status and output. class HookMatcher def initialize(status, args) options = args.empty? ? {} : { message: args.first } @expected_status = status @expected_message = options[:message] end def matches?(check) result = [check.run].flatten if result.is_a?(Array) && (result.first.is_a?(Overcommit::Hook::Message) || result.empty?) messages_match?(result) else actual_status, actual_message = result status_matches?(actual_status) && message_matches?(actual_message) end end def messages_match?(messages) case @expected_status when :fail messages.any? { |message| message.type == :error } when :warn messages.any? { |message| message.type == :warning } else messages.empty? end end def status_matches?(actual_status) @expected_status.nil? || actual_status == @expected_status end def message_matches?(actual_message) return true if @expected_message.nil? if @expected_message.is_a?(Regexp) actual_message =~ @expected_message else actual_message == @expected_message end end def failure_message(actual, error_message) actual_status, actual_message = [actual].flatten if status_matches?(actual_status) error_message << " with message matching #{@expected_message.inspect}," \ " but was #{actual_message.inspect}" end error_message end end # Can't use 'fail' as it is a reserved word. RSpec::Matchers.define :fail_hook do |*args| check_matcher = HookMatcher.new(:fail, args) match do check_matcher.matches?(actual) end failure_message do check_matcher.failure_message( actual, 'expected that the hook would fail' ) end failure_message_when_negated do 'expected that the hook would not fail' end description { 'fail' } end RSpec::Matchers.define :pass do |*args| check_matcher = HookMatcher.new(:pass, args) match do check_matcher.matches?(actual) end failure_message do check_matcher.failure_message( actual, 'expected that the check would pass' ) end failure_message_when_negated do 'expected that the check would not pass' end description { 'pass the check' } end RSpec::Matchers.define :warn do |*args| check_matcher = HookMatcher.new(:warn, args) match do |check| check_matcher.matches?(check) end failure_message do check_matcher.failure_message( actual, 'expected that the check would report a warning' ) end failure_message_when_negated do 'expected that the check would not report a warning' end description { 'report a warning' } end ================================================ FILE: spec/support/normalize_indent.rb ================================================ # frozen_string_literal: true # Strips off excess leading indentation from each line so we can use Heredocs # for writing code without having the leading indentation count. module IndentNormalizer def normalize_indent(code) leading_indent = code[/^(\s*)/, 1] code.lstrip.gsub(/\n#{leading_indent}/, "\n") end end RSpec.configure do |_config| include IndentNormalizer end ================================================ FILE: spec/support/output_helpers.rb ================================================ # frozen_string_literal: true # Helpers for capturing output streams in tests. module OutputHelpers module_function def capture_stdout original = $stdout $stdout = output = StringIO.new yield output.string ensure $stdout = original end end ================================================ FILE: spec/support/shell_helpers.rb ================================================ # frozen_string_literal: true require 'timeout' require 'overcommit/subprocess' # Helpers for executing shell commands in tests. module ShellHelpers def shell(command) Overcommit::Subprocess.spawn(command) end def symlink(source, dest) Overcommit::Utils::FileUtils.symlink(source, dest) end def touch(file) FileUtils.touch(file) end # Wait until the specified condition is true or the given timeout has elapsed, # whichever comes first. # # @param options [Hash] # @raise [Timeout::TimeoutError] timeout has elapsed before condition holds def wait_until(options = {}) Timeout.timeout(options.fetch(:timeout) { 1 }) do loop do return if yield sleep options.fetch(:check_interval) { 0.1 } end end end # Output text to file using `File#puts`, which mimics the behavior of the # `echo` shell command. # # @param text [String] text to write # @param file [String] path to target file # @param options [Hash] # @option options [Boolean] :append whether to append to existing file def echo(text, file, options = {}) mode = options[:append] ? 'a' : 'w' File.open(file, mode) { |f| f.puts(text) } end end ================================================ FILE: template-dir/hooks/commit-msg ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/overcommit-hook ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/post-checkout ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/post-commit ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/post-merge ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/post-rewrite ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/pre-commit ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/pre-push ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/pre-rebase ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end ================================================ FILE: template-dir/hooks/prepare-commit-msg ================================================ #!/usr/bin/env ruby # frozen_string_literal: true # Entrypoint for Overcommit hook integration. Installing Overcommit will result # in all of your git hooks being copied from this file, allowing the framework # to manage your hooks for you. # Prevent a Ruby stack trace from appearing when we interrupt the hook. # Note that this will be overridden when Overcommit is loaded, since the # InterruptHandler will redefine the trap at that time. Signal.trap('INT') do puts 'Hook run interrupted' exit 130 end # Allow hooks to be disabled via environment variable so git commands can be run # in scripts without Overcommit running hooks if ENV['OVERCOMMIT_DISABLE'].to_i != 0 || ENV['OVERCOMMIT_DISABLED'].to_i != 0 exit end hook_type = File.basename($0) if hook_type == 'overcommit-hook' puts "Don't run `overcommit-hook` directly; it is intended to be symlinked " \ "by each hook in a repository's .git/hooks directory." exit 64 # EX_USAGE end # Check if Overcommit should invoke a Bundler context for loading gems File.read('.overcommit.yml') =~ /gemfile: (?:false|['"]?(.*)['"]?)/ gemfile = Regexp.last_match(1) if gemfile ENV['BUNDLE_GEMFILE'] = gemfile require 'bundler' begin Bundler.setup rescue Bundler::BundlerError => e puts "Problem loading '#{gemfile}': #{e.message}" puts "Try running:\nbundle install --gemfile=#{gemfile}" if e.is_a?(Bundler::GemNotFound) exit 78 # EX_CONFIG end end begin require 'overcommit' rescue LoadError if gemfile puts 'You have specified the `gemfile` option in your Overcommit ' \ 'configuration but have not added the `overcommit` gem to ' \ "#{gemfile}." else puts 'This repository contains hooks installed by Overcommit, but the ' \ "`overcommit` gem is not installed.\n" \ 'Install it with `gem install overcommit`.' end exit 64 # EX_USAGE end begin logger = Overcommit::Logger.new(STDOUT) Overcommit::Utils.log = logger # Ensure master hook is up-to-date installer = Overcommit::Installer.new(logger) if installer.run(Overcommit::Utils.repo_root, action: :update) exec($0, *ARGV) # Execute the updated hook with all original arguments end config = Overcommit::ConfigurationLoader.new(logger).load_repo_config context = Overcommit::HookContext.create(hook_type, config, ARGV, STDIN) config.apply_environment!(context, ENV) printer = Overcommit::Printer.new(config, logger, context) runner = Overcommit::HookRunner.new(config, logger, context, printer) status = runner.run exit(status ? 0 : 65) # 65 = EX_DATAERR rescue Overcommit::Exceptions::ConfigurationError => e puts e exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookContextLoadError => e puts e puts 'Are you running an old version of Overcommit?' exit 69 # EX_UNAVAILABLE rescue Overcommit::Exceptions::HookLoadError, Overcommit::Exceptions::InvalidHookDefinition => e puts e.message puts e.backtrace exit 78 # EX_CONFIG rescue Overcommit::Exceptions::HookSetupFailed, Overcommit::Exceptions::HookCleanupFailed => e puts e.message exit 74 # EX_IOERR rescue Overcommit::Exceptions::HookCancelled puts 'You cancelled the hook run' exit 130 # Ctrl-C cancel rescue Overcommit::Exceptions::InvalidGitRepo => e puts e exit 64 # EX_USAGE rescue Overcommit::Exceptions::ConfigurationSignatureChanged => e puts e puts "For more information, see #{Overcommit::REPO_URL}#security" exit 1 rescue Overcommit::Exceptions::InvalidHookSignature exit 1 rescue StandardError => e puts e.message puts e.backtrace puts "Report this bug at #{Overcommit::BUG_REPORT_URL}" exit 70 # EX_SOFTWARE end