git-lint 1.0.0

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.
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