git_toolbox 0.5.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/get/{common.rb → commons/common.rb} +0 -25
- data/lib/get/commons/git.rb +59 -0
- data/lib/get/subcommand/changelog/changelog.rb +165 -0
- data/lib/get/subcommand/commit/commit.rb +10 -8
- data/lib/get/subcommand/commit/prompt.rb +3 -2
- data/lib/get/subcommand/complete/bash_completion.rb +92 -0
- data/lib/get/subcommand/complete/complete.rb +82 -0
- data/lib/get/subcommand/describe/change.rb +2 -2
- data/lib/get/subcommand/describe/describe.rb +15 -22
- data/lib/get/subcommand/describe/docker/docker.rb +6 -4
- data/lib/get/subcommand/init/init.rb +10 -8
- data/lib/get/subcommand/license/license.rb +6 -5
- data/lib/get/subcommand/tree/tree.rb +118 -0
- data/lib/get/version.rb +1 -1
- data/lib/get.rb +7 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0bb05b8ed627b884189e6b9c17eddc7284f20f4e2bce598dd24f154b6f717c99
|
4
|
+
data.tar.gz: bf1d78a1e32c113cb00cb0b0c9150f09359d4743927505d0ea34dd0d9fa48f20
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 949501f9b215d45757904e2201675539a9601a65e88e5418cb97ced39f3fa343aba3a53d34a45caefe490e46095d43318465634bc70d5b55bdda704676fd5333
|
7
|
+
data.tar.gz: 79f86d45ac90f0be948fd36948314aa0e5b27d1b596bdd8d36b661983660986ca1a91199b9698a5d5bce0947d70e0ffd8665f8506ee19ebaad7c5b99acdbea21
|
@@ -21,20 +21,6 @@ require 'English'
|
|
21
21
|
|
22
22
|
# Utility module
|
23
23
|
module Common
|
24
|
-
# Groups: 1 = type, 2 = scope with (), 3 = scope, 4 = breaking change
|
25
|
-
CONVENTIONAL_COMMIT_REGEX = /^(\w+)(\((\w+)\))?(!)?:.*/
|
26
|
-
|
27
|
-
# Check if the command is called while in a git repository.
|
28
|
-
# If the command fails, it is assumed to not be in a git repository.
|
29
|
-
def self.in_git_repo?
|
30
|
-
system('git rev-parse --is-inside-work-tree &>/dev/null')
|
31
|
-
case $CHILD_STATUS.exitstatus
|
32
|
-
when 0 then true
|
33
|
-
when 127 then Common.error '"git" is not installed.'
|
34
|
-
else false
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
24
|
# Print an error message and optionally run a block.
|
39
25
|
# Stdout becomes stderr, so every print is performed to stderr.
|
40
26
|
# This behavior is wanted as this method is called on errors.
|
@@ -55,17 +41,6 @@ module Common
|
|
55
41
|
# Version is not needed in this command
|
56
42
|
end
|
57
43
|
|
58
|
-
# Run a block of code with the list of commits from the given version as an argument.
|
59
|
-
# If the block is not given, this method is a nop.
|
60
|
-
def self.with_commit_list_from(version = nil, &block)
|
61
|
-
return unless block_given?
|
62
|
-
|
63
|
-
commits_from_version =
|
64
|
-
`git --no-pager log --oneline --pretty=format:%s #{version.nil? ? '' : "^#{version}"} HEAD`
|
65
|
-
.split("\n")
|
66
|
-
block.call(commits_from_version)
|
67
|
-
end
|
68
|
-
|
69
44
|
# Print the given message, execute a block if given,
|
70
45
|
# and exit the program with the given exit status.
|
71
46
|
# If exit_status is not 0, the stdout is redirected to stderr.
|
@@ -0,0 +1,59 @@
|
|
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 'English'
|
21
|
+
require 'get/commons/common'
|
22
|
+
|
23
|
+
# Utility module
|
24
|
+
module Git
|
25
|
+
# Groups: 1 = type, 2 = scope with (), 3 = scope, 4 = breaking change, 5 = summary
|
26
|
+
CONVENTIONAL_COMMIT_REGEX = /^(\w+)(\((\w+)\))?(!)?:(.*)/
|
27
|
+
|
28
|
+
# Check if the command is called while in a git repository.
|
29
|
+
# If the command fails, it is assumed to not be in a git repository.
|
30
|
+
def self.in_repo?
|
31
|
+
system('git rev-parse --is-inside-work-tree &>/dev/null')
|
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
|
37
|
+
end
|
38
|
+
|
39
|
+
# Run a block of code with the list of commits from the given version as an argument.
|
40
|
+
# If the block is not given, this method is a nop.
|
41
|
+
def self.with_commit_list_from(version = nil, &block)
|
42
|
+
return unless block_given?
|
43
|
+
|
44
|
+
commits_from_version =
|
45
|
+
`git --no-pager log --oneline --pretty=format:%s #{version.nil? ? '' : "^#{version}"} HEAD`
|
46
|
+
.split("\n")
|
47
|
+
block.call(commits_from_version)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the last version and caches it for the next calls.
|
51
|
+
def self.last_version
|
52
|
+
@@last_version ||= `git describe --tags --abbrev=0`.strip
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the last release and caches it for the next calls.
|
56
|
+
def self.last_release
|
57
|
+
@@last_release ||= `git --no-pager tag --list | sed 's/+/_/' | sort -V | sed 's/_/+/' | tail -n 1`.strip
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,165 @@
|
|
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 'English'
|
21
|
+
require 'get/commons/common'
|
22
|
+
require 'get/commons/git'
|
23
|
+
require 'get/subcommand/command'
|
24
|
+
|
25
|
+
# Class length is disabled as most of its length is given by formatting.
|
26
|
+
# rubocop:disable Metrics/ClassLength
|
27
|
+
# Subcommand, generates a changelog.
|
28
|
+
class Changelog < Command
|
29
|
+
def self.command
|
30
|
+
@@command ||= new
|
31
|
+
@@command
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method :new
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
UNDEFINED_SCOPE = 'other'
|
39
|
+
MARKDOWN_FORMAT = {
|
40
|
+
title: '# %s',
|
41
|
+
type: '## %s',
|
42
|
+
scope: '### %s',
|
43
|
+
list: '%s',
|
44
|
+
item: '- %s'
|
45
|
+
}.freeze
|
46
|
+
|
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
|
+
def initialize
|
88
|
+
super(@@usage, @@description) do
|
89
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
90
|
+
@@option_parser.parse
|
91
|
+
end
|
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)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def set_format
|
101
|
+
%w[title type scope list item].each do |element|
|
102
|
+
@format[element.to_sym] = if @options[:markdown] && !@options["#{element}_given".to_sym]
|
103
|
+
MARKDOWN_FORMAT[element.to_sym]
|
104
|
+
else
|
105
|
+
unless @options["#{element}_format".to_sym].include? '%s'
|
106
|
+
Common.error "The given format for '#{element}' must contain '%s'."
|
107
|
+
end
|
108
|
+
@options["#{element}_format".to_sym]
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def changelog_from(version)
|
114
|
+
commit_map = {}
|
115
|
+
|
116
|
+
Git.with_commit_list_from(version) do |list|
|
117
|
+
list.each do |element|
|
118
|
+
match_result = Git::CONVENTIONAL_COMMIT_REGEX.match(element)
|
119
|
+
temp_hash = {
|
120
|
+
match_result[1] => {
|
121
|
+
(match_result[3] || UNDEFINED_SCOPE) => [match_result[5].strip.capitalize]
|
122
|
+
}
|
123
|
+
}
|
124
|
+
commit_map.merge!(temp_hash) do |_key, old_value, new_value|
|
125
|
+
old_value.merge(new_value) do |_inner_key, old_array, new_array|
|
126
|
+
old_array + new_array
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
format_changelog(version, commit_map)
|
133
|
+
end
|
134
|
+
|
135
|
+
def format_changelog(from_version, changelog)
|
136
|
+
formatted_features = changelog.key?('feat') ? [format_type('feat', changelog['feat'])] : []
|
137
|
+
formatted_fixes = changelog.key?('fix') ? [format_type('fix', changelog['fix'])] : []
|
138
|
+
|
139
|
+
formatted_types = []
|
140
|
+
changelog.except('feat', 'fix').each { |key, value| formatted_types.push(format_type(key, value)) }
|
141
|
+
<<~CHANGELOG
|
142
|
+
#{@format[:title].sub('%s', "Changelog from version #{from_version}")}
|
143
|
+
#{(formatted_features + formatted_fixes + formatted_types).join("\n").strip}
|
144
|
+
CHANGELOG
|
145
|
+
end
|
146
|
+
|
147
|
+
def format_type(type, scopes)
|
148
|
+
formatted_scopes = []
|
149
|
+
scopes.each { |key, value| formatted_scopes.push(format_scope(key, value)) }
|
150
|
+
<<~TYPE
|
151
|
+
#{@format[:type].sub('%s', type.to_s)}
|
152
|
+
#{formatted_scopes.join("\n").strip}
|
153
|
+
TYPE
|
154
|
+
end
|
155
|
+
|
156
|
+
def format_scope(scope, commits)
|
157
|
+
formatted_commits = []
|
158
|
+
commits.each { |element| formatted_commits.push(@format[:item].sub('%s', element)) }
|
159
|
+
<<~SCOPE
|
160
|
+
#{@format[:scope].sub('%s', scope.to_s)}
|
161
|
+
#{@format[:list].sub('%s', formatted_commits.join("\n"))}
|
162
|
+
SCOPE
|
163
|
+
end
|
164
|
+
end
|
165
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -18,7 +18,8 @@
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
20
|
require 'English'
|
21
|
-
require 'get/common'
|
21
|
+
require 'get/commons/common'
|
22
|
+
require 'get/commons/git'
|
22
23
|
require 'get/subcommand/command'
|
23
24
|
require 'get/subcommand/commit/prompt'
|
24
25
|
|
@@ -40,17 +41,18 @@ class Commit < Command
|
|
40
41
|
@@command = nil
|
41
42
|
|
42
43
|
@@usage = 'commit -h|(<subcommand> [<subcommand-options])'
|
43
|
-
@@description = 'Create a new semantic commit'
|
44
|
+
@@description = 'Create a new semantic commit.'
|
44
45
|
@@subcommands = {}
|
45
46
|
# This block is Optimist configuration. It is as long as the number of options of the command.
|
46
47
|
# rubocop:disable Metrics/BlockLength
|
47
|
-
@@
|
48
|
+
@@option_parser = Optimist::Parser.new do
|
48
49
|
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
49
|
-
|
50
|
-
synopsis <<~SUBCOMMANDS unless @@subcommands.empty?
|
50
|
+
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
51
51
|
Subcommands:
|
52
52
|
#{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
|
53
53
|
SUBCOMMANDS
|
54
|
+
usage @@usage
|
55
|
+
synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
|
54
56
|
opt :type,
|
55
57
|
'Define the type of the commit. Enabling this option skips the type selection.',
|
56
58
|
{ type: :string }
|
@@ -75,10 +77,10 @@ class Commit < Command
|
|
75
77
|
|
76
78
|
def initialize
|
77
79
|
super(@@usage, @@description) do
|
78
|
-
Common.
|
79
|
-
|
80
|
-
@@commit_parser.parse
|
80
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
81
|
+
@@option_parser.parse
|
81
82
|
end
|
83
|
+
Common.error 'commit need to be run inside a git repository' unless Git.in_repo?
|
82
84
|
|
83
85
|
message = full_commit_message
|
84
86
|
puts message
|
@@ -18,6 +18,7 @@
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
20
|
require 'highline'
|
21
|
+
require 'get/commons/git'
|
21
22
|
|
22
23
|
# Module for asking to the user informations about a commit message.
|
23
24
|
module PromptHandler
|
@@ -107,9 +108,9 @@ module PromptHandler
|
|
107
108
|
def extract_types_and_scopes
|
108
109
|
return unless @@custom_values_initialized.nil?
|
109
110
|
|
110
|
-
|
111
|
+
Git.with_commit_list_from(FIRST_COMMIT) do |commit_list|
|
111
112
|
commit_list.map do |element|
|
112
|
-
match =
|
113
|
+
match = Git::CONVENTIONAL_COMMIT_REGEX.match(element)
|
113
114
|
next if match.nil?
|
114
115
|
|
115
116
|
type_already_added = DEFAULT_TYPES.include?(match[1].to_sym) || @@custom_types.include?(match[1])
|
@@ -0,0 +1,92 @@
|
|
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
|
+
# Module with bash completion generation functions
|
21
|
+
module BashCompletion
|
22
|
+
def bash_completion(main_module, command_name)
|
23
|
+
generate_functions(main_module, command_name, INITIAL_LEVEL)
|
24
|
+
|
25
|
+
"#{HEADER}\n#{@@function_stack.reverse.join("\n")}"
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
INITIAL_LEVEL = 1
|
31
|
+
|
32
|
+
HEADER = <<~HEADER
|
33
|
+
#!/usr/bin/env bash
|
34
|
+
#
|
35
|
+
# This file has been generated by `get complete`.
|
36
|
+
# The script structure was inspired by https://opensource.com/article/18/3/creating-bash-completion-script
|
37
|
+
#
|
38
|
+
HEADER
|
39
|
+
|
40
|
+
@@function_stack = []
|
41
|
+
|
42
|
+
def full_subcommand_name(base, name)
|
43
|
+
"#{base}_#{name}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def completion_function_definition(name, level, long_options, short_options, subcommands)
|
47
|
+
<<~FUNCTION
|
48
|
+
_#{name}_completion()
|
49
|
+
{
|
50
|
+
if [ "${COMP_CWORD}" -eq "#{level}" ] ; then
|
51
|
+
COMPREPLY=($(compgen -W "#{(long_options | short_options | subcommands).join(' ')}" -- "${COMP_WORDS[#{level}]}"))
|
52
|
+
else
|
53
|
+
case "${COMP_WORDS[#{level}]}" in
|
54
|
+
#{subcommands.map { |element| subcommand_case_completion(name, element) }.join("\n")}
|
55
|
+
*)
|
56
|
+
COMPREPLY=()
|
57
|
+
;;
|
58
|
+
esac
|
59
|
+
fi
|
60
|
+
}
|
61
|
+
FUNCTION
|
62
|
+
end
|
63
|
+
|
64
|
+
def subcommand_case_completion(base, name)
|
65
|
+
<<~CASE
|
66
|
+
"#{name}")
|
67
|
+
_#{full_subcommand_name(base, name)}_completion
|
68
|
+
;;
|
69
|
+
CASE
|
70
|
+
end
|
71
|
+
|
72
|
+
def generate_functions(command_class, name, level)
|
73
|
+
long_options = []
|
74
|
+
short_options = []
|
75
|
+
subcommands = []
|
76
|
+
|
77
|
+
command_class.class_variable_get(:@@option_parser).specs.each_value do |option|
|
78
|
+
long_options.push("--#{option.long}")
|
79
|
+
short_options.push("-#{option.short.nil? ? option.long[0] : option.short}") if option.short != :none
|
80
|
+
end
|
81
|
+
|
82
|
+
command_class.class_variable_get(:@@subcommands).each_key do |subcommand|
|
83
|
+
subcommands.push(subcommand.to_s)
|
84
|
+
end
|
85
|
+
|
86
|
+
@@function_stack.push(completion_function_definition(name, level, long_options, short_options, subcommands))
|
87
|
+
|
88
|
+
command_class.class_variable_get(:@@subcommands).each do |element|
|
89
|
+
generate_functions(element[1].class, full_subcommand_name(name, element[0].to_s), level + 1)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,82 @@
|
|
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 'English'
|
21
|
+
require 'get/commons/common'
|
22
|
+
require 'get/subcommand/command'
|
23
|
+
require 'get/subcommand/complete/bash_completion'
|
24
|
+
require 'get'
|
25
|
+
|
26
|
+
# Class length is disabled as most of its length is given by formatting.
|
27
|
+
# rubocop:disable Metrics/ClassLength
|
28
|
+
# Subcommand, prints the bash completion script.
|
29
|
+
class Complete < Command
|
30
|
+
def self.command
|
31
|
+
@@command ||= new
|
32
|
+
@@command
|
33
|
+
end
|
34
|
+
|
35
|
+
private_class_method :new
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
include BashCompletion
|
40
|
+
|
41
|
+
@@command = nil
|
42
|
+
|
43
|
+
@@usage = 'complete -h|(<subcommand> [<subcommand-options])'
|
44
|
+
@@description = 'Print the shell completion script.'
|
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 :shell,
|
57
|
+
'Select the type of shell of which the completion will be generated.',
|
58
|
+
{ type: :string, default: 'bash' }
|
59
|
+
educate_on_error
|
60
|
+
stop_on @@subcommands.keys.map(&:to_s)
|
61
|
+
end
|
62
|
+
# rubocop:enable Metrics/BlockLength
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
super(@@usage, @@description) do
|
66
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
67
|
+
@@option_parser.parse
|
68
|
+
end
|
69
|
+
|
70
|
+
@completions = {
|
71
|
+
bash: proc { bash_completion(Get, 'get') }
|
72
|
+
}
|
73
|
+
|
74
|
+
selected_shell = @options[:shell].to_sym
|
75
|
+
|
76
|
+
Common.error "Completion for shell '#{selected_shell}' not available." unless @completions.key?(selected_shell)
|
77
|
+
|
78
|
+
puts @completions[selected_shell].call
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -51,7 +51,7 @@ module ChangeHandler
|
|
51
51
|
class ::String
|
52
52
|
# Convert the string (as a conventional commit string) into a change type.
|
53
53
|
def to_change
|
54
|
-
groups =
|
54
|
+
groups = Git::CONVENTIONAL_COMMIT_REGEX.match(self)
|
55
55
|
return :MAJOR if ChangeHandler.triggers_major?(groups[1], groups[3], !groups[4].nil?)
|
56
56
|
return :MINOR if ChangeHandler.triggers_minor?(groups[1], groups[2])
|
57
57
|
return :PATCH if ChangeHandler.triggers_patch?(groups[1], groups[2])
|
@@ -64,7 +64,7 @@ module ChangeHandler
|
|
64
64
|
|
65
65
|
def greatest_change_in(commit_list)
|
66
66
|
commit_list
|
67
|
-
.grep(
|
67
|
+
.grep(Git::CONVENTIONAL_COMMIT_REGEX)
|
68
68
|
.map(&:to_change)
|
69
69
|
.max { |a, b| CHANGE_TYPE.index(a) <=> CHANGE_TYPE.index(b) }
|
70
70
|
end
|
@@ -17,6 +17,8 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
+
require 'get/commons/common'
|
21
|
+
require 'get/commons/git'
|
20
22
|
require 'get/subcommand/command'
|
21
23
|
require 'get/subcommand/describe/change'
|
22
24
|
require 'get/subcommand/describe/prerelease'
|
@@ -50,19 +52,20 @@ class Describe < Command
|
|
50
52
|
/x
|
51
53
|
|
52
54
|
@@usage = 'describe -h|(<subcommand> [<subcommand-options])'
|
53
|
-
@@description = 'Describe the current git repository with semantic version'
|
55
|
+
@@description = 'Describe the current git repository with semantic version.'
|
54
56
|
@@subcommands = {
|
55
57
|
docker: DescribeDocker.command,
|
56
58
|
}
|
57
59
|
# This block is Optimist configuration. It is as long as the number of options of the command.
|
58
60
|
# rubocop:disable Metrics/BlockLength
|
59
|
-
@@
|
61
|
+
@@option_parser = Optimist::Parser.new do
|
60
62
|
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
61
|
-
|
62
|
-
synopsis <<~SUBCOMMANDS unless @@subcommands.empty?
|
63
|
+
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
63
64
|
Subcommands:
|
64
65
|
#{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
|
65
66
|
SUBCOMMANDS
|
67
|
+
usage @@usage
|
68
|
+
synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
|
66
69
|
opt :prerelease,
|
67
70
|
'Describe a prerelease rather than a release',
|
68
71
|
short: :none
|
@@ -109,10 +112,10 @@ class Describe < Command
|
|
109
112
|
|
110
113
|
def initialize
|
111
114
|
super(@@usage, @@description) do
|
112
|
-
Common.
|
113
|
-
|
114
|
-
@@describe_parser.parse
|
115
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
116
|
+
@@option_parser.parse
|
115
117
|
end
|
118
|
+
Common.error 'describe need to be run inside a git repository' unless Git.in_repo?
|
116
119
|
set_options
|
117
120
|
|
118
121
|
if ARGV.length.positive?
|
@@ -141,9 +144,9 @@ class Describe < Command
|
|
141
144
|
end
|
142
145
|
|
143
146
|
def describe_current_commit
|
144
|
-
return last_version if
|
147
|
+
return Git.last_version if Git.with_commit_list_from(Git.last_version, &:empty?)
|
145
148
|
|
146
|
-
puts "Last version: #{last_version}" if @options[:diff]
|
149
|
+
puts "Last version: #{Git.last_version}" if @options[:diff]
|
147
150
|
|
148
151
|
current_commit_version = next_release
|
149
152
|
create_signed_tag(current_commit_version) if @options[:create_tag]
|
@@ -153,9 +156,9 @@ class Describe < Command
|
|
153
156
|
|
154
157
|
def next_release
|
155
158
|
if @options[:prerelease]
|
156
|
-
prepare_prerelease_tag(last_release, last_version)
|
159
|
+
prepare_prerelease_tag(Git.last_release, Git.last_version)
|
157
160
|
else
|
158
|
-
prepare_release_tag(last_release)
|
161
|
+
prepare_release_tag(Git.last_release)
|
159
162
|
end + metadata
|
160
163
|
end
|
161
164
|
|
@@ -172,20 +175,10 @@ class Describe < Command
|
|
172
175
|
"-#{updated_prerelease(base_version_match_data[5], need_reset: base_version_match_data[1] != new_stable_version)}"
|
173
176
|
end
|
174
177
|
|
175
|
-
# Returns the last version and caches it for the next calls.
|
176
|
-
def last_version
|
177
|
-
@last_version ||= `git describe --tags --abbrev=0`.strip
|
178
|
-
end
|
179
|
-
|
180
|
-
# Returns the last release and caches it for the next calls.
|
181
|
-
def last_release
|
182
|
-
@last_release ||= `git --no-pager tag --list | sed 's/+/_/' | sort -V | sed 's/_/+/' | tail -n 1`.strip
|
183
|
-
end
|
184
|
-
|
185
178
|
def updated_stable_version(stable_version)
|
186
179
|
return DEFAULT_RELEASE_VERSION if stable_version.nil?
|
187
180
|
|
188
|
-
greatest_change_from_stable_version =
|
181
|
+
greatest_change_from_stable_version = Git.with_commit_list_from(stable_version) do |commits_from_version|
|
189
182
|
greatest_change_in(commits_from_version)
|
190
183
|
end
|
191
184
|
split_version = stable_version.split('.')
|
@@ -17,6 +17,8 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
+
require 'get/commons/common'
|
21
|
+
require 'get/commons/git'
|
20
22
|
require 'get/subcommand/command'
|
21
23
|
|
22
24
|
# Class length is disabled as most of its length is given by formatting.
|
@@ -41,7 +43,7 @@ class DescribeDocker < Command
|
|
41
43
|
@@subcommands = {}
|
42
44
|
# This block is Optimist configuration. It is as long as the number of options of the command.
|
43
45
|
# rubocop:disable Metrics/BlockLength
|
44
|
-
@@
|
46
|
+
@@option_parser = Optimist::Parser.new do
|
45
47
|
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
46
48
|
usage @@usage
|
47
49
|
synopsis <<~SUBCOMMANDS unless @@subcommands.empty?
|
@@ -64,9 +66,9 @@ class DescribeDocker < Command
|
|
64
66
|
|
65
67
|
def initialize
|
66
68
|
super(@@usage, @@description) do |version|
|
67
|
-
Common.error 'describe need to be run inside a git repository' unless
|
68
|
-
@options = Common.with_subcommand_exception_handling @@
|
69
|
-
@@
|
69
|
+
Common.error 'describe need to be run inside a git repository' unless Git.in_repo?
|
70
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
71
|
+
@@option_parser.parse
|
70
72
|
end
|
71
73
|
set_options
|
72
74
|
|
@@ -18,7 +18,8 @@
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
20
|
require 'English'
|
21
|
-
require 'get/common'
|
21
|
+
require 'get/commons/common'
|
22
|
+
require 'get/commons/git'
|
22
23
|
require 'get/subcommand/command'
|
23
24
|
|
24
25
|
# Class length is disabled as most of its length is given by formatting.
|
@@ -37,17 +38,18 @@ class Init < Command
|
|
37
38
|
@@command = nil
|
38
39
|
|
39
40
|
@@usage = 'init -h|(<subcommand> [<subcommand-options])'
|
40
|
-
@@description = 'Initialize a new git repository with an initial empty commit'
|
41
|
+
@@description = 'Initialize a new git repository with an initial empty commit.'
|
41
42
|
@@subcommands = {}
|
42
43
|
# This block is Optimist configuration. It is as long as the number of options of the command.
|
43
44
|
# rubocop:disable Metrics/BlockLength
|
44
|
-
@@
|
45
|
+
@@option_parser = Optimist::Parser.new do
|
45
46
|
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
46
|
-
|
47
|
-
synopsis <<~SUBCOMMANDS unless @@subcommands.empty?
|
47
|
+
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
48
48
|
Subcommands:
|
49
49
|
#{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
|
50
50
|
SUBCOMMANDS
|
51
|
+
usage @@usage
|
52
|
+
synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
|
51
53
|
opt :empty,
|
52
54
|
'Do not create the first, empty commit.'
|
53
55
|
educate_on_error
|
@@ -57,10 +59,10 @@ class Init < Command
|
|
57
59
|
|
58
60
|
def initialize
|
59
61
|
super(@@usage, @@description) do
|
60
|
-
@options = Common.with_subcommand_exception_handling @@
|
61
|
-
@@
|
62
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
63
|
+
@@option_parser.parse
|
62
64
|
end
|
63
|
-
Common.error 'The current directory is already a git repository' if
|
65
|
+
Common.error 'The current directory is already a git repository' if Git.in_repo?
|
64
66
|
|
65
67
|
init_repository
|
66
68
|
end
|
@@ -17,7 +17,8 @@
|
|
17
17
|
|
18
18
|
# frozen_string_literal: true
|
19
19
|
|
20
|
-
require 'get/common'
|
20
|
+
require 'get/commons/common'
|
21
|
+
require 'get/commons/git'
|
21
22
|
require 'get/subcommand/command'
|
22
23
|
require 'get/subcommand/license/license_retriever'
|
23
24
|
|
@@ -45,7 +46,7 @@ class License < Command
|
|
45
46
|
@@subcommands = {}
|
46
47
|
# This block is Optimist configuration. It is as long as the number of options of the command.
|
47
48
|
# rubocop:disable Metrics/BlockLength
|
48
|
-
@@
|
49
|
+
@@option_parser = Optimist::Parser.new do
|
49
50
|
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
50
51
|
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
51
52
|
Subcommands:
|
@@ -68,8 +69,8 @@ class License < Command
|
|
68
69
|
|
69
70
|
def initialize
|
70
71
|
super(@@usage, @@description) do
|
71
|
-
@options = Common.with_subcommand_exception_handling @@
|
72
|
-
@@
|
72
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
73
|
+
@@option_parser.parse
|
73
74
|
end
|
74
75
|
|
75
76
|
@filename = 'LICENSE'
|
@@ -79,7 +80,7 @@ class License < Command
|
|
79
80
|
create_license_file
|
80
81
|
|
81
82
|
if @options[:create_commit]
|
82
|
-
Common.error 'Not in a git repository: a commit cannot be created.' unless
|
83
|
+
Common.error 'Not in a git repository: a commit cannot be created.' unless Git.in_repo?
|
83
84
|
|
84
85
|
create_license_commit
|
85
86
|
end
|
@@ -0,0 +1,118 @@
|
|
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 'English'
|
21
|
+
require 'get/commons/common'
|
22
|
+
require 'get/commons/git'
|
23
|
+
require 'get/subcommand/command'
|
24
|
+
|
25
|
+
# Class length is disabled as most of its length is given by formatting.
|
26
|
+
# rubocop:disable Metrics/ClassLength
|
27
|
+
# Subcommand, it allow to create a new repository and add an initial, empty commit to it.
|
28
|
+
class Tree < Command
|
29
|
+
def self.command
|
30
|
+
@@command ||= new
|
31
|
+
@@command
|
32
|
+
end
|
33
|
+
|
34
|
+
private_class_method :new
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
@@command = nil
|
39
|
+
|
40
|
+
@@usage = 'tree -h|(<subcommand> [<subcommand-options])'
|
41
|
+
@@description = 'Print the tree of commits.'
|
42
|
+
@@subcommands = {}
|
43
|
+
# This block is Optimist configuration. It is as long as the number of options of the command.
|
44
|
+
# rubocop:disable Metrics/BlockLength
|
45
|
+
@@option_parser = Optimist::Parser.new do
|
46
|
+
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
47
|
+
subcommand_section = <<~SUBCOMMANDS unless @@subcommands.empty?
|
48
|
+
Subcommands:
|
49
|
+
#{@@subcommands.keys.map { |k| " #{k.to_s.ljust(subcommand_max_length)} => #{@@subcommands[k].description}" }.join("\n")}
|
50
|
+
SUBCOMMANDS
|
51
|
+
usage @@usage
|
52
|
+
synopsis @@description + (subcommand_section.nil? ? '' : "\n") + subcommand_section.to_s
|
53
|
+
educate_on_error
|
54
|
+
stop_on @@subcommands.keys.map(&:to_s)
|
55
|
+
end
|
56
|
+
# rubocop:enable Metrics/BlockLength
|
57
|
+
|
58
|
+
def initialize
|
59
|
+
super(@@usage, @@description) do
|
60
|
+
@options = Common.with_subcommand_exception_handling @@option_parser do
|
61
|
+
@@option_parser.parse
|
62
|
+
end
|
63
|
+
Common.error 'tree need to be run inside a git repository' unless Git.in_repo?
|
64
|
+
|
65
|
+
view_tree
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def view_tree
|
70
|
+
page_log(transform_log(log))
|
71
|
+
end
|
72
|
+
|
73
|
+
TREE_FORMAT = '%C(bold blue)%h%C(reset)§%C(dim normal)(%cr)%C(reset)§%C(auto)%d%C(reset)§§%n' \
|
74
|
+
'§§§ %C(normal)%an%C(reset)%C(dim normal): %s%C(reset)'
|
75
|
+
|
76
|
+
def log
|
77
|
+
`git log --all --graph --decorate=short --date-order --color --pretty=format:"#{TREE_FORMAT}"`
|
78
|
+
end
|
79
|
+
|
80
|
+
TIME_REGEX = /(\([a-z0-9 ,]+\))/
|
81
|
+
TIME_MINIMUM_PADDING = 2
|
82
|
+
|
83
|
+
def transform_log(text)
|
84
|
+
split_lines = text.split("\n").map { |element| element.split('§') }
|
85
|
+
# The first line is always a commit line, so it always have a time reference
|
86
|
+
first_line_time = split_lines.first[1]
|
87
|
+
# calc color escape codes length
|
88
|
+
time_color_length = first_line_time.length - first_line_time.match(TIME_REGEX)[1].length
|
89
|
+
|
90
|
+
# calc max length of time references
|
91
|
+
time_padding = 0
|
92
|
+
split_lines.each { |element| time_padding = [time_padding, element[1].length - time_color_length].max }
|
93
|
+
|
94
|
+
# format strings
|
95
|
+
split_lines
|
96
|
+
.map do |element|
|
97
|
+
# Only lines with the date reference have the color escape codes,
|
98
|
+
# the other lines do not need the additional padding
|
99
|
+
left_padding = TIME_MINIMUM_PADDING + time_padding +
|
100
|
+
(element[1].match?(TIME_REGEX) ? time_color_length : 0)
|
101
|
+
format(
|
102
|
+
'%<date>s %<tree_mark>s %<pointers>s %<commit_text>s',
|
103
|
+
{
|
104
|
+
date: element[1].rjust(left_padding),
|
105
|
+
tree_mark: element[0],
|
106
|
+
pointers: element[2],
|
107
|
+
commit_text: element[3]
|
108
|
+
}
|
109
|
+
)
|
110
|
+
end
|
111
|
+
.join("\n")
|
112
|
+
end
|
113
|
+
|
114
|
+
def page_log(text)
|
115
|
+
system("less -RfS <(echo -e '#{text}')")
|
116
|
+
end
|
117
|
+
end
|
118
|
+
# rubocop:enable Metrics/ClassLength
|
data/lib/get/version.rb
CHANGED
data/lib/get.rb
CHANGED
@@ -23,8 +23,11 @@ require 'get/subcommand/describe/describe'
|
|
23
23
|
require 'get/subcommand/commit/commit'
|
24
24
|
require 'get/subcommand/init/init'
|
25
25
|
require 'get/subcommand/license/license'
|
26
|
+
require 'get/subcommand/complete/complete'
|
27
|
+
require 'get/subcommand/changelog/changelog'
|
28
|
+
require 'get/subcommand/tree/tree'
|
26
29
|
require 'get/version'
|
27
|
-
require 'get/common'
|
30
|
+
require 'get/commons/common'
|
28
31
|
|
29
32
|
# Entrypoint of Get
|
30
33
|
module Get
|
@@ -35,6 +38,9 @@ module Get
|
|
35
38
|
commit: Commit.command,
|
36
39
|
init: Init.command,
|
37
40
|
license: License.command,
|
41
|
+
complete: Complete.command,
|
42
|
+
changelog: Changelog.command,
|
43
|
+
tree: Tree.command,
|
38
44
|
}
|
39
45
|
@@option_parser = Optimist::Parser.new do
|
40
46
|
subcommand_max_length = @@subcommands.keys.map { |k| k.to_s.length }.max
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: git_toolbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alex Speranza
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-04-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: optimist
|
@@ -55,10 +55,14 @@ files:
|
|
55
55
|
- bin/get
|
56
56
|
- bin/setup
|
57
57
|
- lib/get.rb
|
58
|
-
- lib/get/common.rb
|
58
|
+
- lib/get/commons/common.rb
|
59
|
+
- lib/get/commons/git.rb
|
60
|
+
- lib/get/subcommand/changelog/changelog.rb
|
59
61
|
- lib/get/subcommand/command.rb
|
60
62
|
- lib/get/subcommand/commit/commit.rb
|
61
63
|
- lib/get/subcommand/commit/prompt.rb
|
64
|
+
- lib/get/subcommand/complete/bash_completion.rb
|
65
|
+
- lib/get/subcommand/complete/complete.rb
|
62
66
|
- lib/get/subcommand/describe/change.rb
|
63
67
|
- lib/get/subcommand/describe/describe.rb
|
64
68
|
- lib/get/subcommand/describe/docker/docker.rb
|
@@ -72,6 +76,7 @@ files:
|
|
72
76
|
- lib/get/subcommand/license/offline_licenses/GNU General Public License v3.0/LICENSE
|
73
77
|
- lib/get/subcommand/license/offline_licenses/MIT License/LICENSE
|
74
78
|
- lib/get/subcommand/license/offline_licenses/Mozilla Public License 2.0/LICENSE
|
79
|
+
- lib/get/subcommand/tree/tree.rb
|
75
80
|
- lib/get/version.rb
|
76
81
|
homepage: https://github.com/asperan/get
|
77
82
|
licenses:
|