├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── CHANGELOG.md ├── Dangerfile ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── danger-commit_lint.gemspec ├── lib ├── commit_lint │ ├── commit_check.rb │ ├── empty_line_check.rb │ ├── gem_version.rb │ ├── plugin.rb │ ├── subject_cap_check.rb │ ├── subject_length_check.rb │ ├── subject_period_check.rb │ └── subject_words_check.rb ├── danger_commit_lint.rb └── danger_plugin.rb └── spec ├── commit_lint_spec.rb ├── spec_helper.rb └── subject_length_check_spec.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea/ 3 | .ruby-gemset 4 | .ruby-version 5 | .yardoc/ 6 | Gemfile.lock 7 | pkg 8 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Style/FrozenStringLiteralComment: 2 | Enabled: false 3 | 4 | Style/For: 5 | EnforcedStyle: for 6 | 7 | Metrics/BlockLength: 8 | Exclude: 9 | - 'spec/**/*' 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | 3 | cache: bundler 4 | 5 | rvm: 6 | - 2.4.5 7 | - 2.5.5 8 | - 2.6.2 9 | - jruby-9.2.6.0 10 | 11 | script: 12 | - bundle exec rake 13 | - bundle exec danger 14 | 15 | notifications: 16 | email: false 17 | webhooks: 18 | urls: 19 | secure: F8ECoiEAmcEvw3rC/BtacmqhCfc+uAn8WhwH0ockorPdWIbOihnxGz8C3QIz+Cl8QTAuG9hY5lp/VG5UfpX0XirhuJ1FH9AmIUYnIWkWois6UKyjmBV4VitQDtGAkFzK7hCLZjrAT9PBpeJKkljltRgGQaeMDLpasxzgGO8MlcKe6hoZP6AoUdesOu10kePl6eQN4jXkg0Cr9BrYq93tZuCKvKkkVbTgeyni5RjxfMVyARGsu5+OWcMjMmaTYzs1+FNb+d7odvzMaIpJywS/MKAyE3QmXi2QFTb6ij/A77Z7Cj3MzWSD8MvVQdRfOBNfUhWYQzvaD79JR2ijYajxvcrZN/3gpi8CemKrMrqlPmDtRn/mcY3+37U/wfZfJIfhEYnQDkxR+OszaE2abbI0yYC4xQGySnHMltFkALxIdoPixir/OsO/KgDPMtuuQ2zG9d9xHp0rgeW6MEF99pOHNvCSxdny/p57GyFwKtAf+y37RwegTw6V3SU4hyfiofnndDGWX9F19TrFWwNFd9c2oPOkGdNqUahitEzmkQBFZQ76UPNLX9k1k/dzm0RLW8l1Ypa7J2B/aW6WNOT6Ucz0LvNCIte8svtyRgi60emy1r7uL1ltZ7lafP4eLQnWzyBClW7yTkU4F5AcrQFVuGuaq6up5wYfbIK9RU7ocbsOo10= 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog][keep] and this project adheres to 6 | [Semantic Versioning][semver]. 7 | 8 | [keep]: http://keepachangelog.com/ 9 | [semver]: http://semver.org/ 10 | 11 | ## [Unreleased] 12 | 13 | [Unreleased]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.7...master 14 | 15 | ## [0.0.7] - 2019-03-29 16 | 17 | ### Changed 18 | 19 | * Ignore git generated subject in commit messages [#21][] 20 | * Express danger plugin dependency in terms of plugin api [#24][] 21 | 22 | [0.0.7]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.6...v0.0.7 23 | [#21]: https://github.com/jonallured/danger-commit_lint/pull/21 24 | [#24]: https://github.com/jonallured/danger-commit_lint/pull/24 25 | 26 | ### Removed 27 | 28 | * Drop Ruby 2.3 from Travis [1aa9cff][1aa9cff] 29 | 30 | ## [0.0.6] - 2017-04-27 31 | 32 | ### Changed 33 | 34 | * Upgrade to newest Danger [480a4e6][480a4e6] 35 | 36 | [0.0.6]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.5...v0.0.6 37 | [480a4e6]: https://github.com/jonallured/danger-commit_lint/commit/480a4e6 38 | 39 | ## [0.0.5] - 2016-12-28 40 | 41 | ### Added 42 | 43 | * Add Ruby 2.4 to Travis [1aa9cff][1aa9cff] 44 | * Add a Dangerfile [51c597a][51c597a] 45 | * Add SHA to feedback [35044ae][35044ae] 46 | * Add check for one-word commit messages [c2dce9c][c2dce9c] 47 | * Add this very CHANGELOG 48 | 49 | ### Removed 50 | 51 | * Drop Ruby 2.2 from Travis [1aa9cff][1aa9cff] 52 | 53 | [0.0.5]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.4...v0.0.5 54 | [1aa9cff]: https://github.com/jonallured/danger-commit_lint/commit/1aa9cff 55 | [51c597a]: https://github.com/jonallured/danger-commit_lint/commit/51c597a 56 | [35044ae]: https://github.com/jonallured/danger-commit_lint/commit/35044ae 57 | [c2dce9c]: https://github.com/jonallured/danger-commit_lint/commit/c2dce9c 58 | 59 | ## [0.0.4] - 2016-11-03 60 | 61 | ### Changed 62 | 63 | * Upgrade to newest Danger [6fbbdd3][6fbbdd3] 64 | 65 | [0.0.4]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.3...v0.0.4 66 | [6fbbdd3]: https://github.com/jonallured/danger-commit_lint/commit/6fbbdd3 67 | 68 | ## [0.0.3] - 2016-10-07 69 | 70 | ### Added 71 | 72 | * Added JRuby to Travis [2266134][2266134] 73 | 74 | ### Changed 75 | 76 | * Improved the inline docs [c5f7408][c5f7408] 77 | * Improved README [71ce9f9][71ce9f9] [c58e34c][c58e34c] 78 | 79 | [0.0.3]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.2...v0.0.3 80 | [2266134]: https://github.com/jonallured/danger-commit_lint/commit/2266134 81 | [c5f7408]: https://github.com/jonallured/danger-commit_lint/commit/c5f7408 82 | [71ce9f9]: https://github.com/jonallured/danger-commit_lint/commit/71ce9f9 83 | [c58e34c]: https://github.com/jonallured/danger-commit_lint/commit/c58e34c 84 | 85 | ## [0.0.2] - 2016-09-01 86 | 87 | ### Added 88 | 89 | * Added a check to ensure first letter is capitalized [4a691ef][4a691ef] 90 | 91 | ### Changed 92 | 93 | * Switched to using the internal API for warn/error [9ca11d6][9ca11d6] 94 | 95 | [0.0.2]: https://github.com/jonallured/danger-commit_lint/compare/v0.0.1...v0.0.2 96 | [4a691ef]: https://github.com/jonallured/danger-commit_lint/commit/4a691ef 97 | [9ca11d6]: https://github.com/jonallured/danger-commit_lint/commit/9ca11d6 98 | 99 | ## 0.0.1 - 2016-08-26 100 | 101 | Initial release 102 | -------------------------------------------------------------------------------- /Dangerfile: -------------------------------------------------------------------------------- 1 | commit_lint.check 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in danger-commit_lint.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Jon Allured 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Commit Lint for Danger 2 | 3 | [![Build Status](https://travis-ci.org/jonallured/danger-commit_lint.svg?branch=master)](https://travis-ci.org/jonallured/danger-commit_lint) 4 | 5 | This is a [Danger Plugin][danger] that ensures nice and tidy commit messages. 6 | The checks performed on each commit message are inspired by [Tim Pope's blog 7 | post][tpope] on good commit messages, echoed by [git's own documentation][book] 8 | on the subject. 9 | 10 | [danger]: http://danger.systems/plugins/commit_lint.html 11 | [tpope]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html 12 | [book]: https://www.git-scm.com/book/en/v2/Distributed-Git-Contributing-to-a-Project#Commit-Guidelines 13 | 14 | ## Installation 15 | 16 | ``` 17 | $ gem install danger-commit_lint 18 | ``` 19 | 20 | ## Usage 21 | 22 | Simply add this to your Dangerfile: 23 | 24 | ```ruby 25 | commit_lint.check 26 | ``` 27 | 28 | That will check each commit in the PR to ensure the following is true: 29 | 30 | * Commit subject begins with a capital letter (`subject_cap`) 31 | * Commit subject is more than one word (`subject_words`) 32 | * Commit subject is no longer than 50 characters (`subject_length`) 33 | * Commit subject does not end in a period (`subject_period`) 34 | * Commit subject and body are separated by an empty line (`empty_line`) 35 | 36 | By default, Commit Lint fails, but you can configure this behavior. 37 | 38 | ## Configuration 39 | 40 | Configuring Commit Lint is done by passing a hash. The four keys that can be 41 | passed are: 42 | 43 | * `disable` 44 | * `fail` 45 | * `warn` 46 | * `limit` 47 | 48 | The first three of these keys can accept either the symbol `:all` or an array of 49 | checks. Here are some ways you could configure Commit Lint: 50 | 51 | ```ruby 52 | # warn on all checks (instead of failing) 53 | commit_lint.check warn: :all 54 | 55 | # disable the `subject_period` check 56 | commit_lint.check disable: [:subject_period] 57 | ``` 58 | 59 | Remember, by default all checks are run and they will fail. Think of this as the 60 | default: 61 | 62 | ```ruby 63 | commit_lint.check fail: :all 64 | ``` 65 | 66 | Also note that there is one more way that Commit Lint can behave: 67 | 68 | ```ruby 69 | commit_lint.check disable: :all 70 | ``` 71 | 72 | This will actually throw a warning that Commit Lint isn't doing anything. 73 | 74 | ### Limiting number of commits checked 75 | 76 | The `limit` key allows you to limit checks to the first `n` commits (oldest to 77 | newest). This can be useful for PR workflows when squashing before merge where 78 | you want the initial commit message to be linted, but want to exclude additional 79 | commits pushed in response to change requests during a code review. 80 | 81 | ```ruby 82 | # limit checks to only the first (oldest) commit 83 | commit_lint.check limit: 1 84 | ``` 85 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'bundler/gem_tasks' 2 | require 'rspec/core/rake_task' 3 | require 'rubocop/rake_task' 4 | 5 | RSpec::Core::RakeTask.new(:spec) 6 | 7 | task default: %i[rubocop spec spec_docs] 8 | 9 | desc 'Run RuboCop on the lib/specs directory' 10 | RuboCop::RakeTask.new(:rubocop) do |task| 11 | task.patterns = ['lib/**/*.rb', 'spec/**/*.rb'] 12 | task.options = ['--display-cop-names'] 13 | end 14 | 15 | desc 'Ensure that the plugin passes `danger plugins lint`' 16 | task :spec_docs do 17 | sh 'bundle exec danger plugins lint' 18 | end 19 | -------------------------------------------------------------------------------- /danger-commit_lint.gemspec: -------------------------------------------------------------------------------- 1 | lib = File.expand_path('lib', __dir__) 2 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 3 | require 'commit_lint/gem_version.rb' 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = 'danger-commit_lint' 7 | spec.version = CommitLint::VERSION 8 | spec.authors = ['Jon Allured'] 9 | spec.email = ['jon.allured@gmail.com'] 10 | spec.description = 'A Danger Plugin that ensures nice and tidy commit messages.' 11 | spec.summary = "A Danger Plugin that ensure commit messages are not too long, don't end in a period and have a line between subject and body" 12 | spec.homepage = 'https://github.com/jonallured/danger-commit_lint' 13 | spec.license = 'MIT' 14 | 15 | spec.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) 16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 18 | spec.require_paths = ['lib'] 19 | 20 | spec.add_runtime_dependency 'danger-plugin-api', '~> 1.0' 21 | 22 | spec.add_development_dependency 'bundler' 23 | spec.add_development_dependency 'pry' 24 | spec.add_development_dependency 'rake' 25 | spec.add_development_dependency 'rspec' 26 | spec.add_development_dependency 'rubocop' 27 | spec.add_development_dependency 'yard' 28 | end 29 | -------------------------------------------------------------------------------- /lib/commit_lint/commit_check.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerCommitLint < Plugin 3 | class CommitCheck # :nodoc: 4 | def self.fail?(message) 5 | new(message).fail? 6 | end 7 | 8 | def initialize(message); end 9 | 10 | def fail? 11 | raise 'implement in subclass' 12 | end 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/commit_lint/empty_line_check.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerCommitLint < Plugin 3 | class EmptyLineCheck < CommitCheck # :nodoc: 4 | MESSAGE = 'Please separate commit message subject from body with newline.'.freeze 5 | 6 | def self.type 7 | :empty_line 8 | end 9 | 10 | def initialize(message) 11 | @empty_line = message[:empty_line] 12 | end 13 | 14 | def fail? 15 | @empty_line && !@empty_line.empty? 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/commit_lint/gem_version.rb: -------------------------------------------------------------------------------- 1 | module CommitLint 2 | VERSION = '0.0.7'.freeze 3 | end 4 | -------------------------------------------------------------------------------- /lib/commit_lint/plugin.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | # Run each commit in the PR through a message linting. 3 | # 4 | # Commit lint will check each commit in the PR to ensure the following is 5 | # true: 6 | # 7 | # * Commit subject begins with a capital letter (`subject_cap`) 8 | # * Commit subject is more than one word (`subject_word`) 9 | # * Commit subject is no longer than 50 characters (`subject_length`) 10 | # * Commit subject does not end in a period (`subject_period`) 11 | # * Commit subject and body are separated by an empty line (`empty_line`) 12 | # 13 | # By default, Commit Lint fails, but you can configure this behavior. 14 | # 15 | # 16 | # @example Lint all commits using defaults 17 | # 18 | # commit_lint.check 19 | # 20 | # @example Warn instead of fail 21 | # 22 | # commit_lint.check warn: :all 23 | # 24 | # @example Disable a particular check 25 | # 26 | # commit_lint.check disable: [:subject_period] 27 | # 28 | # @see danger/danger 29 | # @tags commit linting 30 | # 31 | class DangerCommitLint < Plugin 32 | NOOP_MESSAGE = 'All checks were disabled, nothing to do.'.freeze 33 | 34 | # Checks the commits with whatever config the user passes. 35 | # 36 | # Passing in a hash which contain the following keys: 37 | # 38 | # * `disable` - array of checks to skip 39 | # * `fail` - array of checks to fail on 40 | # * `warn` - array of checks to warn on 41 | # * `limit` - limits checks to first N commits 42 | # 43 | # The current check types are: 44 | # 45 | # * `subject_cap` 46 | # * `subject_word` 47 | # * `subject_length` 48 | # * `subject_period` 49 | # * `empty_line` 50 | # 51 | # Note: you can pass :all instead of an array to target all checks. 52 | # 53 | # @param [Hash] config 54 | # 55 | # @return [void] 56 | # 57 | def check(config = {}) 58 | @config = config 59 | 60 | if all_checks_disabled? 61 | messaging.warn NOOP_MESSAGE 62 | else 63 | check_messages 64 | end 65 | end 66 | 67 | private 68 | 69 | def check_messages 70 | for klass in warning_checkers 71 | warning_shas = failed_shas(klass) 72 | issue_warning(klass::MESSAGE, warning_shas) unless warning_shas.empty? 73 | end 74 | 75 | for klass in failing_checkers 76 | failure_shas = failed_shas(klass) 77 | issue_failure(klass::MESSAGE, failure_shas) unless failure_shas.empty? 78 | end 79 | end 80 | 81 | def failed_shas(klass) 82 | messages.map { |message| message[:sha] if klass.fail? message }.compact 83 | end 84 | 85 | def checkers 86 | [ 87 | SubjectCapCheck, 88 | SubjectWordsCheck, 89 | SubjectLengthCheck, 90 | SubjectPeriodCheck, 91 | EmptyLineCheck 92 | ] 93 | end 94 | 95 | def checks 96 | checkers.map(&:type) 97 | end 98 | 99 | def enabled_checkers 100 | checkers.reject { |klass| disabled_checks.include? klass.type } 101 | end 102 | 103 | def warning_checkers 104 | enabled_checkers.select { |klass| warning_checks.include? klass.type } 105 | end 106 | 107 | def failing_checkers 108 | enabled_checkers - warning_checkers 109 | end 110 | 111 | def all_checks_disabled? 112 | @config[:disable] == :all || disabled_checks.count == checkers.count 113 | end 114 | 115 | def disabled_checks 116 | @config[:disable] || [] 117 | end 118 | 119 | def warning_checks 120 | return checks if @config[:warn] == :all 121 | 122 | @config[:warn] || [] 123 | end 124 | 125 | def commit_limit 126 | @config[:limit] || 0 127 | end 128 | 129 | def messages 130 | return parsed_messages if commit_limit.zero? 131 | 132 | parsed_messages.last(commit_limit) 133 | end 134 | 135 | def parsed_messages 136 | git.commits.map do |commit| 137 | (subject, empty_line) = commit.message.split("\n") 138 | { 139 | subject: subject, 140 | empty_line: empty_line, 141 | sha: commit.sha 142 | } 143 | end 144 | end 145 | 146 | def issue_warning(message, shas) 147 | warning = ([message] + shas).join("\n") 148 | messaging.warn warning 149 | end 150 | 151 | def issue_failure(message, shas) 152 | failure = ([message] + shas).join("\n") 153 | messaging.fail failure 154 | end 155 | end 156 | end 157 | -------------------------------------------------------------------------------- /lib/commit_lint/subject_cap_check.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerCommitLint < Plugin 3 | class SubjectCapCheck < CommitCheck # :nodoc: 4 | MESSAGE = 'Please start commit message subject with capital letter.'.freeze 5 | 6 | def self.type 7 | :subject_cap 8 | end 9 | 10 | def initialize(message) 11 | @first_character = message[:subject].split('').first 12 | end 13 | 14 | def fail? 15 | @first_character != @first_character.upcase 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/commit_lint/subject_length_check.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerCommitLint < Plugin 3 | class SubjectLengthCheck < CommitCheck # :nodoc: 4 | MESSAGE = 'Please limit commit message subject line to 50 characters.'.freeze 5 | GIT_GENERATED_SUBJECT = /^Merge branch \'.+\' into\ /.freeze 6 | GITHUB_GENERATED_SUBJECT = /^Merge pull request #\d+ from\ /.freeze 7 | 8 | attr_reader :subject 9 | 10 | def self.type 11 | :subject_length 12 | end 13 | 14 | def initialize(message) 15 | @subject = message[:subject] 16 | end 17 | 18 | def fail? 19 | subject.length > 50 && !merge_commit? 20 | end 21 | 22 | def merge_commit? 23 | subject =~ /#{GIT_GENERATED_SUBJECT}|#{GITHUB_GENERATED_SUBJECT}/ 24 | end 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/commit_lint/subject_period_check.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerCommitLint < Plugin 3 | class SubjectPeriodCheck < CommitCheck # :nodoc: 4 | MESSAGE = 'Please remove period from end of commit message subject line.'.freeze 5 | 6 | def self.type 7 | :subject_period 8 | end 9 | 10 | def initialize(message) 11 | @subject = message[:subject] 12 | end 13 | 14 | def fail? 15 | @subject.split('').last == '.' 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/commit_lint/subject_words_check.rb: -------------------------------------------------------------------------------- 1 | module Danger 2 | class DangerCommitLint < Plugin 3 | class SubjectWordsCheck < CommitCheck # :nodoc: 4 | MESSAGE = 'Please use more than one word in commit message.'.freeze 5 | 6 | def self.type 7 | :subject_words 8 | end 9 | 10 | def initialize(message) 11 | @subject = message[:subject] 12 | end 13 | 14 | def fail? 15 | @subject.split.count < 2 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/danger_commit_lint.rb: -------------------------------------------------------------------------------- 1 | require 'commit_lint/gem_version' 2 | -------------------------------------------------------------------------------- /lib/danger_plugin.rb: -------------------------------------------------------------------------------- 1 | require 'commit_lint/commit_check' 2 | require 'commit_lint/subject_cap_check' 3 | require 'commit_lint/subject_words_check' 4 | require 'commit_lint/subject_length_check' 5 | require 'commit_lint/subject_period_check' 6 | require 'commit_lint/empty_line_check' 7 | require 'commit_lint/plugin' 8 | -------------------------------------------------------------------------------- /spec/commit_lint_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('spec_helper', __dir__) 2 | 3 | # rubocop:disable Metrics/LineLength 4 | 5 | TEST_MESSAGES = { 6 | subject_cap: 'this subject needs a capital', 7 | subject_words: 'Fixed', 8 | subject_length: 'This is a really long subject line and should result in an error', 9 | subject_period: 'This subject line ends in a period.', 10 | empty_line: "This subject line is fine\nBut then I forgot the empty line separating the subject and the body.", 11 | all_errors: "this is a really long subject and it even ends in a period.\nNot to mention the missing empty line!", 12 | valid: "This is a valid message\n\nYou can tell because it meets all the criteria and the linter does not complain." 13 | }.freeze 14 | 15 | # rubocop:enable Metrics/LineLength 16 | 17 | def report_counts(status_report) 18 | status_report.values.flatten.count 19 | end 20 | 21 | # rubocop:disable Metrics/ClassLength 22 | 23 | module Danger 24 | class DangerCommitLint 25 | describe 'DangerCommitLint' do 26 | it 'should be a plugin' do 27 | expect(Danger::DangerCommitLint.new(nil)).to be_a Danger::Plugin 28 | end 29 | end 30 | 31 | describe 'check without configuration' do 32 | let(:sha) { '1234567' } 33 | let(:commit) { double(:commit, message: message, sha: sha) } 34 | 35 | def message_with_sha(message) 36 | [message, sha].join "\n" 37 | end 38 | 39 | context 'with invalid messages' do 40 | it 'fails those checks' do 41 | checks = { 42 | subject_cap: SubjectCapCheck::MESSAGE, 43 | subject_words: SubjectWordsCheck::MESSAGE, 44 | subject_length: SubjectLengthCheck::MESSAGE, 45 | subject_period: SubjectPeriodCheck::MESSAGE, 46 | empty_line: EmptyLineCheck::MESSAGE 47 | } 48 | 49 | for (check, warning) in checks 50 | commit_lint = testing_dangerfile.commit_lint 51 | commit = double(:commit, message: TEST_MESSAGES[check], sha: sha) 52 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 53 | 54 | commit_lint.check 55 | 56 | status_report = commit_lint.status_report 57 | expect(report_counts(status_report)).to eq 1 58 | expect(status_report[:errors]).to eq [ 59 | message_with_sha(warning) 60 | ] 61 | end 62 | end 63 | end 64 | 65 | context 'with all errors' do 66 | let(:message) { TEST_MESSAGES[:all_errors] } 67 | 68 | it 'fails every check' do 69 | commit_lint = testing_dangerfile.commit_lint 70 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 71 | 72 | commit_lint.check 73 | 74 | status_report = commit_lint.status_report 75 | expect(report_counts(status_report)).to eq 4 76 | expect(status_report[:errors]).to eq [ 77 | message_with_sha(SubjectCapCheck::MESSAGE), 78 | message_with_sha(SubjectLengthCheck::MESSAGE), 79 | message_with_sha(SubjectPeriodCheck::MESSAGE), 80 | message_with_sha(EmptyLineCheck::MESSAGE) 81 | ] 82 | end 83 | end 84 | 85 | context 'with valid messages' do 86 | let(:message) { TEST_MESSAGES[:valid] } 87 | 88 | it 'does nothing' do 89 | checks = { 90 | subject_length: SubjectLengthCheck::MESSAGE, 91 | subject_period: SubjectPeriodCheck::MESSAGE, 92 | empty_line: EmptyLineCheck::MESSAGE 93 | } 94 | 95 | for _ in checks 96 | commit_lint = testing_dangerfile.commit_lint 97 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 98 | 99 | commit_lint.check 100 | 101 | status_report = commit_lint.status_report 102 | expect(report_counts(status_report)).to eq 0 103 | end 104 | end 105 | end 106 | 107 | context 'with repeated bad messages' do 108 | let(:commits) do 109 | [ 110 | double(:commit, message: TEST_MESSAGES[:subject_cap], sha: 'sha1'), 111 | double(:commit, message: TEST_MESSAGES[:subject_cap], sha: 'sha2') 112 | ] 113 | end 114 | 115 | it 'fails are grouped' do 116 | commit_lint = testing_dangerfile.commit_lint 117 | allow(commit_lint.git).to receive(:commits).and_return(commits) 118 | 119 | commit_lint.check 120 | 121 | status_report = commit_lint.status_report 122 | expect(report_counts(status_report)).to eq 1 123 | expect(status_report[:errors]).to eq [ 124 | SubjectCapCheck::MESSAGE + "\n" + 'sha1' + "\n" + 'sha2' 125 | ] 126 | end 127 | end 128 | end 129 | 130 | describe 'disable configuration' do 131 | let(:sha) { '1234567' } 132 | let(:commit) { double(:commit, message: message, sha: sha) } 133 | 134 | def message_with_sha(message) 135 | [message, sha].join "\n" 136 | end 137 | 138 | context 'with individual checks' do 139 | context 'with invalid messages' do 140 | it 'does nothing' do 141 | checks = { 142 | subject_length: SubjectLengthCheck::MESSAGE, 143 | subject_period: SubjectPeriodCheck::MESSAGE, 144 | empty_line: EmptyLineCheck::MESSAGE 145 | } 146 | 147 | for (check, _) in checks 148 | commit_lint = testing_dangerfile.commit_lint 149 | commit = double(:commit, message: TEST_MESSAGES[check], sha: sha) 150 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 151 | 152 | commit_lint.check disable: [check] 153 | 154 | status_report = commit_lint.status_report 155 | expect(report_counts(status_report)).to eq 0 156 | end 157 | end 158 | end 159 | end 160 | 161 | context 'with all checks, implicitly' do 162 | let(:message) { TEST_MESSAGES[:all_errors] } 163 | 164 | it 'warns that nothing was checked' do 165 | commit_lint = testing_dangerfile.commit_lint 166 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 167 | 168 | all_checks = %i[ 169 | subject_cap 170 | subject_words 171 | subject_length 172 | subject_period 173 | empty_line 174 | ] 175 | commit_lint.check disable: all_checks 176 | 177 | status_report = commit_lint.status_report 178 | expect(report_counts(status_report)).to eq 1 179 | expect(status_report[:warnings]).to eq [NOOP_MESSAGE] 180 | end 181 | end 182 | 183 | context 'with all checks, explicitly' do 184 | let(:message) { TEST_MESSAGES[:all_errors] } 185 | 186 | it 'warns that nothing was checked' do 187 | commit_lint = testing_dangerfile.commit_lint 188 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 189 | 190 | commit_lint.check disable: :all 191 | 192 | status_report = commit_lint.status_report 193 | expect(report_counts(status_report)).to eq 1 194 | expect(status_report[:warnings]).to eq [NOOP_MESSAGE] 195 | end 196 | end 197 | end 198 | 199 | describe 'warn configuration' do 200 | let(:sha) { '1234567' } 201 | let(:commit) { double(:commit, message: message, sha: sha) } 202 | 203 | def message_with_sha(message) 204 | [message, sha].join "\n" 205 | end 206 | 207 | context 'with individual checks' do 208 | context 'with invalid messages' do 209 | it 'warns instead of failing' do 210 | checks = { 211 | subject_length: SubjectLengthCheck::MESSAGE, 212 | subject_period: SubjectPeriodCheck::MESSAGE, 213 | empty_line: EmptyLineCheck::MESSAGE 214 | } 215 | 216 | for (check, warning) in checks 217 | commit_lint = testing_dangerfile.commit_lint 218 | commit = double(:commit, message: TEST_MESSAGES[check], sha: sha) 219 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 220 | 221 | commit_lint.check warn: [check] 222 | 223 | status_report = commit_lint.status_report 224 | expect(report_counts(status_report)).to eq 1 225 | expect(status_report[:warnings]).to eq [ 226 | message_with_sha(warning) 227 | ] 228 | end 229 | end 230 | end 231 | 232 | context 'with valid messages' do 233 | let(:message) { TEST_MESSAGES[:valid] } 234 | 235 | it 'does nothing' do 236 | checks = { 237 | subject_length: SubjectLengthCheck::MESSAGE, 238 | subject_period: SubjectPeriodCheck::MESSAGE, 239 | empty_line: EmptyLineCheck::MESSAGE 240 | } 241 | 242 | for (check, _) in checks 243 | commit_lint = testing_dangerfile.commit_lint 244 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 245 | 246 | commit_lint.check warn: [check] 247 | 248 | status_report = commit_lint.status_report 249 | expect(report_counts(status_report)).to eq 0 250 | end 251 | end 252 | end 253 | end 254 | 255 | context 'with all checks' do 256 | context 'with all errors' do 257 | let(:message) { TEST_MESSAGES[:all_errors] } 258 | 259 | it 'warns instead of failing' do 260 | commit_lint = testing_dangerfile.commit_lint 261 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 262 | 263 | commit_lint.check warn: :all 264 | 265 | status_report = commit_lint.status_report 266 | expect(report_counts(status_report)).to eq 4 267 | expect(status_report[:warnings]).to eq [ 268 | message_with_sha(SubjectCapCheck::MESSAGE), 269 | message_with_sha(SubjectLengthCheck::MESSAGE), 270 | message_with_sha(SubjectPeriodCheck::MESSAGE), 271 | message_with_sha(EmptyLineCheck::MESSAGE) 272 | ] 273 | end 274 | end 275 | 276 | context 'with a valid message' do 277 | let(:message) { TEST_MESSAGES[:valid] } 278 | 279 | it 'does nothing' do 280 | commit_lint = testing_dangerfile.commit_lint 281 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 282 | 283 | commit_lint.check warn: :all 284 | 285 | status_report = commit_lint.status_report 286 | expect(report_counts(status_report)).to eq 0 287 | end 288 | end 289 | 290 | context 'with repeated bad messages' do 291 | let(:commits) do 292 | [ 293 | double(:commit, message: TEST_MESSAGES[:empty_line], sha: 'sha1'), 294 | double(:commit, message: TEST_MESSAGES[:empty_line], sha: 'sha2') 295 | ] 296 | end 297 | 298 | it 'warnings are grouped' do 299 | commit_lint = testing_dangerfile.commit_lint 300 | allow(commit_lint.git).to receive(:commits).and_return(commits) 301 | 302 | commit_lint.check warn: :all 303 | 304 | status_report = commit_lint.status_report 305 | expect(report_counts(status_report)).to eq 1 306 | expect(status_report[:warnings]).to eq [ 307 | EmptyLineCheck::MESSAGE + "\n" + 'sha1' + "\n" + 'sha2' 308 | ] 309 | end 310 | end 311 | end 312 | end 313 | 314 | describe 'limit configuration' do 315 | let(:sha1) { '1111111' } 316 | let(:commit1) { double(:commit, message: message, sha: sha) } 317 | 318 | let(:sha2) { '2222222' } 319 | let(:commit2) { double(:commit, message: message, sha: sha) } 320 | 321 | let(:sha3) { '3333333' } 322 | let(:commit3) { double(:commit, message: message, sha: sha) } 323 | 324 | def message_with_sha(message) 325 | [message, sha2, sha3].join "\n" 326 | end 327 | 328 | it 'fails checks only on messages within limit' do 329 | checks = { 330 | subject_cap: SubjectCapCheck::MESSAGE, 331 | subject_words: SubjectWordsCheck::MESSAGE, 332 | subject_length: SubjectLengthCheck::MESSAGE, 333 | subject_period: SubjectPeriodCheck::MESSAGE, 334 | empty_line: EmptyLineCheck::MESSAGE 335 | } 336 | 337 | for (check, warning) in checks 338 | commit_lint = testing_dangerfile.commit_lint 339 | commit1 = double(:commit, message: TEST_MESSAGES[check], sha: sha1) 340 | commit2 = double(:commit, message: TEST_MESSAGES[check], sha: sha2) 341 | commit3 = double(:commit, message: TEST_MESSAGES[check], sha: sha3) 342 | commits = [commit1, commit2, commit3] 343 | allow(commit_lint.git).to receive(:commits).and_return(commits) 344 | 345 | commit_lint.check limit: 2 346 | 347 | status_report = commit_lint.status_report 348 | expect(report_counts(status_report)).to eq 1 349 | expect(status_report[:errors]).to eq [ 350 | message_with_sha(warning) 351 | ] 352 | end 353 | end 354 | end 355 | 356 | describe 'fail configuration' do 357 | let(:sha) { '1234567' } 358 | let(:commit) { double(:commit, message: message, sha: sha) } 359 | 360 | def message_with_sha(message) 361 | [message, sha].join "\n" 362 | end 363 | 364 | context 'with individual checks' do 365 | context 'with invalid messages' do 366 | it 'fails those checks' do 367 | checks = { 368 | subject_length: SubjectLengthCheck::MESSAGE, 369 | subject_period: SubjectPeriodCheck::MESSAGE, 370 | empty_line: EmptyLineCheck::MESSAGE 371 | } 372 | 373 | for (check, warning) in checks 374 | commit_lint = testing_dangerfile.commit_lint 375 | commit = double(:commit, message: TEST_MESSAGES[check], sha: sha) 376 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 377 | 378 | commit_lint.check fail: [check] 379 | 380 | status_report = commit_lint.status_report 381 | expect(report_counts(status_report)).to eq 1 382 | expect(status_report[:errors]).to eq [ 383 | message_with_sha(warning) 384 | ] 385 | end 386 | end 387 | end 388 | 389 | context 'with valid messages' do 390 | let(:message) { TEST_MESSAGES[:valid] } 391 | 392 | it 'does nothing' do 393 | checks = { 394 | subject_length: SubjectLengthCheck::MESSAGE, 395 | subject_period: SubjectPeriodCheck::MESSAGE, 396 | empty_line: EmptyLineCheck::MESSAGE 397 | } 398 | 399 | for (check, _) in checks 400 | commit_lint = testing_dangerfile.commit_lint 401 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 402 | 403 | commit_lint.check fail: [check] 404 | 405 | status_report = commit_lint.status_report 406 | expect(report_counts(status_report)).to eq 0 407 | end 408 | end 409 | end 410 | end 411 | 412 | context 'with all checks' do 413 | context 'with all errors' do 414 | let(:message) { TEST_MESSAGES[:all_errors] } 415 | 416 | it 'fails those checks' do 417 | commit_lint = testing_dangerfile.commit_lint 418 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 419 | 420 | commit_lint.check fail: :all 421 | 422 | status_report = commit_lint.status_report 423 | expect(report_counts(status_report)).to eq 4 424 | expect(status_report[:errors]).to eq [ 425 | message_with_sha(SubjectCapCheck::MESSAGE), 426 | message_with_sha(SubjectLengthCheck::MESSAGE), 427 | message_with_sha(SubjectPeriodCheck::MESSAGE), 428 | message_with_sha(EmptyLineCheck::MESSAGE) 429 | ] 430 | end 431 | end 432 | 433 | context 'with a valid message' do 434 | let(:message) { TEST_MESSAGES[:valid] } 435 | 436 | it 'does nothing' do 437 | commit_lint = testing_dangerfile.commit_lint 438 | allow(commit_lint.git).to receive(:commits).and_return([commit]) 439 | 440 | commit_lint.check fail: :all 441 | 442 | status_report = commit_lint.status_report 443 | expect(report_counts(status_report)).to eq 0 444 | end 445 | end 446 | 447 | context 'with repeated bad messages' do 448 | let(:commits) do 449 | [ 450 | double(:commit, message: TEST_MESSAGES[:empty_line], sha: 'sha1'), 451 | double(:commit, message: TEST_MESSAGES[:empty_line], sha: 'sha2') 452 | ] 453 | end 454 | 455 | it 'warnings are grouped' do 456 | commit_lint = testing_dangerfile.commit_lint 457 | allow(commit_lint.git).to receive(:commits).and_return(commits) 458 | 459 | commit_lint.check fail: :all 460 | 461 | status_report = commit_lint.status_report 462 | expect(report_counts(status_report)).to eq 1 463 | expect(status_report[:errors]).to eq [ 464 | EmptyLineCheck::MESSAGE + "\n" + 'sha1' + "\n" + 'sha2' 465 | ] 466 | end 467 | end 468 | end 469 | end 470 | end 471 | end 472 | 473 | # rubocop:enable Metrics/ClassLength 474 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | require 'pathname' 2 | ROOT = Pathname.new(File.expand_path('..', __dir__)) 3 | $LOAD_PATH.unshift((ROOT + 'lib').to_s) 4 | $LOAD_PATH.unshift((ROOT + 'spec').to_s) 5 | 6 | require 'bundler/setup' 7 | require 'pry' 8 | 9 | require 'rspec' 10 | require 'danger' 11 | 12 | # Use coloured output, it's the best. 13 | RSpec.configure do |config| 14 | config.filter_gems_from_backtrace 'bundler' 15 | config.color = true 16 | config.tty = true 17 | end 18 | 19 | require 'danger_plugin' 20 | 21 | # These functions are a subset of https://github.com/danger/danger/blob/master/spec/spec_helper.rb 22 | # If you are expanding these files, see if it's already been done ^. 23 | 24 | # A silent version of the user interface, 25 | # it comes with an extra function `.string` which will 26 | # strip all ANSI colours from the string. 27 | 28 | # rubocop:disable Lint/NestedMethodDefinition 29 | def testing_ui 30 | @output = StringIO.new 31 | def @output.winsize 32 | [20, 9999] 33 | end 34 | 35 | cork = Cork::Board.new(out: @output) 36 | def cork.string 37 | out.string.gsub(/\e\[([;\d]+)?m/, '') 38 | end 39 | cork 40 | end 41 | # rubocop:enable Lint/NestedMethodDefinition 42 | 43 | # Example environment (ENV) that would come from 44 | # running a PR on TravisCI 45 | def testing_env 46 | { 47 | 'HAS_JOSH_K_SEAL_OF_APPROVAL' => 'true', 48 | 'TRAVIS_PULL_REQUEST' => '800', 49 | 'TRAVIS_REPO_SLUG' => 'artsy/eigen', 50 | 'TRAVIS_COMMIT_RANGE' => '759adcbd0d8f...13c4dc8bb61d', 51 | 'DANGER_GITHUB_API_TOKEN' => '123sbdq54erfsd3422gdfio' 52 | } 53 | end 54 | 55 | # A stubbed out Dangerfile for use in tests 56 | def testing_dangerfile 57 | env = Danger::EnvironmentManager.new(testing_env) 58 | Danger::Dangerfile.new(env, testing_ui) 59 | end 60 | -------------------------------------------------------------------------------- /spec/subject_length_check_spec.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('spec_helper', __dir__) 2 | 3 | describe Danger::DangerCommitLint::SubjectLengthCheck do 4 | describe '#fail?' do 5 | let(:commit_subject) { 'This is a common valid commit message' } 6 | 7 | let(:message) do 8 | { 9 | subject: commit_subject 10 | } 11 | end 12 | 13 | subject do 14 | described_class.new(message).fail? 15 | end 16 | 17 | it { expect(subject).to be_falsy } 18 | 19 | context 'when message is invalid' do 20 | let(:commit_subject) do 21 | 'This is a really long subject line and should result in an error' 22 | end 23 | 24 | it { expect(subject).to be_truthy } 25 | end 26 | 27 | context 'when message is a merge commit generated by git' do 28 | let(:commit_subject) do 29 | "Merge branch 'ignore-length-on-git-merge-commits' into master" 30 | end 31 | 32 | it { expect(subject).to be_falsy } 33 | end 34 | 35 | context 'when message is a merge commit generated by GitHub' do 36 | let(:commit_subject) do 37 | 'Merge pull request #1234 from jonallured/ignore-length-merge-commits' 38 | end 39 | 40 | it { expect(subject).to be_falsy } 41 | end 42 | end 43 | end 44 | --------------------------------------------------------------------------------