fit-commit 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|