sem_ver_components 0.0.1
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 +7 -0
- data/bin/sem_ver_git +43 -0
- data/lib/sem_ver_components/local_git.rb +65 -0
- data/lib/sem_ver_components/output.rb +15 -0
- data/lib/sem_ver_components/outputs/info.rb +39 -0
- data/lib/sem_ver_components/outputs/semantic_release_analyze.rb +33 -0
- data/lib/sem_ver_components/outputs/semantic_release_generate_notes.rb +104 -0
- data/lib/sem_ver_components/plugins.rb +43 -0
- data/lib/sem_ver_components/version.rb +5 -0
- metadata +81 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 350d90498079e7485e8eab0897b6d02c92c8aa9bddea07d04e31425dcef60152
|
4
|
+
data.tar.gz: 4414c65c293c951d94346108c67523430229e22b2764205877a6c10e350b02b8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a5b77dd8f100c03fe7b77f09adb0291c478095458d35756a3f10799e2d66e423c219bab025a81d19d9e382bc66db08cc56a1fb5a78ff5dcfcf178263f52092ab
|
7
|
+
data.tar.gz: e6ce0c0d97f7cdc3c336da1a28af412a037a3ca7ca278ca3d07adc4911672a17b2f947821078fff81f9ef751635ce75baea736308d82d0d5beadad0beb8c3e34
|
data/bin/sem_ver_git
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'optparse'
|
3
|
+
require 'sem_ver_components/plugins'
|
4
|
+
require 'sem_ver_components/local_git'
|
5
|
+
require 'sem_ver_components/version'
|
6
|
+
require 'sem_ver_components/output'
|
7
|
+
|
8
|
+
# Default values
|
9
|
+
git_repo = '.'
|
10
|
+
git_from = nil
|
11
|
+
git_to = 'HEAD'
|
12
|
+
output = :info
|
13
|
+
output_plugins = SemVerComponents::Plugins.new(:outputs)
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: #{File.basename($0)} [options]"
|
16
|
+
opts.on('-f', '--from GIT_REF', 'Git reference from which commits are to be analyzed (defaults to first commit)') do |git_ref|
|
17
|
+
git_from = git_ref
|
18
|
+
end
|
19
|
+
opts.on('-h', '--help', 'Display this help') do
|
20
|
+
puts opts
|
21
|
+
exit 0
|
22
|
+
end
|
23
|
+
opts.on('-o', '--output OUTPUT', "Specify the output format of the analysis. Possible values are #{output_plugins.list.sort.join(', ')} (defauts to #{output})") do |output_str|
|
24
|
+
output = output_str.to_sym
|
25
|
+
end
|
26
|
+
opts.on('-r', '--repo GIT_URL', "Specify the Git URL of the repository to analyze (defaults to #{git_repo})") do |git_url|
|
27
|
+
git_repo = git_url
|
28
|
+
end
|
29
|
+
opts.on('-t', '--to GIT_REF', "Git reference to which commits are to be analyzed (defaults to #{git_to})") do |git_ref|
|
30
|
+
git_to = git_ref
|
31
|
+
end
|
32
|
+
opts.on('-v', '--version', 'Display version') do
|
33
|
+
puts "#{File.basename($0)} v#{SemVerComponents::VERSION}"
|
34
|
+
exit 0
|
35
|
+
end
|
36
|
+
end.parse!
|
37
|
+
|
38
|
+
raise "Unknown parameters: #{ARGV.join(' ')}" unless ARGV.empty?
|
39
|
+
|
40
|
+
local_git = SemVerComponents::LocalGit.new(git_repo, git_from, git_to)
|
41
|
+
commits_info = local_git.analyze_commits
|
42
|
+
|
43
|
+
output_plugins[output].new(local_git).process(commits_info)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'git'
|
2
|
+
|
3
|
+
module SemVerComponents
|
4
|
+
|
5
|
+
class LocalGit
|
6
|
+
|
7
|
+
attr_reader *%i[git_from git_to git]
|
8
|
+
|
9
|
+
# Constructor
|
10
|
+
#
|
11
|
+
# Parameters::
|
12
|
+
# * *git_repo* (String): The git repository to analyze
|
13
|
+
# * *git_from* (String or nil): The git from ref
|
14
|
+
# * *git_to* (String): The git to ref
|
15
|
+
def initialize(git_repo, git_from, git_to)
|
16
|
+
@git_repo = git_repo
|
17
|
+
@git_from = git_from
|
18
|
+
@git_to = git_to
|
19
|
+
@git = Git.open(@git_repo)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get full the git log.
|
23
|
+
# Keep a cache of it.
|
24
|
+
#
|
25
|
+
# Result::
|
26
|
+
# * Array< Git::Object::Commit >: Full git log
|
27
|
+
def git_log
|
28
|
+
@git_log = @git.log(nil) unless defined?(@git_log)
|
29
|
+
@git_log
|
30
|
+
end
|
31
|
+
|
32
|
+
# Semantically analyze commits.
|
33
|
+
#
|
34
|
+
# Result::
|
35
|
+
# * Array< Hash<Symbol, Object> >: The commits information:
|
36
|
+
# * *components_bump_levels* (Hash<String or nil, Integer>): Set of bump levels (0: patch, 1: minor, 2: major) per component name (nil for global)
|
37
|
+
# * *commit* (Git::Object::Commit): Corresponding git commit
|
38
|
+
def analyze_commits
|
39
|
+
git_log.between(git_from.nil? ? git_log.last.sha : git_from, git_to).map do |git_commit|
|
40
|
+
# Analyze the message
|
41
|
+
# Always consider a minimum of global patch bump per commit.
|
42
|
+
components_bump_levels = { nil => [0] }
|
43
|
+
git_commit.message.scan(/\[([^\]]+)\]/).flatten(1).each do |commit_label|
|
44
|
+
commit_type, component = commit_label =~ /^(.+)\((.+)\)$/ ? [$1, $2] : [commit_label, nil]
|
45
|
+
components_bump_levels[component] = [] unless components_bump_levels.key?(component)
|
46
|
+
components_bump_levels[component] <<
|
47
|
+
case commit_type.downcase
|
48
|
+
when 'feat', 'feature'
|
49
|
+
1
|
50
|
+
when 'break', 'breaking'
|
51
|
+
2
|
52
|
+
else
|
53
|
+
0
|
54
|
+
end
|
55
|
+
end
|
56
|
+
{
|
57
|
+
commit: git_commit,
|
58
|
+
components_bump_levels: Hash[components_bump_levels.map { |component, component_bump_levels| [component, component_bump_levels.max] }]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module SemVerComponents
|
2
|
+
|
3
|
+
module Outputs
|
4
|
+
|
5
|
+
class Info < Output
|
6
|
+
|
7
|
+
# Process commits info
|
8
|
+
#
|
9
|
+
# Parameters::
|
10
|
+
# * *commits_info* (Array< Hash<Symbol, Object> >): List of commits info:
|
11
|
+
# * *components_bump_levels* (Hash<String or nil, Integer>): Set of bump levels (0: patch, 1: minor, 2: major) per component name (nil for global)
|
12
|
+
# * *commit* (Git::Object::Commit): Corresponding git commit
|
13
|
+
def process(commits_info)
|
14
|
+
# Display bump levels per component
|
15
|
+
commits_info.inject({}) do |components_bump_levels, commit_info|
|
16
|
+
components_bump_levels.merge(commit_info[:components_bump_levels]) do |_component, bump_level_1, bump_level_2|
|
17
|
+
[bump_level_1, bump_level_2].max
|
18
|
+
end
|
19
|
+
end.each do |component, bump_level|
|
20
|
+
puts "#{component.nil? ? 'Global' : component}: Bump #{
|
21
|
+
case bump_level
|
22
|
+
when 0
|
23
|
+
'patch'
|
24
|
+
when 1
|
25
|
+
'minor'
|
26
|
+
when 2
|
27
|
+
'major'
|
28
|
+
else
|
29
|
+
raise "Invalid bump level: #{bump_level}"
|
30
|
+
end
|
31
|
+
} version"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module SemVerComponents
|
2
|
+
|
3
|
+
module Outputs
|
4
|
+
|
5
|
+
class SemanticReleaseAnalyze < Output
|
6
|
+
|
7
|
+
# Process commits info
|
8
|
+
#
|
9
|
+
# Parameters::
|
10
|
+
# * *commits_info* (Array< Hash<Symbol, Object> >): List of commits info:
|
11
|
+
# * *components_bump_levels* (Hash<String or nil, Integer>): Set of bump levels (0: patch, 1: minor, 2: major) per component name (nil for global)
|
12
|
+
# * *commit* (Git::Object::Commit): Corresponding git commit
|
13
|
+
def process(commits_info)
|
14
|
+
bump_level = commits_info.map { |commit_info| commit_info[:components_bump_levels].values }.flatten(1).max
|
15
|
+
puts(
|
16
|
+
case bump_level
|
17
|
+
when 0
|
18
|
+
'patch'
|
19
|
+
when 1
|
20
|
+
'minor'
|
21
|
+
when 2
|
22
|
+
'major'
|
23
|
+
else
|
24
|
+
raise "Invalid bump level: #{bump_level}"
|
25
|
+
end
|
26
|
+
)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module SemVerComponents
|
4
|
+
|
5
|
+
module Outputs
|
6
|
+
|
7
|
+
class SemanticReleaseGenerateNotes < Output
|
8
|
+
|
9
|
+
# Process commits info
|
10
|
+
#
|
11
|
+
# Parameters::
|
12
|
+
# * *commits_info* (Array< Hash<Symbol, Object> >): List of commits info:
|
13
|
+
# * *components_bump_levels* (Hash<String or nil, Integer>): Set of bump levels (0: patch, 1: minor, 2: major) per component name (nil for global)
|
14
|
+
# * *commit* (Git::Object::Commit): Corresponding git commit
|
15
|
+
def process(commits_info)
|
16
|
+
# Compute new version
|
17
|
+
new_version =
|
18
|
+
if @local_git.git_from.nil?
|
19
|
+
'0.0.1'
|
20
|
+
elsif @local_git.git_from =~ /^v(\d+)\.(\d+)\.(\d+)$/
|
21
|
+
major = Integer($1)
|
22
|
+
minor = Integer($2)
|
23
|
+
patch = Integer($3)
|
24
|
+
bump_level = commits_info.map { |commit_info| commit_info[:components_bump_levels].values }.flatten(1).max
|
25
|
+
case bump_level
|
26
|
+
when 0
|
27
|
+
patch += 1
|
28
|
+
when 1
|
29
|
+
minor += 1
|
30
|
+
patch = 0
|
31
|
+
when 2
|
32
|
+
major += 1
|
33
|
+
minor = 0
|
34
|
+
patch = 0
|
35
|
+
else
|
36
|
+
raise "Invalid bump level: #{bump_level}"
|
37
|
+
end
|
38
|
+
"#{major}.#{minor}.#{patch}"
|
39
|
+
else
|
40
|
+
raise "Can't generate release notes from a git ref that is not a semantic release (#{@local_git.git_from})"
|
41
|
+
end
|
42
|
+
git_url = @local_git.git.remote('origin').url
|
43
|
+
git_url = git_url[0..-5] if git_url.end_with?('.git')
|
44
|
+
# Group commits per bump level, per component
|
45
|
+
# Hash< String or nil, Hash< Integer, Array<Git::Object::Commit> >
|
46
|
+
commits_per_component = {}
|
47
|
+
# Also reference commits to be ignored: commits that are part of a merge commit
|
48
|
+
merged_commits = []
|
49
|
+
commits_info.each do |commit_info|
|
50
|
+
git_commit = commit_info[:commit]
|
51
|
+
commit_info[:components_bump_levels].each do |component, bump_level|
|
52
|
+
commits_per_component[component] = {} unless commits_per_component.key?(component)
|
53
|
+
commits_per_component[component][bump_level] = [] unless commits_per_component[component].key?(bump_level)
|
54
|
+
commits_per_component[component][bump_level] << git_commit
|
55
|
+
end
|
56
|
+
# In the case of a merge commit, reference all commits that are part of this merge commit, directly from the graph
|
57
|
+
merged_commits.concat(@local_git.git_log.between(@local_git.git.merge_base(*git_commit.parents.map(&:sha)).first.sha, git_commit.sha)[1..-1].map(&:sha)) if git_commit.parents.size > 1
|
58
|
+
end
|
59
|
+
puts "# [v#{new_version}](#{git_url}/compare/#{@local_git.git_from}...v#{new_version}) (#{Time.now.utc.strftime('%F %T')})"
|
60
|
+
puts
|
61
|
+
commits_per_component.sort_by { |component, _component_info| component || '' }.each do |(component, component_info)|
|
62
|
+
puts "## #{component.nil? ? 'Global changes' : "Changes for #{component}"}\n" if commits_per_component.size > 1 || !component.nil?
|
63
|
+
component_info.each do |bump_level, commits|
|
64
|
+
puts "### #{
|
65
|
+
case bump_level
|
66
|
+
when 0
|
67
|
+
'Patches'
|
68
|
+
when 1
|
69
|
+
'Features'
|
70
|
+
when 2
|
71
|
+
'Breaking changes'
|
72
|
+
else
|
73
|
+
raise "Invalid bump level: #{bump_level}"
|
74
|
+
end
|
75
|
+
}"
|
76
|
+
puts
|
77
|
+
# Gather an ordered set of commit lines (with the corresponding commit sha) in order to not duplicate the info when there are merge commits
|
78
|
+
# Hash< String, String >
|
79
|
+
commit_lines = {}
|
80
|
+
commits.each do |commit|
|
81
|
+
# Don't put merged commits as we consider the changelog should contain the merge commit comment.
|
82
|
+
next if merged_commits.include?(commit.sha)
|
83
|
+
message_lines = commit.message.split("\n")
|
84
|
+
commit_line = message_lines.first
|
85
|
+
if commit_line =~ /^Merge pull request .+$/
|
86
|
+
# Consider the next line as commit line
|
87
|
+
next_line = message_lines[1..-1].join("\n").strip.split("\n").first
|
88
|
+
commit_line = next_line unless next_line.nil?
|
89
|
+
end
|
90
|
+
commit_lines[commit_line] = commit.sha
|
91
|
+
end
|
92
|
+
commit_lines.each do |commit_line, commit_sha|
|
93
|
+
puts "* [#{commit_line}](#{git_url}/commit/#{commit_sha})"
|
94
|
+
end
|
95
|
+
puts
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SemVerComponents
|
2
|
+
|
3
|
+
class Plugins
|
4
|
+
|
5
|
+
# Constructor
|
6
|
+
#
|
7
|
+
# Parameters::
|
8
|
+
# * *plugins_type* (Symbol): Plugins type we are parsing
|
9
|
+
def initialize(plugins_type)
|
10
|
+
@plugins_type = plugins_type
|
11
|
+
@plugins = Hash[Dir.glob("#{__dir__}/#{plugins_type}/*.rb").map do |plugin_file|
|
12
|
+
plugin_name = File.basename(plugin_file, '.rb').to_sym
|
13
|
+
require "#{__dir__}/#{plugins_type}/#{plugin_name}.rb"
|
14
|
+
[
|
15
|
+
plugin_name,
|
16
|
+
SemVerComponents.
|
17
|
+
const_get(plugins_type.to_s.split('_').collect(&:capitalize).join.to_sym).
|
18
|
+
const_get(plugin_name.to_s.split('_').collect(&:capitalize).join.to_sym)
|
19
|
+
]
|
20
|
+
end]
|
21
|
+
end
|
22
|
+
|
23
|
+
# List available plugin names
|
24
|
+
#
|
25
|
+
# Result::
|
26
|
+
# * Array<Symbol>: Available plugin names
|
27
|
+
def list
|
28
|
+
@plugins.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
# Get a plugin class
|
32
|
+
#
|
33
|
+
# Parameters::
|
34
|
+
# * *plugin_name* (Symbol): The plugin name
|
35
|
+
# Result::
|
36
|
+
# * Class: The corresponding plugin class
|
37
|
+
def [](plugin_name)
|
38
|
+
@plugins[plugin_name]
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sem_ver_components
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Muriel Salvan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-01-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: git
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.10'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.10'
|
41
|
+
description: Tools helping in maintaining semantic versioning at a components level
|
42
|
+
instead of a global package-only level.
|
43
|
+
email:
|
44
|
+
- muriel@x-aeon.com
|
45
|
+
executables:
|
46
|
+
- sem_ver_git
|
47
|
+
extensions: []
|
48
|
+
extra_rdoc_files: []
|
49
|
+
files:
|
50
|
+
- bin/sem_ver_git
|
51
|
+
- lib/sem_ver_components/local_git.rb
|
52
|
+
- lib/sem_ver_components/output.rb
|
53
|
+
- lib/sem_ver_components/outputs/info.rb
|
54
|
+
- lib/sem_ver_components/outputs/semantic_release_analyze.rb
|
55
|
+
- lib/sem_ver_components/outputs/semantic_release_generate_notes.rb
|
56
|
+
- lib/sem_ver_components/plugins.rb
|
57
|
+
- lib/sem_ver_components/version.rb
|
58
|
+
homepage: https://github.com/Muriel-Salvan/sem_ver_components
|
59
|
+
licenses:
|
60
|
+
- BSD-3-Clause
|
61
|
+
metadata: {}
|
62
|
+
post_install_message:
|
63
|
+
rdoc_options: []
|
64
|
+
require_paths:
|
65
|
+
- lib
|
66
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
requirements: []
|
77
|
+
rubygems_version: 3.1.2
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Apply semantic versioning to various components of a same package
|
81
|
+
test_files: []
|