fit-commit 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +25 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +25 -0
- data/LICENSE +22 -0
- data/README.md +48 -0
- data/Rakefile +10 -0
- data/TODO.txt +1 -0
- data/bin/fit-commit +9 -0
- data/fit-commit.gemspec +33 -0
- data/lib/fit-commit/cli.rb +20 -0
- data/lib/fit-commit/has_errors.rb +36 -0
- data/lib/fit-commit/installer.rb +29 -0
- data/lib/fit-commit/line.rb +22 -0
- data/lib/fit-commit/runner.rb +83 -0
- data/lib/fit-commit/validators/base.rb +33 -0
- data/lib/fit-commit/validators/frathouse.rb +14 -0
- data/lib/fit-commit/validators/line_length.rb +32 -0
- data/lib/fit-commit/validators/summary_period.rb +13 -0
- data/lib/fit-commit/validators/tense.rb +78 -0
- data/lib/fit-commit/validators/wip.rb +14 -0
- data/lib/fit-commit/version.rb +3 -0
- data/lib/fit-commit.rb +21 -0
- data/templates/hooks/commit-msg +25 -0
- data/test/runner_test.rb +81 -0
- data/test/validators/frathouse_test.rb +47 -0
- data/test/validators/line_length_test.rb +107 -0
- data/test/validators/summary_period_test.rb +25 -0
- data/test/validators/tense_test.rb +27 -0
- data/test/validators/wip_test.rb +38 -0
- metadata +127 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 81b67790de7d999eae5047a8acb714c14257f91d
|
4
|
+
data.tar.gz: 57e990996a4c9e9d704d483730cbc85ce2b46e65
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 621591bb43cd64e82dd41161c3375bf6bdabc1a3487899151e52837bbee44cbf149a030529d3a1129632c88609e75e9e5cd4ec01fa054b21e3240efbcf096e50
|
7
|
+
data.tar.gz: abb255557aa5b523d4ee9fa21eb1a46dd48362013536f3d93dc54d850032589bcf56fbf76311d1a7c4db3287f1324ef1532e9d6dead1496eef718add794615ec
|
data/.gitignore
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/test/tmp/
|
9
|
+
/test/version_tmp/
|
10
|
+
/tmp/
|
11
|
+
/bin/
|
12
|
+
|
13
|
+
## Environment normalisation:
|
14
|
+
/.bundle/
|
15
|
+
/vendor/bundle
|
16
|
+
/lib/bundler/man/
|
17
|
+
|
18
|
+
# for a library or gem, you might want to ignore these files since the code is
|
19
|
+
# intended to run in multiple environments; otherwise, check them in:
|
20
|
+
# Gemfile.lock
|
21
|
+
# .ruby-version
|
22
|
+
# .ruby-gemset
|
23
|
+
|
24
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
25
|
+
.rvmrc
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
fit-commit (1.0.0)
|
5
|
+
swearjar (~> 1.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
fuzzyhash (0.0.11)
|
11
|
+
minitest (5.8.0)
|
12
|
+
rake (10.4.2)
|
13
|
+
swearjar (1.0.0)
|
14
|
+
fuzzyhash (~> 0.0.11)
|
15
|
+
|
16
|
+
PLATFORMS
|
17
|
+
ruby
|
18
|
+
|
19
|
+
DEPENDENCIES
|
20
|
+
fit-commit!
|
21
|
+
minitest (~> 5.8)
|
22
|
+
rake (~> 10.4)
|
23
|
+
|
24
|
+
BUNDLED WITH
|
25
|
+
1.10.6
|
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Mike Foley
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
22
|
+
|
data/README.md
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
# Fit Commit
|
2
|
+
|
3
|
+
A Git hook to validate your commit messages, based largely on Tim Pope's [authoritative guide](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
4
|
+
|
5
|
+
## Example
|
6
|
+
|
7
|
+
```
|
8
|
+
$ git commit
|
9
|
+
Adding a cool feature
|
10
|
+
foobar foobar foobar,
|
11
|
+
foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar foobar
|
12
|
+
|
13
|
+
1: Error: Message must use present imperative tense.
|
14
|
+
2: Error: Second line must be blank.
|
15
|
+
3: Error: Lines should be <= 72 chars. (76)
|
16
|
+
|
17
|
+
Force commit? [y/n] ▊
|
18
|
+
```
|
19
|
+
|
20
|
+
## Prerequisites
|
21
|
+
|
22
|
+
* Ruby
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
Install the gem:
|
27
|
+
|
28
|
+
$ gem install fit-commit
|
29
|
+
|
30
|
+
Install the hook in your Git repo:
|
31
|
+
|
32
|
+
$ fit-commit install
|
33
|
+
|
34
|
+
This creates a `.git/hooks/commit-msg` script which will automatically check your Git commit messages.
|
35
|
+
|
36
|
+
## Validations
|
37
|
+
|
38
|
+
* **Line Length**: All lines must be <= 72 chars (URLs excluded). First line should be <= 50 chars. Second line must be blank.
|
39
|
+
* **Tense**: Message must use present imperative tense.
|
40
|
+
* **Summary Period**: Do not end your summary with a period.
|
41
|
+
* **WIP**: Do not commit WIPs to master.
|
42
|
+
* **Frat House**: No frat house commit messages in master.
|
43
|
+
|
44
|
+
## Credits
|
45
|
+
|
46
|
+
Author: [Mike Foley](https://github.com/m1foley)
|
47
|
+
|
48
|
+
Inspiration taken from: [Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html), [Jason Fox](https://gist.github.com/jasonrobertfox/8057124), [Addam Hardy](http://addamhardy.com/blog/2013/06/05/good-commit-messages-and-enforcing-them-with-git-hooks/), [pre-commit](https://github.com/jish/pre-commit)
|
data/Rakefile
ADDED
data/TODO.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
- Configuration to disable validations.
|
data/bin/fit-commit
ADDED
data/fit-commit.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path("../lib/fit-commit/version", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "fit-commit"
|
6
|
+
gem.version = FitCommit::VERSION
|
7
|
+
gem.license = "MIT"
|
8
|
+
gem.authors = ["Michael Foley"]
|
9
|
+
gem.email = ["foley3@gmail.com"]
|
10
|
+
gem.homepage = "https://github.com/m1foley/fit-commit"
|
11
|
+
gem.summary = "A Git hook to validate your commit messages"
|
12
|
+
gem.description = "A Git hook to validate your commit messages, based largely on Tim Pope's authoritative guide."
|
13
|
+
gem.files = `git ls-files`.split("\n")
|
14
|
+
gem.executables = ["fit-commit"]
|
15
|
+
gem.default_executable = "fit-commit"
|
16
|
+
gem.test_files = `git ls-files -- test/*`.split("\n")
|
17
|
+
gem.require_paths = ["lib"]
|
18
|
+
gem.extra_rdoc_files = ["README.md"]
|
19
|
+
gem.rdoc_options = ["--main", "README.md"]
|
20
|
+
|
21
|
+
gem.post_install_message == <<-EOF
|
22
|
+
Thank you for installing fit-commit!
|
23
|
+
Install the hook in each git repo you want to scan using:
|
24
|
+
|
25
|
+
> fit-commit install
|
26
|
+
|
27
|
+
Read more: https://github.com/m1foley/fit-commit#readme
|
28
|
+
EOF
|
29
|
+
|
30
|
+
gem.add_dependency("swearjar", "~> 1.0")
|
31
|
+
gem.add_development_dependency("minitest", "~> 5.8")
|
32
|
+
gem.add_development_dependency("rake", "~> 10.4")
|
33
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "fit-commit/installer"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
class Cli
|
5
|
+
attr_accessor :args
|
6
|
+
def initialize(*args)
|
7
|
+
self.args = args
|
8
|
+
end
|
9
|
+
|
10
|
+
def execute
|
11
|
+
action_name = args.shift
|
12
|
+
if action_name == "install"
|
13
|
+
FitCommit::Installer.new.install
|
14
|
+
else
|
15
|
+
warn "Usage: fit-commit install"
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Mixin for adding errors & warnings
|
2
|
+
module FitCommit
|
3
|
+
module HasErrors
|
4
|
+
def errors
|
5
|
+
@errors ||= Hash.new([])
|
6
|
+
end
|
7
|
+
|
8
|
+
def warnings
|
9
|
+
@warnings ||= Hash.new([])
|
10
|
+
end
|
11
|
+
|
12
|
+
def add_error(lineno, message)
|
13
|
+
errors[lineno] += [message]
|
14
|
+
end
|
15
|
+
|
16
|
+
def add_warning(lineno, message)
|
17
|
+
warnings[lineno] += [message]
|
18
|
+
end
|
19
|
+
|
20
|
+
def merge_errors(other_errors)
|
21
|
+
merge_hashes(errors, other_errors)
|
22
|
+
end
|
23
|
+
|
24
|
+
def merge_warnings(other_warnings)
|
25
|
+
merge_hashes(warnings, other_warnings)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def merge_hashes(error_hash, other_hash)
|
31
|
+
error_hash.merge!(other_hash) do |_lineno, messages, other_messages|
|
32
|
+
messages + other_messages
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
class Installer
|
5
|
+
HOOK_TEMPLATE_PATH = File.expand_path("../../../templates/hooks/commit-msg", __FILE__)
|
6
|
+
|
7
|
+
def install
|
8
|
+
FileUtils.mkdir_p(File.dirname(hook_path))
|
9
|
+
FileUtils.cp(HOOK_TEMPLATE_PATH, hook_path)
|
10
|
+
FileUtils.chmod(0755, hook_path)
|
11
|
+
puts "Installed hook to #{hook_path}"
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def hook_path
|
18
|
+
@hook_path ||= File.join(gitdir, "hooks/commit-msg")
|
19
|
+
end
|
20
|
+
|
21
|
+
def gitdir
|
22
|
+
if File.directory?(".git")
|
23
|
+
".git"
|
24
|
+
else
|
25
|
+
File.readlines(".git").first.match(/gitdir: (.*)$/)[1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Represents a line from the commit
|
2
|
+
module FitCommit
|
3
|
+
class Line
|
4
|
+
attr_accessor :lineno, :text
|
5
|
+
def initialize(lineno, text)
|
6
|
+
self.lineno = lineno
|
7
|
+
self.text = text
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
text
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
text.empty?
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.from_array(text_array)
|
19
|
+
text_array.map.with_index(1) { |text, lineno| new(lineno, text) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require "fit-commit/has_errors"
|
2
|
+
require "fit-commit/line"
|
3
|
+
|
4
|
+
module FitCommit
|
5
|
+
class Runner
|
6
|
+
include FitCommit::HasErrors
|
7
|
+
|
8
|
+
attr_accessor :message_path, :branch_name, :stdout, :stdin
|
9
|
+
def initialize(message_path, branch_name, stdout = $stdout, stdin = $stdin)
|
10
|
+
self.message_path = message_path
|
11
|
+
self.branch_name = branch_name
|
12
|
+
self.stdout = stdout
|
13
|
+
self.stdin = stdin
|
14
|
+
end
|
15
|
+
|
16
|
+
def run
|
17
|
+
return true if empty_commit?
|
18
|
+
run_validators
|
19
|
+
return true if [errors, warnings].all?(&:empty?)
|
20
|
+
print_results
|
21
|
+
|
22
|
+
allow_commit = errors.empty?
|
23
|
+
unless allow_commit
|
24
|
+
stdout.print "\nForce commit? [y/n] "
|
25
|
+
return false unless stdin.gets =~ /y/i
|
26
|
+
allow_commit = true
|
27
|
+
end
|
28
|
+
|
29
|
+
stdout.print "\n"
|
30
|
+
allow_commit
|
31
|
+
rescue Interrupt # Ctrl-c
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def run_validators
|
38
|
+
Dir[File.dirname(__FILE__) + "/validators/*.rb"].each { |file| require file }
|
39
|
+
FitCommit::Validators::Base.all.each do |validator_class|
|
40
|
+
validator = validator_class.new(lines, branch_name)
|
41
|
+
validator.validate
|
42
|
+
merge_errors(validator.errors)
|
43
|
+
merge_warnings(validator.warnings)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def print_results
|
48
|
+
unless errors.empty?
|
49
|
+
stdout.puts lines
|
50
|
+
stdout.print "\n"
|
51
|
+
end
|
52
|
+
|
53
|
+
(errors.keys | warnings.keys).sort.each do |lineno|
|
54
|
+
errors[lineno].each do |error|
|
55
|
+
stdout.puts "#{lineno}: Error: #{error}"
|
56
|
+
end
|
57
|
+
warnings[lineno].each do |warning|
|
58
|
+
stdout.puts "#{lineno}: Warning: #{warning}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def lines
|
64
|
+
@lines ||= FitCommit::Line.from_array(relevant_message_lines)
|
65
|
+
end
|
66
|
+
|
67
|
+
def relevant_message_lines
|
68
|
+
message_text.lines.map(&:chomp).reject(&method(:comment?))
|
69
|
+
end
|
70
|
+
|
71
|
+
def message_text
|
72
|
+
File.open(message_path, "r").read
|
73
|
+
end
|
74
|
+
|
75
|
+
def comment?(text)
|
76
|
+
text =~ /\A#/
|
77
|
+
end
|
78
|
+
|
79
|
+
def empty_commit?
|
80
|
+
lines.all?(&:empty?)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require "fit-commit/has_errors"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
module Validators
|
5
|
+
class Base
|
6
|
+
include FitCommit::HasErrors
|
7
|
+
|
8
|
+
attr_accessor :lines, :branch_name
|
9
|
+
def initialize(lines, branch_name)
|
10
|
+
self.lines = lines
|
11
|
+
self.branch_name = branch_name
|
12
|
+
end
|
13
|
+
|
14
|
+
@all = []
|
15
|
+
class << self
|
16
|
+
attr_accessor :all
|
17
|
+
def inherited(subclass)
|
18
|
+
all << subclass
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def validate
|
23
|
+
lines.each do |line|
|
24
|
+
validate_line(line.lineno, line.text, branch_name)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def validate_line(*)
|
29
|
+
fail NotImplementedError, "Implement in subclass"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "fit-commit/validators/base"
|
2
|
+
require "swearjar"
|
3
|
+
|
4
|
+
module FitCommit
|
5
|
+
module Validators
|
6
|
+
class Frathouse < Base
|
7
|
+
def validate_line(lineno, text, branch_name)
|
8
|
+
if branch_name == "master" && Swearjar.default.profane?(text)
|
9
|
+
add_error(lineno, "No frat house commit messages in master.")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "fit-commit/validators/base"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
module Validators
|
5
|
+
class LineLength < Base
|
6
|
+
FIRST_LINE_MAX_LENGTH = 50
|
7
|
+
LINE_MAX_LENGTH = 72
|
8
|
+
|
9
|
+
def validate_line(lineno, text, _branch_name)
|
10
|
+
if lineno == 1 && text.empty?
|
11
|
+
add_error(lineno, "First line cannot be blank.")
|
12
|
+
elsif lineno == 2 && !text.empty?
|
13
|
+
add_error(lineno, "Second line must be blank.")
|
14
|
+
elsif line_too_long?(text)
|
15
|
+
add_error(lineno, format("Lines should be <= %i chars. (%i)",
|
16
|
+
LINE_MAX_LENGTH, text.length))
|
17
|
+
elsif lineno == 1 && text.length > FIRST_LINE_MAX_LENGTH
|
18
|
+
add_warning(lineno, format("First line should be <= %i chars. (%i)",
|
19
|
+
FIRST_LINE_MAX_LENGTH, text.length))
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def line_too_long?(text)
|
24
|
+
text.length > 72 && !contains_url?(text)
|
25
|
+
end
|
26
|
+
|
27
|
+
def contains_url?(text)
|
28
|
+
text =~ %r{[a-z]+://}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "fit-commit/validators/base"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
module Validators
|
5
|
+
class SummaryPeriod < Base
|
6
|
+
def validate_line(lineno, text, branch_name)
|
7
|
+
if lineno == 1 && text.end_with?(".")
|
8
|
+
add_error(lineno, "Do not end your summary with a period.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "fit-commit/validators/base"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
module Validators
|
5
|
+
class Tense < Base
|
6
|
+
VERB_BLACKLIST = %w(
|
7
|
+
adds adding added
|
8
|
+
allows allowing allowed
|
9
|
+
amends amending amended
|
10
|
+
bumps bumping bumped
|
11
|
+
calculates calculating calculated
|
12
|
+
changes changing changed
|
13
|
+
cleans cleaning cleaned
|
14
|
+
commits committing committed
|
15
|
+
corrects correcting corrected
|
16
|
+
creates creating created
|
17
|
+
darkens darkening darkened
|
18
|
+
disables disabling disabled
|
19
|
+
displays displaying displayed
|
20
|
+
drys drying dryed
|
21
|
+
ends ending ended
|
22
|
+
enforces enforcing enforced
|
23
|
+
enqueues enqueuing enqueued
|
24
|
+
extracts extracting extracted
|
25
|
+
finishes finishing finished
|
26
|
+
fixes fixing fixed
|
27
|
+
formats formatting formatted
|
28
|
+
guards guarding guarded
|
29
|
+
handles handling handled
|
30
|
+
hides hiding hid
|
31
|
+
increases increasing increased
|
32
|
+
ignores ignoring ignored
|
33
|
+
implements implementing implemented
|
34
|
+
improves improving improved
|
35
|
+
keeps keeping kept
|
36
|
+
kills killing killed
|
37
|
+
makes making made
|
38
|
+
merges merging merged
|
39
|
+
moves moving moved
|
40
|
+
permits permitting permitted
|
41
|
+
prevents preventing prevented
|
42
|
+
pushes pushing pushed
|
43
|
+
rebases rebasing rebased
|
44
|
+
refactors refactoring refactored
|
45
|
+
removes removing removed
|
46
|
+
renames renaming renamed
|
47
|
+
reorders reordering reordered
|
48
|
+
requires requiring required
|
49
|
+
restores restoring restored
|
50
|
+
sends sending sent
|
51
|
+
sets setting
|
52
|
+
separates separating separated
|
53
|
+
shows showing showed
|
54
|
+
skips skipping skipped
|
55
|
+
sorts sorting
|
56
|
+
speeds speeding sped
|
57
|
+
starts starting started
|
58
|
+
supports supporting supported
|
59
|
+
takes taking took
|
60
|
+
tests testing tested
|
61
|
+
truncates truncating truncated
|
62
|
+
updates updating updated
|
63
|
+
uses using used
|
64
|
+
)
|
65
|
+
|
66
|
+
def validate_line(lineno, text, _branch_name)
|
67
|
+
if lineno == 1 && wrong_tense?(text)
|
68
|
+
add_error(lineno, "Message must use present imperative tense.")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def wrong_tense?(text)
|
73
|
+
first_word = text.split.first(2).detect { |w| w =~ /\A\w/ }
|
74
|
+
first_word && VERB_BLACKLIST.include?(first_word.downcase)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "fit-commit/validators/base"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
module Validators
|
5
|
+
class Wip < Base
|
6
|
+
def validate_line(lineno, text, branch_name)
|
7
|
+
if lineno == 1 && branch_name == "master" &&
|
8
|
+
text.split.any? { |word| word == "WIP" }
|
9
|
+
add_error(lineno, "Do not commit WIPs to master.")
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/fit-commit.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require "fit-commit/runner"
|
2
|
+
|
3
|
+
module FitCommit
|
4
|
+
def self.run
|
5
|
+
runner.run || exit(1)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def self.runner
|
11
|
+
FitCommit::Runner.new(message_path, branch_name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.message_path
|
15
|
+
ENV.fetch("COMMIT_MESSAGE_PATH")
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.branch_name
|
19
|
+
`git branch | grep '^\*' | cut -b3-`.strip
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env sh
|
2
|
+
#
|
3
|
+
# This hook will attempt to setup your environment before running checks.
|
4
|
+
|
5
|
+
if which rvm >/dev/null 2>/dev/null
|
6
|
+
then cmd="rvm default do ruby"
|
7
|
+
elif which rbenv >/dev/null 2>/dev/null
|
8
|
+
then cmd="rbenv exec ruby"
|
9
|
+
else cmd="ruby"
|
10
|
+
fi
|
11
|
+
|
12
|
+
export COMMIT_MESSAGE_PATH=$1
|
13
|
+
|
14
|
+
${cmd} -rrubygems -e '
|
15
|
+
begin
|
16
|
+
require "fit-commit"
|
17
|
+
true
|
18
|
+
rescue LoadError => e
|
19
|
+
$stderr.puts <<-MESSAGE
|
20
|
+
fit-commit: WARNING: Skipping checks because: #{e}
|
21
|
+
fit-commit: Did you set your Ruby version?
|
22
|
+
MESSAGE
|
23
|
+
false
|
24
|
+
end and FitCommit.run
|
25
|
+
' < /dev/tty
|
data/test/runner_test.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "fit-commit/runner"
|
3
|
+
|
4
|
+
describe FitCommit::Runner do
|
5
|
+
after do
|
6
|
+
commit_msg_file.unlink
|
7
|
+
end
|
8
|
+
|
9
|
+
def call_runner
|
10
|
+
allow_commit = runner.run
|
11
|
+
stdout.rewind
|
12
|
+
allow_commit
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:commit_msg_file) do
|
16
|
+
Tempfile.new("test-commit-msg").tap do |f|
|
17
|
+
f.write(commit_msg)
|
18
|
+
f.close
|
19
|
+
end
|
20
|
+
end
|
21
|
+
let(:stdout) { StringIO.new }
|
22
|
+
let(:stdin) { StringIO.new }
|
23
|
+
let(:branch_name) { "any" }
|
24
|
+
let(:runner) do
|
25
|
+
FitCommit::Runner.new(commit_msg_file.path, branch_name, stdout, stdin)
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "empty commit msg" do
|
29
|
+
let(:commit_msg) { "" }
|
30
|
+
it "returns truthy value without printing to stdout" do
|
31
|
+
assert call_runner
|
32
|
+
assert stdout.read.empty?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "commit msg consists of all comments" do
|
37
|
+
let(:commit_msg) { "\n#hi\n#yo" }
|
38
|
+
it "returns truthy value without printing to stdout" do
|
39
|
+
assert call_runner
|
40
|
+
assert stdout.read.empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "commit msg is present but no errors" do
|
45
|
+
let(:commit_msg) { "hello\n\nhi\n#" }
|
46
|
+
it "returns truthy value without printing to stdout" do
|
47
|
+
assert call_runner
|
48
|
+
assert stdout.read.empty?
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "commit msg contains errors" do
|
53
|
+
let(:commit_msg) { "foo.\nbar" }
|
54
|
+
|
55
|
+
def assert_error_output
|
56
|
+
stdout_lines = stdout.read.lines.map(&:chomp)
|
57
|
+
assert_equal 7, stdout_lines.size
|
58
|
+
assert_equal commit_msg, stdout_lines[0..1].join("\n")
|
59
|
+
assert_empty stdout_lines[2]
|
60
|
+
assert_match(/\A1: Error: /, stdout_lines[3])
|
61
|
+
assert_match(/\A2: Error: /, stdout_lines[4])
|
62
|
+
assert_empty stdout_lines[5]
|
63
|
+
assert_equal "Force commit? [y/n] ", stdout_lines[6]
|
64
|
+
end
|
65
|
+
|
66
|
+
describe "user does not force commit" do
|
67
|
+
let(:stdin) { StringIO.new("n") }
|
68
|
+
it "prints errors to stdout and returns falsey value" do
|
69
|
+
assert !call_runner
|
70
|
+
assert_error_output
|
71
|
+
end
|
72
|
+
end
|
73
|
+
describe "user forces commit" do
|
74
|
+
let(:stdin) { StringIO.new("y") }
|
75
|
+
it "prints errors to stdout and returns truthy value" do
|
76
|
+
assert call_runner
|
77
|
+
assert_error_output
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "fit-commit/validators/frathouse"
|
3
|
+
|
4
|
+
describe FitCommit::Validators::Frathouse do
|
5
|
+
let(:validator) { FitCommit::Validators::Frathouse.new(commit_lines, branch_name) }
|
6
|
+
let(:commit_lines) { FitCommit::Line.from_array(commit_msg.split("\n")) }
|
7
|
+
|
8
|
+
describe "master branch" do
|
9
|
+
let(:branch_name) { "master" }
|
10
|
+
describe "contains swear word" do
|
11
|
+
let(:commit_msg) { "fucking foobar" }
|
12
|
+
it "has error" do
|
13
|
+
validator.validate
|
14
|
+
assert_equal 1, validator.errors[1].size
|
15
|
+
assert_empty validator.warnings
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe "contains swear words on multiple lines" do
|
19
|
+
let(:commit_msg) { "damn\n\nIE7 is fucking bullshit!" }
|
20
|
+
it "has 1 error per line" do
|
21
|
+
validator.validate
|
22
|
+
assert_equal 1, validator.errors[1].size
|
23
|
+
assert_equal 1, validator.errors[3].size
|
24
|
+
assert_empty validator.warnings
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe "does not contain swear words" do
|
28
|
+
let(:commit_msg) { "foo" }
|
29
|
+
it "does not have errors/warnings" do
|
30
|
+
validator.validate
|
31
|
+
assert_empty validator.errors
|
32
|
+
assert_empty validator.warnings
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
describe "not master branch" do
|
37
|
+
let(:branch_name) { "notmaster" }
|
38
|
+
describe "contains swear word" do
|
39
|
+
let(:commit_msg) { "fucking foo" }
|
40
|
+
it "does not have errors/warnings" do
|
41
|
+
validator.validate
|
42
|
+
assert_empty validator.errors
|
43
|
+
assert_empty validator.warnings
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "fit-commit/validators/line_length"
|
3
|
+
|
4
|
+
describe FitCommit::Validators::LineLength do
|
5
|
+
let(:validator) { FitCommit::Validators::LineLength.new(commit_lines, branch_name) }
|
6
|
+
let(:commit_lines) { FitCommit::Line.from_array(commit_msg.split("\n")) }
|
7
|
+
|
8
|
+
let(:branch_name) { "any" }
|
9
|
+
|
10
|
+
describe "first line" do
|
11
|
+
describe "first line is empty" do
|
12
|
+
let(:commit_msg) { "\n\nbar" }
|
13
|
+
it "has error" do
|
14
|
+
validator.validate
|
15
|
+
assert_equal 1, validator.errors[1].size
|
16
|
+
assert_empty validator.warnings
|
17
|
+
end
|
18
|
+
end
|
19
|
+
describe "first line is not empty" do
|
20
|
+
let(:commit_msg) { "foo\n\nbar" }
|
21
|
+
it "does not have error" do
|
22
|
+
validator.validate
|
23
|
+
assert_empty validator.errors
|
24
|
+
assert_empty validator.warnings
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe "first line is over warning limit" do
|
28
|
+
let(:commit_msg) do
|
29
|
+
"x" * (FitCommit::Validators::LineLength::FIRST_LINE_MAX_LENGTH + 1)
|
30
|
+
end
|
31
|
+
it "has a warning" do
|
32
|
+
validator.validate
|
33
|
+
assert_empty validator.errors
|
34
|
+
assert_equal 1, validator.warnings[1].size
|
35
|
+
end
|
36
|
+
end
|
37
|
+
describe "first line is over error limit" do
|
38
|
+
let(:commit_msg) do
|
39
|
+
"x" * (FitCommit::Validators::LineLength::LINE_MAX_LENGTH + 1)
|
40
|
+
end
|
41
|
+
it "has an error and no warning" do
|
42
|
+
validator.validate
|
43
|
+
assert_equal 1, validator.errors[1].size
|
44
|
+
assert_empty validator.warnings
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
describe "second line" do
|
49
|
+
describe "second line is not empty" do
|
50
|
+
let(:commit_msg) { "foo\nbar" }
|
51
|
+
it "has error" do
|
52
|
+
validator.validate
|
53
|
+
assert_equal 1, validator.errors[2].size
|
54
|
+
assert_empty validator.warnings
|
55
|
+
end
|
56
|
+
end
|
57
|
+
describe "second line is not empty and too long" do
|
58
|
+
let(:commit_msg) do
|
59
|
+
"foo\n" + ("x" * (FitCommit::Validators::LineLength::LINE_MAX_LENGTH + 1))
|
60
|
+
end
|
61
|
+
it "only mentions blank error" do
|
62
|
+
validator.validate
|
63
|
+
assert_equal 1, validator.errors[2].size
|
64
|
+
assert_match(/must be blank/, validator.errors[2][0])
|
65
|
+
assert_empty validator.warnings
|
66
|
+
end
|
67
|
+
end
|
68
|
+
describe "second line is empty" do
|
69
|
+
let(:commit_msg) { "foo\n\nbar" }
|
70
|
+
it "does not have error" do
|
71
|
+
validator.validate
|
72
|
+
assert_empty validator.errors
|
73
|
+
assert_empty validator.warnings
|
74
|
+
end
|
75
|
+
end
|
76
|
+
describe "does not have a second line" do
|
77
|
+
let(:commit_msg) { "foo" }
|
78
|
+
it "does not have error" do
|
79
|
+
validator.validate
|
80
|
+
assert_empty validator.errors
|
81
|
+
assert_empty validator.warnings
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
describe "body text" do
|
86
|
+
describe "line is over length limit" do
|
87
|
+
let(:commit_msg) do
|
88
|
+
"foo\n\n" + ("x" * (FitCommit::Validators::LineLength::LINE_MAX_LENGTH + 1))
|
89
|
+
end
|
90
|
+
it "has error" do
|
91
|
+
validator.validate
|
92
|
+
assert_equal 1, validator.errors[3].size
|
93
|
+
assert_empty validator.warnings
|
94
|
+
end
|
95
|
+
end
|
96
|
+
describe "line is equal to length limit" do
|
97
|
+
let(:commit_msg) do
|
98
|
+
"foo\n\n" + ("x" * FitCommit::Validators::LineLength::LINE_MAX_LENGTH)
|
99
|
+
end
|
100
|
+
it "does not have error" do
|
101
|
+
validator.validate
|
102
|
+
assert_empty validator.errors
|
103
|
+
assert_empty validator.warnings
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "fit-commit/validators/summary_period"
|
3
|
+
|
4
|
+
describe FitCommit::Validators::SummaryPeriod do
|
5
|
+
let(:validator) { FitCommit::Validators::SummaryPeriod.new(commit_lines, branch_name) }
|
6
|
+
let(:commit_lines) { FitCommit::Line.from_array(commit_msg.split("\n")) }
|
7
|
+
let(:branch_name) { "any" }
|
8
|
+
|
9
|
+
describe "summary ends with period" do
|
10
|
+
let(:commit_msg) { "foo bar." }
|
11
|
+
it "has error" do
|
12
|
+
validator.validate
|
13
|
+
assert_equal 1, validator.errors[1].size
|
14
|
+
assert_empty validator.warnings
|
15
|
+
end
|
16
|
+
end
|
17
|
+
describe "summary does not end with period" do
|
18
|
+
let(:commit_msg) { "foo bar\n\nhi." }
|
19
|
+
it "does not have errors/warnings" do
|
20
|
+
validator.validate
|
21
|
+
assert_empty validator.errors
|
22
|
+
assert_empty validator.warnings
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "fit-commit/validators/tense"
|
3
|
+
|
4
|
+
describe FitCommit::Validators::Tense do
|
5
|
+
let(:validator) { FitCommit::Validators::Tense.new(commit_lines, branch_name) }
|
6
|
+
let(:commit_lines) { FitCommit::Line.from_array(commit_msg.split("\n")) }
|
7
|
+
|
8
|
+
let(:branch_name) { "anybranch" }
|
9
|
+
|
10
|
+
describe "uses incorrect tense on first line" do
|
11
|
+
let(:commit_msg) { "Changed something\nhi" }
|
12
|
+
it "has error" do
|
13
|
+
validator.validate
|
14
|
+
assert_equal 1, validator.errors[1].size
|
15
|
+
assert_empty validator.warnings
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "uses incorrect tense on a line other than first line" do
|
20
|
+
let(:commit_msg) { "hi\nChanged something" }
|
21
|
+
it "does not have errors/warnings" do
|
22
|
+
validator.validate
|
23
|
+
assert_empty validator.errors
|
24
|
+
assert_empty validator.warnings
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
require "fit-commit/validators/wip"
|
3
|
+
|
4
|
+
describe FitCommit::Validators::Wip do
|
5
|
+
let(:validator) { FitCommit::Validators::Wip.new(commit_lines, branch_name) }
|
6
|
+
let(:commit_lines) { FitCommit::Line.from_array(commit_msg.split("\n")) }
|
7
|
+
|
8
|
+
describe "master branch" do
|
9
|
+
let(:branch_name) { "master" }
|
10
|
+
describe "contains WIP" do
|
11
|
+
let(:commit_msg) { "WIP foo" }
|
12
|
+
it "has error" do
|
13
|
+
validator.validate
|
14
|
+
assert_equal 1, validator.errors[1].size
|
15
|
+
assert_empty validator.warnings
|
16
|
+
end
|
17
|
+
end
|
18
|
+
describe "does not contain WIP" do
|
19
|
+
let(:commit_msg) { "foo" }
|
20
|
+
it "does not have errors/warnings" do
|
21
|
+
validator.validate
|
22
|
+
assert_empty validator.errors
|
23
|
+
assert_empty validator.warnings
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe "not master branch" do
|
28
|
+
let(:branch_name) { "notmaster" }
|
29
|
+
describe "contains WIP" do
|
30
|
+
let(:commit_msg) { "WIP foo" }
|
31
|
+
it "does not have errors/warnings" do
|
32
|
+
validator.validate
|
33
|
+
assert_empty validator.errors
|
34
|
+
assert_empty validator.warnings
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
metadata
ADDED
@@ -0,0 +1,127 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fit-commit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Foley
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-26 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: swearjar
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: minitest
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '5.8'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '5.8'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.4'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.4'
|
55
|
+
description: A Git hook to validate your commit messages, based largely on Tim Pope's
|
56
|
+
authoritative guide.
|
57
|
+
email:
|
58
|
+
- foley3@gmail.com
|
59
|
+
executables:
|
60
|
+
- fit-commit
|
61
|
+
extensions: []
|
62
|
+
extra_rdoc_files:
|
63
|
+
- README.md
|
64
|
+
files:
|
65
|
+
- ".gitignore"
|
66
|
+
- Gemfile
|
67
|
+
- Gemfile.lock
|
68
|
+
- LICENSE
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- TODO.txt
|
72
|
+
- bin/fit-commit
|
73
|
+
- fit-commit.gemspec
|
74
|
+
- lib/fit-commit.rb
|
75
|
+
- lib/fit-commit/cli.rb
|
76
|
+
- lib/fit-commit/has_errors.rb
|
77
|
+
- lib/fit-commit/installer.rb
|
78
|
+
- lib/fit-commit/line.rb
|
79
|
+
- lib/fit-commit/runner.rb
|
80
|
+
- lib/fit-commit/validators/base.rb
|
81
|
+
- lib/fit-commit/validators/frathouse.rb
|
82
|
+
- lib/fit-commit/validators/line_length.rb
|
83
|
+
- lib/fit-commit/validators/summary_period.rb
|
84
|
+
- lib/fit-commit/validators/tense.rb
|
85
|
+
- lib/fit-commit/validators/wip.rb
|
86
|
+
- lib/fit-commit/version.rb
|
87
|
+
- templates/hooks/commit-msg
|
88
|
+
- test/runner_test.rb
|
89
|
+
- test/validators/frathouse_test.rb
|
90
|
+
- test/validators/line_length_test.rb
|
91
|
+
- test/validators/summary_period_test.rb
|
92
|
+
- test/validators/tense_test.rb
|
93
|
+
- test/validators/wip_test.rb
|
94
|
+
homepage: https://github.com/m1foley/fit-commit
|
95
|
+
licenses:
|
96
|
+
- MIT
|
97
|
+
metadata: {}
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options:
|
100
|
+
- "--main"
|
101
|
+
- README.md
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.2.3
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: A Git hook to validate your commit messages
|
120
|
+
test_files:
|
121
|
+
- test/runner_test.rb
|
122
|
+
- test/validators/frathouse_test.rb
|
123
|
+
- test/validators/line_length_test.rb
|
124
|
+
- test/validators/summary_period_test.rb
|
125
|
+
- test/validators/tense_test.rb
|
126
|
+
- test/validators/wip_test.rb
|
127
|
+
has_rdoc:
|