git_toolbox 0.7.1 → 0.8.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.
- checksums.yaml +4 -4
- data/bin/get +1 -1
- data/lib/get/commons/command_issuer.rb +60 -0
- data/lib/get/commons/common.rb +70 -0
- data/lib/get/commons/git.rb +31 -14
- data/lib/get/commons/http_client.rb +79 -0
- data/lib/get/subcommand/changelog/changelog.rb +56 -61
- data/lib/get/subcommand/command.rb +58 -6
- data/lib/get/subcommand/commit/commit.rb +58 -61
- data/lib/get/subcommand/commit/prompt.rb +25 -23
- data/lib/get/subcommand/complete/bash_completion.rb +7 -6
- data/lib/get/subcommand/complete/complete.rb +31 -36
- data/lib/get/subcommand/describe/change.rb +25 -27
- data/lib/get/subcommand/describe/describe.rb +113 -115
- data/lib/get/subcommand/describe/docker/docker.rb +55 -58
- data/lib/get/subcommand/describe/metadata.rb +16 -19
- data/lib/get/subcommand/describe/prerelease.rb +12 -13
- data/lib/get/subcommand/init/init.rb +43 -44
- data/lib/get/subcommand/license/license.rb +78 -56
- data/lib/get/subcommand/license/license_retriever.rb +38 -23
- data/lib/get/subcommand/tree/tree.rb +48 -42
- data/lib/get/version.rb +1 -3
- data/lib/get.rb +47 -34
- metadata +16 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 500f22d2000495865a5671476122c580ea0e5fdb6ebf2804ce42d209828a1841
|
4
|
+
data.tar.gz: 70a3c312974ce5ee382a8c6793325bf676dba7fb40206ed828f56c2019ce9d64
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce3a643b6a110c7b70e7ddc33649548b15f064e61dbe35c13fa5eba3fa70b7c71a5118185ee88b89aab83f74602b605d9175b4633e58c933ae30ebfae97c2dab
|
7
|
+
data.tar.gz: 5125a3a0af8ab4a6590af9166c95d442c1dae5f89648f1d94224ff5a4d3e18fcaf21ec5e9d52b1f50939340d17d60ddd5cd37fa7bd6bd16b4e7ce88f2ac868da
|
data/bin/get
CHANGED
@@ -0,0 +1,60 @@
|
|
1
|
+
# Get is a toolbox based on git which simplifies the adoption of conventions and some git commands.
|
2
|
+
# Copyright (C) 2023 Alex Speranza
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program and the additional permissions granted by
|
16
|
+
# the Lesser GPL. If not, see <https://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'open3'
|
21
|
+
|
22
|
+
# Module for simplify command execution.
|
23
|
+
module CommandIssuer
|
24
|
+
# A class containing the result of a command, including the exit status and the command executed.
|
25
|
+
class CommandResult
|
26
|
+
attr_reader :command, :exit_status, :output, :error
|
27
|
+
|
28
|
+
def initialize(command_string, exit_status, standard_output, standard_error)
|
29
|
+
@command = command_string
|
30
|
+
@exit_status = exit_status
|
31
|
+
@output = standard_output
|
32
|
+
@error = standard_error
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.run(executable, *args)
|
37
|
+
full_path_executable = CommandIssuer.send(:find, executable)
|
38
|
+
command = [full_path_executable, *args].join(' ').strip
|
39
|
+
output, error, status = Open3.capture3(command)
|
40
|
+
CommandResult.new(command, status.exitstatus, output, error)
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
private
|
45
|
+
|
46
|
+
# Checks if the given executable exists. If it does not exists,
|
47
|
+
# an error message will be displayed and the program will exit.
|
48
|
+
# Based on https://stackoverflow.com/a/5471032
|
49
|
+
def find(executable)
|
50
|
+
exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
|
51
|
+
ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
|
52
|
+
exts.each do |ext|
|
53
|
+
exe = File.join(path, "#{executable}#{ext}")
|
54
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
Common.error("'#{executable}' was not found in PATH.")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
data/lib/get/commons/common.rb
CHANGED
@@ -51,4 +51,74 @@ module Common
|
|
51
51
|
action.call if action.respond_to?('call')
|
52
52
|
exit(exit_code)
|
53
53
|
end
|
54
|
+
|
55
|
+
# Add an instance attribute (with a default value) to a module.
|
56
|
+
# It is intended to be called in the body of a module definition:
|
57
|
+
# module MyModule
|
58
|
+
# DEFAULT_VALUE = 1
|
59
|
+
# Common.module_instance_attr(self, my_variable, DEFAULT_VALUE)
|
60
|
+
# end
|
61
|
+
# produces the code:
|
62
|
+
# module MyModule
|
63
|
+
# instance_variable_set(:@my_variable, 1)
|
64
|
+
# def self.my_variable
|
65
|
+
# instance_variable_get(:@my_variable)
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# def self.my_variable=(value)
|
69
|
+
# instance_variable_set(:@my_variable, value)
|
70
|
+
# end
|
71
|
+
# end
|
72
|
+
def self.module_instance_attr(mod, name, default_value = nil)
|
73
|
+
mod.module_eval(<<~CODE, __FILE__, __LINE__ + 1)
|
74
|
+
# module MyModule
|
75
|
+
# instance_variable_set(:@my_variable, 1)
|
76
|
+
# def self.my_variable
|
77
|
+
# instance_variable_get(:@my_variable)
|
78
|
+
# end
|
79
|
+
#
|
80
|
+
# def self.my_variable=(value)
|
81
|
+
# instance_variable_set(:@my_variable, value)
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
|
85
|
+
instance_variable_set(:@#{name}, #{default_value})
|
86
|
+
|
87
|
+
def self.#{name}
|
88
|
+
instance_variable_get(:@#{name})
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.#{name}=(value)
|
92
|
+
instance_variable_set(:@#{name}, value)
|
93
|
+
end
|
94
|
+
CODE
|
95
|
+
end
|
96
|
+
|
97
|
+
def self.module_instance_value(mod, name, value)
|
98
|
+
mod.module_eval(<<~CODE, __FILE__, __LINE__ + 1)
|
99
|
+
# module MyModule
|
100
|
+
# instance_variable_set(:@my_variable, 1)
|
101
|
+
# def self.my_variable
|
102
|
+
# instance_variable_get(:@my_variable)
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
|
106
|
+
instance_variable_set(:@#{name}, #{value})
|
107
|
+
|
108
|
+
def self.#{name}
|
109
|
+
instance_variable_get(:@#{name})
|
110
|
+
end
|
111
|
+
CODE
|
112
|
+
end
|
113
|
+
|
114
|
+
# Add a new 'MOD_REF' constant with the module symbol for a shorter variable name.
|
115
|
+
def self.add_module_self_reference(mod)
|
116
|
+
mod.module_eval(<<~CODE, __FILE__, __LINE__ + 1)
|
117
|
+
# module MyModule
|
118
|
+
# MOD_REF = MyModule
|
119
|
+
# end
|
120
|
+
|
121
|
+
MOD_REF = #{mod.name}
|
122
|
+
CODE
|
123
|
+
end
|
54
124
|
end
|
data/lib/get/commons/git.rb
CHANGED
@@ -17,23 +17,17 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
-
|
21
|
-
require 'get/commons/common'
|
20
|
+
require_relative './command_issuer'
|
22
21
|
|
23
22
|
# Utility module
|
24
23
|
module Git
|
25
24
|
# Groups: 1 = type, 2 = scope with (), 3 = scope, 4 = breaking change, 5 = summary
|
26
|
-
CONVENTIONAL_COMMIT_REGEX =
|
25
|
+
CONVENTIONAL_COMMIT_REGEX = %r{^(\w+)(\(([\w/-]+)\))?(!)?:(.*)}
|
27
26
|
|
28
27
|
# Check if the command is called while in a git repository.
|
29
28
|
# If the command fails, it is assumed to not be in a git repository.
|
30
29
|
def self.in_repo?
|
31
|
-
|
32
|
-
case $CHILD_STATUS.exitstatus
|
33
|
-
when 0 then true
|
34
|
-
when 127 then Common.error '"git" is not installed.'
|
35
|
-
else false
|
36
|
-
end
|
30
|
+
CommandIssuer.run('git', 'rev-parse', '--is-inside-work-tree').exit_status.zero?
|
37
31
|
end
|
38
32
|
|
39
33
|
# Run a block of code with the list of commits from the given version as an argument.
|
@@ -41,19 +35,42 @@ module Git
|
|
41
35
|
def self.with_commit_list_from(version = nil, &block)
|
42
36
|
return unless block_given?
|
43
37
|
|
44
|
-
|
45
|
-
|
46
|
-
|
38
|
+
command_result = CommandIssuer.run(
|
39
|
+
'git',
|
40
|
+
'--no-pager',
|
41
|
+
'log',
|
42
|
+
'--oneline',
|
43
|
+
'--pretty=format:%s',
|
44
|
+
version.nil? ? '' : "^#{version} HEAD"
|
45
|
+
)
|
46
|
+
commits_from_version = if command_result.exit_status.zero?
|
47
|
+
command_result.output.split("\n")
|
48
|
+
else
|
49
|
+
[]
|
50
|
+
end
|
47
51
|
block.call(commits_from_version)
|
48
52
|
end
|
49
53
|
|
50
54
|
# Returns the last version and caches it for the next calls.
|
51
55
|
def self.last_version
|
52
|
-
|
56
|
+
@last_version ||=
|
57
|
+
CommandIssuer.run('git', 'describe', '--tags', '--abbrev=0')
|
58
|
+
.then { |result| result.output.strip if result.exit_status.zero? }
|
53
59
|
end
|
54
60
|
|
55
61
|
# Returns the last release and caches it for the next calls.
|
56
62
|
def self.last_release
|
57
|
-
|
63
|
+
@last_release ||=
|
64
|
+
CommandIssuer.run('git', '--no-pager', 'tag', '--list')
|
65
|
+
.then do |value|
|
66
|
+
unless value.output.empty?
|
67
|
+
value.output
|
68
|
+
.split("\n")
|
69
|
+
.map { |str| str.sub('+', '_') }
|
70
|
+
.sort
|
71
|
+
.map { |str| str.sub('_', '+') }
|
72
|
+
.last
|
73
|
+
end
|
74
|
+
end
|
58
75
|
end
|
59
76
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# Get is a toolbox based on git which simplifies the adoption of conventions and some git commands.
|
2
|
+
# Copyright (C) 2023 Alex Speranza
|
3
|
+
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
5
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
6
|
+
# the Free Software Foundation, either version 3 of the License, or
|
7
|
+
# (at your option) any later version.
|
8
|
+
|
9
|
+
# This program is distributed in the hope that it will be useful,
|
10
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
11
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
12
|
+
# GNU General Public License for more details.
|
13
|
+
|
14
|
+
# You should have received a copy of the GNU General Public License
|
15
|
+
# along with this program and the additional permissions granted by
|
16
|
+
# the Lesser GPL. If not, see <https://www.gnu.org/licenses/>.
|
17
|
+
|
18
|
+
# frozen_string_literal: true
|
19
|
+
|
20
|
+
require 'net/http'
|
21
|
+
require 'uri'
|
22
|
+
|
23
|
+
# Client HTTP which allows to perform GET request and follow redirections.
|
24
|
+
class HTTPClient
|
25
|
+
include Singleton
|
26
|
+
|
27
|
+
# Number of attempts to try when following a redirection link.
|
28
|
+
MAX_ATTEMPTS = 10
|
29
|
+
|
30
|
+
# Perform a get request to an address, following the redirections at most MAX_ATTEMPTS times.
|
31
|
+
def http_get_request(address)
|
32
|
+
uri = URI.parse(address)
|
33
|
+
|
34
|
+
# Code based on https://shadow-file.blogspot.com/2009/03/handling-http-redirection-in-ruby.html
|
35
|
+
attempts = 0
|
36
|
+
until attempts >= MAX_ATTEMPTS
|
37
|
+
attempts += 1
|
38
|
+
|
39
|
+
resp = build_http(uri)
|
40
|
+
.request(Net::HTTP::Get.new(uri.path == '' ? '/' : uri.path))
|
41
|
+
return resp if resp.is_a?(Net::HTTPSuccess) || resp.header['location'].nil?
|
42
|
+
|
43
|
+
uri = updated_uri(uri, resp.header['location'])
|
44
|
+
end
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
def response_error_message(response)
|
49
|
+
if response.nil?
|
50
|
+
'too many redirections'
|
51
|
+
else
|
52
|
+
"#{response.code} #{response.message}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def build_http(uri)
|
59
|
+
Net::HTTP.new(uri.host, uri.port)
|
60
|
+
.tap { |http| http.open_timeout = 10 }
|
61
|
+
.tap { |http| http.read_timeout = 10 }
|
62
|
+
.tap do |http|
|
63
|
+
if uri.instance_of? URI::HTTPS
|
64
|
+
http.use_ssl = true
|
65
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def updated_uri(old_uri, location)
|
71
|
+
URI.parse(location).then do |value|
|
72
|
+
if value.relative?
|
73
|
+
old_uri + location
|
74
|
+
else
|
75
|
+
value
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -17,22 +17,14 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
require 'get/subcommand/command'
|
20
|
+
require_relative '../../commons/common'
|
21
|
+
require_relative '../../commons/git'
|
22
|
+
require_relative '../command'
|
24
23
|
|
25
24
|
# Class length is disabled as most of its length is given by formatting.
|
26
25
|
# rubocop:disable Metrics/ClassLength
|
27
26
|
# Subcommand, generates a changelog.
|
28
27
|
class Changelog < Command
|
29
|
-
def self.command
|
30
|
-
@@command ||= new
|
31
|
-
@@command
|
32
|
-
end
|
33
|
-
|
34
|
-
private_class_method :new
|
35
|
-
|
36
28
|
private
|
37
29
|
|
38
30
|
UNDEFINED_SCOPE = 'other'
|
@@ -44,56 +36,11 @@ class Changelog < Command
|
|
44
36
|
item: '- %s'
|
45
37
|
}.freeze
|
46
38
|
|
47
|
-
@@command = nil
|
48
|
-
|
49
|
-
@@usage = 'changelog -h|(<subcommand> [<subcommand-options])'
|
50
|
-
@@description = 'Generate a changelog. Format options require a "%s" where the content must be.'
|
51
|
-
@@subcommands = {}
|
52
|
-
# This block is Optimist configuration. It is as long as the number of options of the command.
|
53
|
-
# rubocop:disable Metrics/BlockLength
|
54
|
-
@@option_parser = Optimist::Parser.new do
|
55
|
-
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
56
|
-
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
57
|
-
Subcommands:
|
58
|
-
#{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
|
59
|
-
SUBCOMMANDS
|
60
|
-
usage @@usage
|
61
|
-
synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
|
62
|
-
opt :latest,
|
63
|
-
'Generate the changelog from the latest version rather than the latest release'
|
64
|
-
opt :title_format,
|
65
|
-
'Set the symbol for the title.',
|
66
|
-
{ type: :string, short: 'T', default: '# %s' }
|
67
|
-
opt :type_format,
|
68
|
-
'Set the symbol for the commit types.',
|
69
|
-
{ type: :string, short: 't', default: '= %s' }
|
70
|
-
opt :scope_format,
|
71
|
-
'Set the symbol for the commit scopes.',
|
72
|
-
{ type: :string, short: 's', default: '- %s' }
|
73
|
-
opt :list_format,
|
74
|
-
'Set the symbol for lists.',
|
75
|
-
{ type: :string, short: 'l', default: '%s' }
|
76
|
-
opt :item_format,
|
77
|
-
'Set the symbol for list items.',
|
78
|
-
{ type: :string, short: 'i', default: '* %s' }
|
79
|
-
opt :markdown,
|
80
|
-
'Shortcut for `-T "# %s" -t "## %s" -s "### %s" -l "%s" -i "- %s"`. ' \
|
81
|
-
'Can be overwritten by the single options.'
|
82
|
-
educate_on_error
|
83
|
-
stop_on @@subcommands.keys.map(&:to_s)
|
84
|
-
end
|
85
|
-
# rubocop:enable Metrics/BlockLength
|
86
|
-
|
87
39
|
def initialize
|
88
|
-
super(
|
89
|
-
@
|
90
|
-
|
91
|
-
|
92
|
-
Common.error 'changelog need to be run inside a git repository' unless Git.in_repo?
|
93
|
-
@format = {}
|
94
|
-
set_format
|
95
|
-
|
96
|
-
puts changelog_from(@options[:latest] ? Git.last_version : Git.last_release)
|
40
|
+
super() do
|
41
|
+
@usage = 'changelog -h|(<subcommand> [<subcommand-options])'
|
42
|
+
@description = 'Generate a changelog. Format options require a "%s" where the content must be.'
|
43
|
+
@subcommands = {}
|
97
44
|
end
|
98
45
|
end
|
99
46
|
|
@@ -139,7 +86,7 @@ class Changelog < Command
|
|
139
86
|
formatted_types = []
|
140
87
|
changelog.except('feat', 'fix').each { |key, value| formatted_types.push(format_type(key, value)) }
|
141
88
|
<<~CHANGELOG
|
142
|
-
#{@format[:title].sub('%s', "Changelog from version #{from_version}")}
|
89
|
+
#{@format[:title].sub('%s', "Changelog from #{from_version.nil? ? 'first commit' : "version #{from_version}"}")}
|
143
90
|
#{(formatted_features + formatted_fixes + formatted_types).join("\n").strip}
|
144
91
|
CHANGELOG
|
145
92
|
end
|
@@ -161,5 +108,53 @@ class Changelog < Command
|
|
161
108
|
#{@format[:list].sub('%s', formatted_commits.join("\n"))}
|
162
109
|
SCOPE
|
163
110
|
end
|
111
|
+
|
112
|
+
protected
|
113
|
+
|
114
|
+
def setup_option_parser
|
115
|
+
@option_parser = Optimist::Parser.new(
|
116
|
+
@usage,
|
117
|
+
full_description,
|
118
|
+
stop_condition
|
119
|
+
) do |usage_header, description, stop_condition|
|
120
|
+
usage usage_header
|
121
|
+
synopsis description
|
122
|
+
opt :latest,
|
123
|
+
'Generate the changelog from the latest version rather than the latest release'
|
124
|
+
opt :title_format,
|
125
|
+
'Set the symbol for the title.',
|
126
|
+
{ type: :string, short: 'T', default: '# %s' }
|
127
|
+
opt :type_format,
|
128
|
+
'Set the symbol for the commit types.',
|
129
|
+
{ type: :string, short: 't', default: '= %s' }
|
130
|
+
opt :scope_format,
|
131
|
+
'Set the symbol for the commit scopes.',
|
132
|
+
{ type: :string, short: 's', default: '- %s' }
|
133
|
+
opt :list_format,
|
134
|
+
'Set the symbol for lists.',
|
135
|
+
{ type: :string, short: 'l', default: '%s' }
|
136
|
+
opt :item_format,
|
137
|
+
'Set the symbol for list items.',
|
138
|
+
{ type: :string, short: 'i', default: '* %s' }
|
139
|
+
opt :markdown,
|
140
|
+
'Shortcut for `-T "# %s" -t "## %s" -s "### %s" -l "%s" -i "- %s"`. ' \
|
141
|
+
'Can be overwritten by the single options.'
|
142
|
+
educate_on_error
|
143
|
+
stop_on stop_condition
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def setup_action
|
148
|
+
@action = lambda do
|
149
|
+
@options = Common.with_subcommand_exception_handling @option_parser do
|
150
|
+
@option_parser.parse
|
151
|
+
end
|
152
|
+
Common.error 'changelog need to be run inside a git repository' unless Git.in_repo?
|
153
|
+
@format = {}
|
154
|
+
set_format
|
155
|
+
|
156
|
+
puts changelog_from(@options[:latest] ? Git.last_version : Git.last_release)
|
157
|
+
end
|
158
|
+
end
|
164
159
|
end
|
165
160
|
# rubocop:enable Metrics/ClassLength
|
@@ -17,15 +17,67 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
-
|
20
|
+
require 'singleton'
|
21
|
+
|
22
|
+
# Base class for (sub)commands.
|
21
23
|
class Command
|
22
|
-
|
24
|
+
include Singleton
|
25
|
+
|
26
|
+
attr_reader :usage, :description, :action, :subcommands, :option_parser
|
23
27
|
|
24
28
|
protected
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
attr_writer :usage, :description, :action, :subcommands, :option_parser
|
31
|
+
|
32
|
+
def initialize
|
33
|
+
super
|
34
|
+
yield self if block_given?
|
35
|
+
setup_option_parser
|
36
|
+
if @option_parser.nil?
|
37
|
+
raise("No variable '@option_parser' has been created in the option_parser setup of command #{self.class.name}.")
|
38
|
+
end
|
39
|
+
|
40
|
+
setup_action
|
41
|
+
return unless @action.nil?
|
42
|
+
|
43
|
+
raise("No variable '@action' has been created in the action setup of the command #{self.class.name}")
|
44
|
+
end
|
45
|
+
|
46
|
+
@description = ''
|
47
|
+
@subcommands = {}
|
48
|
+
|
49
|
+
# This method must be overridden by subclasses to create a new option parser in a '@option_parser' variable.
|
50
|
+
# Do not call 'super' in the new implementation.
|
51
|
+
def setup_option_parser
|
52
|
+
raise("Error: command #{self.class.name} do not have a defined option parser.")
|
53
|
+
end
|
54
|
+
|
55
|
+
# This method must be overridden by subclasses to create a new option parser in a '@action' variable.
|
56
|
+
# Do not call 'super' in the new implementation.
|
57
|
+
def setup_action
|
58
|
+
raise("Error: command #{self.class.name} do not have a defined action.")
|
59
|
+
end
|
60
|
+
|
61
|
+
def full_description
|
62
|
+
description + if subcommands.empty?
|
63
|
+
''
|
64
|
+
else
|
65
|
+
subcommand_max_length = subcommands.keys.map { |k| k.to_s.length }.max || 0
|
66
|
+
<<~SUBCOMMANDS.chomp
|
67
|
+
\n
|
68
|
+
Subcommands:
|
69
|
+
#{subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{subcommands[k].description}" }.join("\n")}
|
70
|
+
SUBCOMMANDS
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def stop_condition
|
75
|
+
subcommands.keys.map(&:to_s)
|
76
|
+
end
|
77
|
+
|
78
|
+
def educated_error(message)
|
79
|
+
Common.error message do
|
80
|
+
@option_parser.educate
|
81
|
+
end
|
30
82
|
end
|
31
83
|
end
|
@@ -17,77 +17,24 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
require 'get/subcommand/commit/prompt'
|
20
|
+
require_relative '../../commons/common'
|
21
|
+
require_relative '../../commons/git'
|
22
|
+
require_relative '../command'
|
23
|
+
require_relative './prompt'
|
25
24
|
|
26
25
|
# Class length is disabled as most of its length is given by formatting.
|
27
26
|
# rubocop:disable Metrics/ClassLength
|
28
27
|
# Subcommand, it manages the description of the current git repository using semantic version.
|
29
28
|
class Commit < Command
|
30
|
-
def self.command
|
31
|
-
@@command ||= new
|
32
|
-
@@command
|
33
|
-
end
|
34
|
-
|
35
|
-
private_class_method :new
|
36
|
-
|
37
29
|
private
|
38
30
|
|
39
31
|
include PromptHandler
|
40
32
|
|
41
|
-
@@command = nil
|
42
|
-
|
43
|
-
@@usage = 'commit -h|(<subcommand> [<subcommand-options])'
|
44
|
-
@@description = 'Create a new semantic commit.'
|
45
|
-
@@subcommands = {}
|
46
|
-
# This block is Optimist configuration. It is as long as the number of options of the command.
|
47
|
-
# rubocop:disable Metrics/BlockLength
|
48
|
-
@@option_parser = Optimist::Parser.new do
|
49
|
-
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
50
|
-
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
51
|
-
Subcommands:
|
52
|
-
#{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
|
53
|
-
SUBCOMMANDS
|
54
|
-
usage @@usage
|
55
|
-
synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
|
56
|
-
opt :type,
|
57
|
-
'Define the type of the commit. Enabling this option skips the type selection.',
|
58
|
-
{ type: :string }
|
59
|
-
opt :scope,
|
60
|
-
'Define the scope of the commit. Enabling this option skips the scope selection.',
|
61
|
-
{ type: :string, short: 'S' }
|
62
|
-
opt :summary,
|
63
|
-
'Define the summary message of the commit. Enabling this option skips the summary message prompt.',
|
64
|
-
{ type: :string, short: 's' }
|
65
|
-
opt :message,
|
66
|
-
'Define the message body of the commit. Enabling this option skips the message body prompt.',
|
67
|
-
{ type: :string }
|
68
|
-
opt :breaking,
|
69
|
-
'Set the commit to have a breaking change. ' \
|
70
|
-
'Can be negated with "--no-breaking". ' \
|
71
|
-
'Enabling this option skips the breaking change prompt.',
|
72
|
-
{ type: :flag, short: :none }
|
73
|
-
educate_on_error
|
74
|
-
stop_on @@subcommands.keys.map(&:to_s)
|
75
|
-
end
|
76
|
-
# rubocop:enable Metrics/BlockLength
|
77
|
-
|
78
33
|
def initialize
|
79
|
-
super(
|
80
|
-
@
|
81
|
-
|
82
|
-
|
83
|
-
Common.error 'commit need to be run inside a git repository' unless Git.in_repo?
|
84
|
-
|
85
|
-
message = full_commit_message
|
86
|
-
puts message
|
87
|
-
output = `git commit --no-status -m "#{message.gsub('"', '\"')}"`
|
88
|
-
Common.error "git commit failed: #{output}" if $CHILD_STATUS.exitstatus.positive?
|
89
|
-
rescue Interrupt
|
90
|
-
Common.print_then_do_and_exit "\nCommit cancelled"
|
34
|
+
super() do
|
35
|
+
@usage = 'commit -h|(<subcommand> [<subcommand-options])'
|
36
|
+
@description = 'Create a new semantic commit.'
|
37
|
+
@subcommands = {}
|
91
38
|
end
|
92
39
|
end
|
93
40
|
|
@@ -143,5 +90,55 @@ class Commit < Command
|
|
143
90
|
ask_for_message
|
144
91
|
end.to_s.strip
|
145
92
|
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def setup_option_parser
|
97
|
+
@option_parser = Optimist::Parser.new(
|
98
|
+
@usage,
|
99
|
+
full_description,
|
100
|
+
stop_condition
|
101
|
+
) do |usage_header, description, stop_condition|
|
102
|
+
usage usage_header
|
103
|
+
synopsis description
|
104
|
+
opt :type,
|
105
|
+
'Define the type of the commit. Enabling this option skips the type selection.',
|
106
|
+
{ type: :string }
|
107
|
+
opt :scope,
|
108
|
+
'Define the scope of the commit. Enabling this option skips the scope selection.',
|
109
|
+
{ type: :string, short: 'S' }
|
110
|
+
opt :summary,
|
111
|
+
'Define the summary message of the commit. Enabling this option skips the summary message prompt.',
|
112
|
+
{ type: :string, short: 's' }
|
113
|
+
opt :message,
|
114
|
+
'Define the message body of the commit. Enabling this option skips the message body prompt.',
|
115
|
+
{ type: :string }
|
116
|
+
opt :breaking,
|
117
|
+
'Set the commit to have a breaking change. ' \
|
118
|
+
'Can be negated with "--no-breaking". ' \
|
119
|
+
'Enabling this option skips the breaking change prompt.',
|
120
|
+
{ type: :flag, short: :none }
|
121
|
+
opt :quiet,
|
122
|
+
'Disable the print of the complete message.'
|
123
|
+
educate_on_error
|
124
|
+
stop_on stop_condition
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def setup_action
|
129
|
+
@action = lambda do
|
130
|
+
@options = Common.with_subcommand_exception_handling @option_parser do
|
131
|
+
@option_parser.parse
|
132
|
+
end
|
133
|
+
Common.error 'commit need to be run inside a git repository' unless Git.in_repo?
|
134
|
+
|
135
|
+
message = full_commit_message
|
136
|
+
puts message unless @options[:quiet]
|
137
|
+
command_result = CommandIssuer.run('git', 'commit', '--no-status', '-m', "\"#{message.gsub('"', '\"')}\"")
|
138
|
+
Common.error "git commit failed: #{command_result.output}" if command_result.exit_status.positive?
|
139
|
+
rescue Interrupt
|
140
|
+
Common.print_then_do_and_exit "\nCommit cancelled"
|
141
|
+
end
|
142
|
+
end
|
146
143
|
end
|
147
144
|
# rubocop:enable Metrics/ClassLength
|