git-lint 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/LICENSE.adoc +162 -0
  5. data/README.adoc +1068 -0
  6. data/bin/git-lint +9 -0
  7. data/lib/git/kit/repo.rb +30 -0
  8. data/lib/git/lint.rb +51 -0
  9. data/lib/git/lint/analyzers/abstract.rb +108 -0
  10. data/lib/git/lint/analyzers/commit_author_capitalization.rb +35 -0
  11. data/lib/git/lint/analyzers/commit_author_email.rb +35 -0
  12. data/lib/git/lint/analyzers/commit_author_name.rb +40 -0
  13. data/lib/git/lint/analyzers/commit_body_bullet.rb +43 -0
  14. data/lib/git/lint/analyzers/commit_body_bullet_capitalization.rb +46 -0
  15. data/lib/git/lint/analyzers/commit_body_bullet_delimiter.rb +40 -0
  16. data/lib/git/lint/analyzers/commit_body_issue_tracker_link.rb +45 -0
  17. data/lib/git/lint/analyzers/commit_body_leading_line.rb +30 -0
  18. data/lib/git/lint/analyzers/commit_body_line_length.rb +42 -0
  19. data/lib/git/lint/analyzers/commit_body_paragraph_capitalization.rb +47 -0
  20. data/lib/git/lint/analyzers/commit_body_phrase.rb +72 -0
  21. data/lib/git/lint/analyzers/commit_body_presence.rb +36 -0
  22. data/lib/git/lint/analyzers/commit_body_single_bullet.rb +40 -0
  23. data/lib/git/lint/analyzers/commit_subject_length.rb +33 -0
  24. data/lib/git/lint/analyzers/commit_subject_prefix.rb +42 -0
  25. data/lib/git/lint/analyzers/commit_subject_suffix.rb +39 -0
  26. data/lib/git/lint/analyzers/commit_trailer_collaborator_capitalization.rb +51 -0
  27. data/lib/git/lint/analyzers/commit_trailer_collaborator_duplication.rb +56 -0
  28. data/lib/git/lint/analyzers/commit_trailer_collaborator_email.rb +52 -0
  29. data/lib/git/lint/analyzers/commit_trailer_collaborator_key.rb +56 -0
  30. data/lib/git/lint/analyzers/commit_trailer_collaborator_name.rb +57 -0
  31. data/lib/git/lint/branches/environments/circle_ci.rb +28 -0
  32. data/lib/git/lint/branches/environments/local.rb +28 -0
  33. data/lib/git/lint/branches/environments/netlify_ci.rb +34 -0
  34. data/lib/git/lint/branches/environments/travis_ci.rb +57 -0
  35. data/lib/git/lint/branches/feature.rb +44 -0
  36. data/lib/git/lint/cli.rb +122 -0
  37. data/lib/git/lint/collector.rb +64 -0
  38. data/lib/git/lint/commits/saved.rb +104 -0
  39. data/lib/git/lint/commits/unsaved.rb +120 -0
  40. data/lib/git/lint/errors/base.rb +14 -0
  41. data/lib/git/lint/errors/severity.rb +13 -0
  42. data/lib/git/lint/errors/sha.rb +13 -0
  43. data/lib/git/lint/identity.rb +13 -0
  44. data/lib/git/lint/kit/filter_list.rb +30 -0
  45. data/lib/git/lint/parsers/trailers/collaborator.rb +57 -0
  46. data/lib/git/lint/rake/setup.rb +4 -0
  47. data/lib/git/lint/rake/tasks.rb +33 -0
  48. data/lib/git/lint/refinements/strings.rb +25 -0
  49. data/lib/git/lint/reporters/branch.rb +67 -0
  50. data/lib/git/lint/reporters/commit.rb +30 -0
  51. data/lib/git/lint/reporters/line.rb +32 -0
  52. data/lib/git/lint/reporters/lines/paragraph.rb +49 -0
  53. data/lib/git/lint/reporters/lines/sentence.rb +31 -0
  54. data/lib/git/lint/reporters/style.rb +52 -0
  55. data/lib/git/lint/runner.rb +34 -0
  56. data/lib/git/lint/validators/capitalization.rb +29 -0
  57. data/lib/git/lint/validators/email.rb +24 -0
  58. data/lib/git/lint/validators/name.rb +30 -0
  59. metadata +363 -0
  60. metadata.gz.sig +3 -0
@@ -0,0 +1,9 @@
1
+ #! /usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "git/lint"
5
+ require "git/lint/cli"
6
+ require "git/lint/identity"
7
+
8
+ Process.setproctitle Git::Lint::Identity::VERSION_LABEL
9
+ Git::Lint::CLI.start
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Kit
5
+ class Repo
6
+ def initialize shell: Open3
7
+ @shell = shell
8
+ end
9
+
10
+ def exist?
11
+ shell.capture2e("git rev-parse --git-dir > /dev/null 2>&1")
12
+ .then { |result, status| result && status.success? }
13
+ end
14
+
15
+ def branch_name
16
+ shell.capture2e("git rev-parse --abbrev-ref HEAD | tr -d '\n'")
17
+ .then { |result, _status| result }
18
+ end
19
+
20
+ def shas start: "master", finish: branch_name
21
+ shell.capture2e(%(git log --pretty=format:"%H" #{start}..#{finish}))
22
+ .then { |result, _status| result.split "\n" }
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :shell
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "git/lint/identity"
4
+ require "git/kit/repo"
5
+ require "git/lint/refinements/strings"
6
+ require "git/lint/errors/base"
7
+ require "git/lint/errors/severity"
8
+ require "git/lint/errors/sha"
9
+ require "git/lint/kit/filter_list"
10
+ require "git/lint/validators/email"
11
+ require "git/lint/validators/name"
12
+ require "git/lint/validators/capitalization"
13
+ require "git/lint/parsers/trailers/collaborator"
14
+ require "git/lint/commits/saved"
15
+ require "git/lint/commits/unsaved"
16
+ require "git/lint/branches/environments/local"
17
+ require "git/lint/branches/environments/circle_ci"
18
+ require "git/lint/branches/environments/netlify_ci"
19
+ require "git/lint/branches/environments/travis_ci"
20
+ require "git/lint/branches/feature"
21
+ require "git/lint/analyzers/abstract"
22
+ require "git/lint/analyzers/commit_author_capitalization"
23
+ require "git/lint/analyzers/commit_author_email"
24
+ require "git/lint/analyzers/commit_author_name"
25
+ require "git/lint/analyzers/commit_body_bullet"
26
+ require "git/lint/analyzers/commit_body_bullet_capitalization"
27
+ require "git/lint/analyzers/commit_body_bullet_delimiter"
28
+ require "git/lint/analyzers/commit_body_issue_tracker_link"
29
+ require "git/lint/analyzers/commit_body_leading_line"
30
+ require "git/lint/analyzers/commit_body_line_length"
31
+ require "git/lint/analyzers/commit_body_paragraph_capitalization"
32
+ require "git/lint/analyzers/commit_body_phrase"
33
+ require "git/lint/analyzers/commit_body_presence"
34
+ require "git/lint/analyzers/commit_body_single_bullet"
35
+ require "git/lint/analyzers/commit_subject_length"
36
+ require "git/lint/analyzers/commit_subject_prefix"
37
+ require "git/lint/analyzers/commit_subject_suffix"
38
+ require "git/lint/analyzers/commit_trailer_collaborator_capitalization"
39
+ require "git/lint/analyzers/commit_trailer_collaborator_duplication"
40
+ require "git/lint/analyzers/commit_trailer_collaborator_email"
41
+ require "git/lint/analyzers/commit_trailer_collaborator_key"
42
+ require "git/lint/analyzers/commit_trailer_collaborator_name"
43
+ require "git/lint/collector"
44
+ require "git/lint/reporters/lines/sentence"
45
+ require "git/lint/reporters/lines/paragraph"
46
+ require "git/lint/reporters/line"
47
+ require "git/lint/reporters/style"
48
+ require "git/lint/reporters/commit"
49
+ require "git/lint/reporters/branch"
50
+ require "git/lint/runner"
51
+ require "git/lint/cli"
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/strings"
4
+
5
+ module Git
6
+ module Lint
7
+ module Analyzers
8
+ # An abstract class which provides basic functionality from which all analyzers inherit from.
9
+ class Abstract
10
+ using ::Refinements::Strings
11
+
12
+ LEVELS = %i[warn error].freeze
13
+ ISSUE_LINE_OFFSET = 2
14
+
15
+ def self.inherited klass
16
+ @descendants ||= []
17
+ @descendants << klass unless klass.to_s.start_with? "#<Class" # Ignore anonymous classes.
18
+ end
19
+
20
+ def self.id
21
+ to_s.delete_prefix("Git::Lint::Analyzers").snakecase.to_sym
22
+ end
23
+
24
+ def self.label
25
+ to_s.delete_prefix("Git::Lint::Analyzers").titleize
26
+ end
27
+
28
+ def self.defaults
29
+ fail NotImplementedError, "The `.#{__method__}` method must be implemented."
30
+ end
31
+
32
+ def self.descendants
33
+ @descendants || []
34
+ end
35
+
36
+ def self.build_issue_line index, line
37
+ {number: index + ISSUE_LINE_OFFSET, content: line}
38
+ end
39
+
40
+ attr_reader :commit
41
+
42
+ def initialize commit:, settings: self.class.defaults
43
+ @commit = commit
44
+ @settings = settings
45
+ @filter_list = load_filter_list
46
+ end
47
+
48
+ def enabled?
49
+ settings.fetch :enabled
50
+ end
51
+
52
+ def severity
53
+ settings.fetch(:severity).tap do |level|
54
+ fail Errors::Severity, level unless LEVELS.include? level
55
+ end
56
+ end
57
+
58
+ def valid?
59
+ fail NotImplementedError, "The `##{__method__}` method must be implemented."
60
+ end
61
+
62
+ def invalid?
63
+ !valid?
64
+ end
65
+
66
+ def warning?
67
+ invalid? && severity == :warn
68
+ end
69
+
70
+ def error?
71
+ invalid? && severity == :error
72
+ end
73
+
74
+ def issue
75
+ fail NotImplementedError, "The `##{__method__}` method must be implemented."
76
+ end
77
+
78
+ protected
79
+
80
+ attr_reader :settings, :filter_list
81
+
82
+ def load_filter_list
83
+ Kit::FilterList.new settings[:list]
84
+ end
85
+
86
+ def affected_commit_body_lines
87
+ commit.body_lines.each.with_object([]).with_index do |(line, lines), index|
88
+ yield if block_given?
89
+ lines << self.class.build_issue_line(index, line) if invalid_line? line
90
+ end
91
+ end
92
+
93
+ def affected_commit_trailer_lines
94
+ commit.trailer_lines
95
+ .each.with_object([])
96
+ .with_index(commit.trailer_index) do |(line, lines), index|
97
+ yield if block_given?
98
+ lines << self.class.build_issue_line(index, line) if invalid_line? line
99
+ end
100
+ end
101
+
102
+ def invalid_line? _line
103
+ fail NotImplementedError, "The `.#{__method__}` method must be implemented."
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Analyzers
6
+ class CommitAuthorCapitalization < Abstract
7
+ def self.defaults
8
+ {
9
+ enabled: true,
10
+ severity: :error
11
+ }
12
+ end
13
+
14
+ def initialize commit:, settings: self.class.defaults, validator: Validators::Capitalization
15
+ super commit: commit, settings: settings
16
+ @validator = validator
17
+ end
18
+
19
+ def valid?
20
+ validator.new(commit.author_name).valid?
21
+ end
22
+
23
+ def issue
24
+ return {} if valid?
25
+
26
+ {hint: %(Capitalize each part of name: "#{commit.author_name}".)}
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :validator
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Analyzers
6
+ class CommitAuthorEmail < Abstract
7
+ def self.defaults
8
+ {
9
+ enabled: true,
10
+ severity: :error
11
+ }
12
+ end
13
+
14
+ def initialize commit:, settings: self.class.defaults, validator: Validators::Email
15
+ super commit: commit, settings: settings
16
+ @validator = validator
17
+ end
18
+
19
+ def valid?
20
+ validator.new(commit.author_email).valid?
21
+ end
22
+
23
+ def issue
24
+ return {} if valid?
25
+
26
+ {hint: %(Use "<name>@<server>.<domain>" instead of "#{commit.author_email}".)}
27
+ end
28
+
29
+ private
30
+
31
+ attr_reader :validator
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Analyzers
6
+ class CommitAuthorName < Abstract
7
+ def self.defaults
8
+ {
9
+ enabled: true,
10
+ severity: :error,
11
+ minimum: 2
12
+ }
13
+ end
14
+
15
+ def initialize commit:, settings: self.class.defaults, validator: Validators::Name
16
+ super commit: commit, settings: settings
17
+ @validator = validator
18
+ end
19
+
20
+ def valid?
21
+ validator.new(commit.author_name, minimum: minimum).valid?
22
+ end
23
+
24
+ def issue
25
+ return {} if valid?
26
+
27
+ {hint: "Author name must consist of #{minimum} parts (minimum)."}
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :validator
33
+
34
+ def minimum
35
+ settings.fetch :minimum
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Analyzers
6
+ class CommitBodyBullet < Abstract
7
+ def self.defaults
8
+ {
9
+ enabled: true,
10
+ severity: :error,
11
+ excludes: %w[\\* •]
12
+ }
13
+ end
14
+
15
+ def valid?
16
+ commit.body_lines.all? { |line| !invalid_line? line }
17
+ end
18
+
19
+ def issue
20
+ return {} if valid?
21
+
22
+ {
23
+ hint: %(Avoid: #{filter_list.to_hint}.),
24
+ lines: affected_commit_body_lines
25
+ }
26
+ end
27
+
28
+ protected
29
+
30
+ def load_filter_list
31
+ Kit::FilterList.new settings.fetch :excludes
32
+ end
33
+
34
+ # :reek:FeatureEnvy
35
+ def invalid_line? line
36
+ return false if line.strip.empty?
37
+
38
+ !line.match?(/\A(?!\s*#{Regexp.union filter_list.to_regexp}\s+).+\Z/)
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Analyzers
6
+ class CommitBodyBulletCapitalization < Abstract
7
+ def self.defaults
8
+ {
9
+ enabled: true,
10
+ severity: :error,
11
+ includes: %w[\\-]
12
+ }
13
+ end
14
+
15
+ def valid?
16
+ lowercased_bullets.size.zero?
17
+ end
18
+
19
+ def issue
20
+ return {} if valid?
21
+
22
+ {
23
+ hint: "Capitalize first word.",
24
+ lines: affected_commit_body_lines
25
+ }
26
+ end
27
+
28
+ protected
29
+
30
+ def load_filter_list
31
+ Kit::FilterList.new settings.fetch :includes
32
+ end
33
+
34
+ def invalid_line? line
35
+ line.match?(/\A\s*#{Regexp.union filter_list.to_regexp}\s[[:lower:]]+/)
36
+ end
37
+
38
+ private
39
+
40
+ def lowercased_bullets
41
+ commit.body_lines.select { |line| invalid_line? line }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Analyzers
6
+ class CommitBodyBulletDelimiter < Abstract
7
+ def self.defaults
8
+ {
9
+ enabled: true,
10
+ severity: :error,
11
+ includes: %w[\\-]
12
+ }
13
+ end
14
+
15
+ def valid?
16
+ commit.body_lines.none? { |line| invalid_line? line }
17
+ end
18
+
19
+ def issue
20
+ return {} if valid?
21
+
22
+ {
23
+ hint: "Use space after bullet.",
24
+ lines: affected_commit_body_lines
25
+ }
26
+ end
27
+
28
+ protected
29
+
30
+ def load_filter_list
31
+ Kit::FilterList.new settings.fetch :includes
32
+ end
33
+
34
+ def invalid_line? line
35
+ line.match?(/\A\s*#{Regexp.union filter_list.to_regexp}(?!\s).+\Z/)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end