git-lint 2.3.3 → 3.0.2

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 (75) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/LICENSE.adoc +207 -155
  4. data/README.adoc +203 -222
  5. data/{bin → exe}/git-lint +1 -3
  6. data/lib/git/lint/analyzer.rb +76 -0
  7. data/lib/git/lint/analyzers/abstract.rb +8 -18
  8. data/lib/git/lint/analyzers/commit_author_capitalization.rb +3 -9
  9. data/lib/git/lint/analyzers/commit_author_email.rb +3 -9
  10. data/lib/git/lint/analyzers/commit_author_name.rb +5 -14
  11. data/lib/git/lint/analyzers/commit_body_bullet.rb +2 -9
  12. data/lib/git/lint/analyzers/commit_body_bullet_capitalization.rb +2 -9
  13. data/lib/git/lint/analyzers/commit_body_bullet_delimiter.rb +2 -9
  14. data/lib/git/lint/analyzers/commit_body_leading_line.rb +1 -7
  15. data/lib/git/lint/analyzers/commit_body_line_length.rb +4 -11
  16. data/lib/git/lint/analyzers/commit_body_paragraph_capitalization.rb +1 -7
  17. data/lib/git/lint/analyzers/commit_body_phrase.rb +2 -38
  18. data/lib/git/lint/analyzers/commit_body_presence.rb +5 -12
  19. data/lib/git/lint/analyzers/commit_body_single_bullet.rb +2 -9
  20. data/lib/git/lint/analyzers/commit_body_tracker_shorthand.rb +27 -0
  21. data/lib/git/lint/analyzers/commit_subject_length.rb +4 -11
  22. data/lib/git/lint/analyzers/commit_subject_prefix.rb +2 -9
  23. data/lib/git/lint/analyzers/commit_subject_suffix.rb +2 -13
  24. data/lib/git/lint/analyzers/commit_trailer_collaborator_capitalization.rb +3 -12
  25. data/lib/git/lint/analyzers/commit_trailer_collaborator_duplication.rb +3 -11
  26. data/lib/git/lint/analyzers/commit_trailer_collaborator_email.rb +3 -12
  27. data/lib/git/lint/analyzers/commit_trailer_collaborator_key.rb +4 -13
  28. data/lib/git/lint/analyzers/commit_trailer_collaborator_name.rb +5 -15
  29. data/lib/git/lint/cli/actions/analyze/branch.rb +43 -0
  30. data/lib/git/lint/cli/actions/analyze/commit.rb +44 -0
  31. data/lib/git/lint/cli/actions/config.rb +37 -0
  32. data/lib/git/lint/cli/actions/hook.rb +34 -0
  33. data/lib/git/lint/cli/parser.rb +33 -0
  34. data/lib/git/lint/cli/parsers/analyze.rb +40 -0
  35. data/lib/git/lint/cli/parsers/core.rb +72 -0
  36. data/lib/git/lint/cli/shell.rb +56 -0
  37. data/lib/git/lint/collector.rb +3 -4
  38. data/lib/git/lint/commits/container.rb +19 -0
  39. data/lib/git/lint/commits/loader.rb +51 -0
  40. data/lib/git/lint/commits/systems/circle_ci.rb +26 -0
  41. data/lib/git/lint/{branches/environments → commits/systems}/git_hub_action.rb +10 -8
  42. data/lib/git/lint/commits/systems/local.rb +26 -0
  43. data/lib/git/lint/{branches/environments → commits/systems}/netlify_ci.rb +14 -10
  44. data/lib/git/lint/configuration/content.rb +26 -0
  45. data/lib/git/lint/configuration/defaults.yml +118 -0
  46. data/lib/git/lint/configuration/loader.rb +50 -0
  47. data/lib/git/lint/configuration/setting.rb +24 -0
  48. data/lib/git/lint/container.rb +41 -0
  49. data/lib/git/lint/errors/severity.rb +1 -0
  50. data/lib/git/lint/errors/sha.rb +1 -0
  51. data/lib/git/lint/identity.rb +2 -1
  52. data/lib/git/lint/kit/filter_list.rb +1 -1
  53. data/lib/git/lint/parsers/trailers/collaborator.rb +1 -0
  54. data/lib/git/lint/rake/tasks.rb +5 -4
  55. data/lib/git/lint/reporters/branch.rb +12 -6
  56. data/lib/git/lint/reporters/commit.rb +3 -1
  57. data/lib/git/lint/reporters/line.rb +3 -5
  58. data/lib/git/lint/reporters/lines/paragraph.rb +3 -0
  59. data/lib/git/lint/reporters/lines/sentence.rb +3 -0
  60. data/lib/git/lint/reporters/style.rb +3 -1
  61. data/lib/git/lint/validators/capitalization.rb +1 -0
  62. data/lib/git/lint/validators/email.rb +1 -0
  63. data/lib/git/lint/validators/name.rb +1 -0
  64. data/lib/git/lint.rb +18 -47
  65. data.tar.gz.sig +0 -0
  66. metadata +55 -29
  67. metadata.gz.sig +0 -0
  68. data/lib/git/lint/analyzers/commit_body_issue_tracker_link.rb +0 -39
  69. data/lib/git/lint/branches/environments/circle_ci.rb +0 -24
  70. data/lib/git/lint/branches/environments/local.rb +0 -24
  71. data/lib/git/lint/branches/environments/travis_ci.rb +0 -49
  72. data/lib/git/lint/branches/feature.rb +0 -42
  73. data/lib/git/lint/cli.rb +0 -117
  74. data/lib/git/lint/refinements/strings.rb +0 -21
  75. data/lib/git/lint/runner.rb +0 -35
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module CLI
6
+ module Actions
7
+ module Analyze
8
+ # Handles analyze action for branch.
9
+ class Branch
10
+ def initialize analyzer: Analyzer.new, container: Container
11
+ @analyzer = analyzer
12
+ @container = container
13
+ end
14
+
15
+ def call
16
+ parse
17
+ rescue Errors::Base => error
18
+ logger.error { error.message }
19
+ kernel.abort
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :analyzer, :container
25
+
26
+ def parse
27
+ analyzer.call do |collector, reporter|
28
+ kernel.puts reporter
29
+ kernel.abort if collector.errors?
30
+ end
31
+ end
32
+
33
+ def repository = container[__method__]
34
+
35
+ def kernel = container[__method__]
36
+
37
+ def logger = container[__method__]
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module CLI
6
+ module Actions
7
+ module Analyze
8
+ # Handles analyze action for commit(s) by SHA.
9
+ class Commit
10
+ def initialize analyzer: Analyzer.new,
11
+ parser: GitPlus::Parsers::Commits::Saved::History.with_show,
12
+ container: Container
13
+ @analyzer = analyzer
14
+ @parser = parser
15
+ @container = container
16
+ end
17
+
18
+ def call sha = nil
19
+ process sha
20
+ rescue Errors::Base => error
21
+ logger.error { error.message }
22
+ kernel.abort
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :analyzer, :parser, :container
28
+
29
+ def process sha
30
+ analyzer.call commits: parser.call(*sha) do |collector, reporter|
31
+ kernel.puts reporter
32
+ kernel.abort if collector.errors?
33
+ end
34
+ end
35
+
36
+ def kernel = container[__method__]
37
+
38
+ def logger = container[__method__]
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module CLI
6
+ module Actions
7
+ # Handles gem configuration action.
8
+ class Config
9
+ def initialize configuration: Configuration::Loader::CLIENT, container: Container
10
+ @configuration = configuration
11
+ @container = container
12
+ end
13
+
14
+ def call action
15
+ case action
16
+ when :edit then edit
17
+ when :view then view
18
+ else logger.error { "Invalid configuration action: #{action}." }
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ attr_reader :configuration, :container
25
+
26
+ def edit = kernel.system("$EDITOR #{configuration.current}")
27
+
28
+ def view = kernel.system("cat #{configuration.current}")
29
+
30
+ def kernel = container[__method__]
31
+
32
+ def logger = container[__method__]
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module CLI
6
+ module Actions
7
+ # Handles unsaved Git commit action.
8
+ class Hook
9
+ def initialize analyzer: Analyzer.new, container: Container
10
+ @analyzer = analyzer
11
+ @container = container
12
+ end
13
+
14
+ def call path
15
+ analyzer.call commits: [repository.unsaved(path)] do |collector, reporter|
16
+ kernel.puts reporter
17
+ kernel.abort if collector.errors?
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :analyzer, :container
24
+
25
+ def repository = container[__method__]
26
+
27
+ def kernel = container[__method__]
28
+
29
+ def logger = container[__method__]
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module Git
6
+ module Lint
7
+ module CLI
8
+ # Assembles and parses all Command Line Interface (CLI) options.
9
+ class Parser
10
+ CLIENT = OptionParser.new nil, 40, " "
11
+ SECTIONS = [Parsers::Core, Parsers::Analyze].freeze # Order matters.
12
+
13
+ def initialize sections: SECTIONS, client: CLIENT, container: Container
14
+ @sections = sections
15
+ @client = client
16
+ @configuration = container[:configuration].dup
17
+ end
18
+
19
+ def call arguments = []
20
+ sections.each { |section| section.call configuration, client: }
21
+ client.parse arguments
22
+ configuration.freeze
23
+ end
24
+
25
+ def to_s = client.to_s
26
+
27
+ private
28
+
29
+ attr_reader :sections, :client, :configuration
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/structs"
4
+
5
+ module Git
6
+ module Lint
7
+ module CLI
8
+ module Parsers
9
+ # Handles parsing of Command Line Interface (CLI) core options.
10
+ class Analyze
11
+ using ::Refinements::Structs
12
+
13
+ def self.call(...) = new(...).call
14
+
15
+ def initialize configuration = Container[:configuration], client: Parser::CLIENT
16
+ @configuration = configuration
17
+ @client = client
18
+ end
19
+
20
+ def call arguments = []
21
+ client.separator "\nANALYZE OPTIONS:\n"
22
+ add_sha
23
+ client.parse arguments
24
+ configuration
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :configuration, :client
30
+
31
+ def add_sha
32
+ client.on "--sha HASH", "Analyze specific commit SHA." do |sha|
33
+ configuration.merge! analyze_sha: sha
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/structs"
4
+
5
+ module Git
6
+ module Lint
7
+ module CLI
8
+ module Parsers
9
+ # Handles parsing of Command Line Interface (CLI) core options.
10
+ class Core
11
+ using ::Refinements::Structs
12
+
13
+ def self.call(...) = new(...).call
14
+
15
+ def initialize configuration = Container[:configuration], client: Parser::CLIENT
16
+ @configuration = configuration
17
+ @client = client
18
+ end
19
+
20
+ def call arguments = []
21
+ client.banner = "#{Identity::LABEL} - #{Identity::SUMMARY}"
22
+ client.separator "\nUSAGE:\n"
23
+ collate
24
+ client.parse arguments
25
+ configuration
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :configuration, :client
31
+
32
+ def collate = private_methods.sort.grep(/add_/).each { |method| __send__ method }
33
+
34
+ def add_analyze
35
+ client.on "-a", "--analyze [options]", "Analyze current branch commits." do
36
+ configuration.merge! action_analyze: true
37
+ end
38
+ end
39
+
40
+ def add_config
41
+ client.on(
42
+ "-c",
43
+ "--config ACTION",
44
+ %i[edit view],
45
+ "Manage gem configuration. Actions: edit or view."
46
+ ) do |action|
47
+ configuration.merge! action_config: action
48
+ end
49
+ end
50
+
51
+ def add_hook
52
+ client.on "--hook PATH", "Hook for analyzing unsaved commits." do |path|
53
+ configuration.merge! action_hook: Pathname(path)
54
+ end
55
+ end
56
+
57
+ def add_version
58
+ client.on "-v", "--version", "Show gem version." do
59
+ configuration.merge! action_version: true
60
+ end
61
+ end
62
+
63
+ def add_help
64
+ client.on "-h", "--help", "Show this message." do
65
+ configuration.merge! action_help: true
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module CLI
6
+ # The main Command Line Interface (CLI) object.
7
+ class Shell
8
+ ACTIONS = {
9
+ analyze_branch: Actions::Analyze::Branch.new,
10
+ analyze_commit: Actions::Analyze::Commit.new,
11
+ config: Actions::Config.new,
12
+ hook: Actions::Hook.new
13
+ }.freeze
14
+
15
+ def initialize parser: Parser.new, actions: ACTIONS, container: Container
16
+ @parser = parser
17
+ @actions = actions
18
+ @container = container
19
+ end
20
+
21
+ def call arguments = []
22
+ perform parser.call(arguments)
23
+ rescue OptionParser::ParseError, Errors::Base => error
24
+ logger.error { error.message }
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :parser, :actions, :container
30
+
31
+ def perform configuration
32
+ case configuration
33
+ in action_analyze: true, analyze_sha: nil then analyze_branch
34
+ in action_analyze: true, analyze_sha: String => sha then analyze_commit sha
35
+ in action_config: Symbol => action then config action
36
+ in action_hook: Pathname => path then hook path
37
+ in action_version: true then logger.info Identity::VERSION_LABEL
38
+ else usage
39
+ end
40
+ end
41
+
42
+ def analyze_branch = actions.fetch(__method__).call
43
+
44
+ def analyze_commit(sha) = actions.fetch(__method__).call(sha)
45
+
46
+ def config(action) = actions.fetch(__method__).call(action)
47
+
48
+ def hook(path) = actions.fetch(__method__).call(path)
49
+
50
+ def usage = logger.unknown { parser.to_s }
51
+
52
+ def logger = container[__method__]
53
+ end
54
+ end
55
+ end
56
+ end
@@ -1,12 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "refinements/hashes"
4
-
5
3
  module Git
6
4
  module Lint
5
+ # Collects and categorizes, by severity, all issues (if any).
7
6
  class Collector
8
- using ::Refinements::Hashes
9
-
10
7
  def initialize
11
8
  @collection = Hash.new { |default, missing_id| default[missing_id] = [] }
12
9
  end
@@ -18,6 +15,8 @@ module Git
18
15
 
19
16
  def retrieve(id) = collection[id]
20
17
 
18
+ def clear = collection.clear && self
19
+
21
20
  def empty? = collection.empty?
22
21
 
23
22
  def warnings? = collection.values.flatten.any?(&:warning?)
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry-container"
4
+ require "git_plus"
5
+
6
+ module Git
7
+ module Lint
8
+ module Commits
9
+ # Provides container specific to this namespace for all systems.
10
+ module Container
11
+ extend Dry::Container::Mixin
12
+
13
+ register(:repository) { GitPlus::Repository.new }
14
+ register(:shell) { Open3 }
15
+ register(:environment) { ENV }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/strings"
4
+
5
+ module Git
6
+ module Lint
7
+ module Commits
8
+ # Automatically detects and loads system.
9
+ class Loader
10
+ using ::Refinements::Strings
11
+
12
+ SYSTEMS = {
13
+ circle_ci: Systems::CircleCI.new,
14
+ git_hub_action: Systems::GitHubAction.new,
15
+ netlify_ci: Systems::NetlifyCI.new,
16
+ local: Systems::Local.new
17
+ }.freeze
18
+
19
+ def initialize systems: SYSTEMS, container: Container
20
+ @systems = systems
21
+ @container = container
22
+ end
23
+
24
+ def call
25
+ message = "Invalid repository. Are you within a Git repository?"
26
+ fail Errors::Base, message unless repository.exist?
27
+
28
+ load_system.call
29
+ end
30
+
31
+ private
32
+
33
+ attr_reader :systems, :container
34
+
35
+ def load_system
36
+ if key? "CIRCLECI" then systems.fetch :circle_ci
37
+ elsif key? "GITHUB_ACTIONS" then systems.fetch :git_hub_action
38
+ elsif key? "NETLIFY" then systems.fetch :netlify_ci
39
+ else systems.fetch :local
40
+ end
41
+ end
42
+
43
+ def key?(key) = environment.fetch(key, "false").to_bool
44
+
45
+ def repository = container[__method__]
46
+
47
+ def environment = container[__method__]
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Commits
6
+ module Systems
7
+ # Provides Circle CI build environment feature branch information.
8
+ class CircleCI
9
+ def initialize container: Container
10
+ @container = container
11
+ end
12
+
13
+ def call = repository.commits("origin/#{repository.branch_default}..#{name}")
14
+
15
+ private
16
+
17
+ attr_reader :container
18
+
19
+ def name = "origin/#{repository.branch_name}"
20
+
21
+ def repository = container[__method__]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,21 +2,23 @@
2
2
 
3
3
  module Git
4
4
  module Lint
5
- module Branches
6
- module Environments
5
+ module Commits
6
+ module Systems
7
7
  # Provides GitHub Action build environment feature branch information.
8
8
  class GitHubAction
9
- def initialize repository: GitPlus::Repository.new
10
- @repository = repository
9
+ def initialize container: Container
10
+ @container = container
11
11
  end
12
12
 
13
- def name = "origin/#{repository.branch_name}"
14
-
15
- def commits = repository.commits("origin/#{repository.branch_default}..#{name}")
13
+ def call = repository.commits("origin/#{repository.branch_default}..#{name}")
16
14
 
17
15
  private
18
16
 
19
- attr_reader :repository
17
+ attr_reader :container
18
+
19
+ def name = "origin/#{repository.branch_name}"
20
+
21
+ def repository = container[__method__]
20
22
  end
21
23
  end
22
24
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Commits
6
+ module Systems
7
+ # Provides local build environment feature branch information.
8
+ class Local
9
+ def initialize container: Container
10
+ @container = container
11
+ end
12
+
13
+ def call = repository.commits("#{repository.branch_default}..#{name}")
14
+
15
+ private
16
+
17
+ attr_reader :container
18
+
19
+ def name = repository.branch_name
20
+
21
+ def repository = container[__method__]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -4,19 +4,15 @@ require "open3"
4
4
 
5
5
  module Git
6
6
  module Lint
7
- module Branches
8
- module Environments
7
+ module Commits
8
+ module Systems
9
9
  # Provides Netlify CI build environment feature branch information.
10
10
  class NetlifyCI
11
- def initialize repository: GitPlus::Repository.new, shell: Open3, environment: ENV
12
- @repository = repository
13
- @shell = shell
14
- @environment = environment
11
+ def initialize container: Container
12
+ @container = container
15
13
  end
16
14
 
17
- def name = environment["HEAD"]
18
-
19
- def commits
15
+ def call
20
16
  shell.capture3 "git remote add -f origin #{environment["REPOSITORY_URL"]}"
21
17
  shell.capture3 "git fetch origin #{name}:#{name}"
22
18
  repository.commits "origin/#{repository.branch_default}..origin/#{name}"
@@ -24,7 +20,15 @@ module Git
24
20
 
25
21
  private
26
22
 
27
- attr_reader :repository, :shell, :environment
23
+ attr_reader :container
24
+
25
+ def name = environment["HEAD"]
26
+
27
+ def repository = container[__method__]
28
+
29
+ def shell = container[__method__]
30
+
31
+ def environment = container[__method__]
28
32
  end
29
33
  end
30
34
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Git
4
+ module Lint
5
+ module Configuration
6
+ # Defines configuration content as the primary source of truth for use throughout the gem.
7
+ Content = Struct.new(
8
+ :action_analyze,
9
+ :action_config,
10
+ :action_help,
11
+ :action_hook,
12
+ :action_version,
13
+ :analyze_sha,
14
+ :analyzers,
15
+ keyword_init: true
16
+ ) do
17
+ def initialize *arguments
18
+ super
19
+ freeze
20
+ end
21
+
22
+ def find_setting(id) = analyzers.find { |setting| setting.id == id }
23
+ end
24
+ end
25
+ end
26
+ end