git-releaselog 0.6.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 +7 -0
- data/bin/git-releaselog +51 -0
- data/lib/changelog.rb +116 -0
- data/lib/changelog_helpers.rb +123 -0
- data/lib/git-releaselog.rb +105 -0
- metadata +97 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 567008c8838f03f01eef3ab47173418335f66749
|
4
|
+
data.tar.gz: 5a92522b789dfe71c4418de08d2afada0687e02a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5c04a68c551ec6bf27e8c3cd611686656fb65608a91b1af0580c9de9938b18ed6f9b555841dc8663180843401d6e7e461bacd85f4cbc57a60dbe8ad9bd13a84c
|
7
|
+
data.tar.gz: 73d5c5d3251ce70bfedb2f8e8bae6f31c6000d14be7552077ba2cb1ef362abd6324de98be3cce9885be841345907ef759077b2a17a285ccb8653eb3e0fbf0523
|
data/bin/git-releaselog
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require "git-releaselog"
|
3
|
+
require "docopt"
|
4
|
+
|
5
|
+
doc = <<DOCOPT
|
6
|
+
A script to generate release-notes from a git repository
|
7
|
+
|
8
|
+
Commit messages are parsed for lines of the following format:
|
9
|
+
|
10
|
+
`* fix: [<scope(optional)>] <description>`
|
11
|
+
`* feat: [<scope(optional)>] <description>`
|
12
|
+
`* gui: [<scope(optional)>] <description>`
|
13
|
+
`* refactor: [<scope(optional)>] <description>`
|
14
|
+
|
15
|
+
The descriptions are collected and printed as releaselog.
|
16
|
+
|
17
|
+
Usage:
|
18
|
+
#{__FILE__} [--complete][--debug][--format=<format>][--scope=<scope>]
|
19
|
+
#{__FILE__} <from-ref> [--debug][--format=<format>][--scope=<scope>]
|
20
|
+
#{__FILE__} <from-ref> <to-ref> [--debug][--format=<format>][--scope=<scope>]
|
21
|
+
#{__FILE__} -h | --help
|
22
|
+
#{__FILE__} --version
|
23
|
+
|
24
|
+
Options:
|
25
|
+
from-ref Git-Ref from which should the log be generated. Can be a tag-name or commit-hash. Will default to the latest tag
|
26
|
+
to-ref Git-Ref to which the log should be generated. Can be a tag-name or commit-hash. Has to be newer than `from-ref`. Will default to head
|
27
|
+
--scope=<scope> The scope. Will only include releaselog entries with that scope or without scope.
|
28
|
+
--format=<format> The format in which the output should be generated. Currently supports 'slack' and 'md' (for markdown)
|
29
|
+
--complete Traverses the whole git history and generates a releaselog for all tags
|
30
|
+
-h --help Show this screen.
|
31
|
+
--version Show version.
|
32
|
+
--debug Show debug output
|
33
|
+
DOCOPT
|
34
|
+
|
35
|
+
# Parse Commandline Arguments
|
36
|
+
begin
|
37
|
+
args = Docopt::docopt(doc, version: '0.6.0')
|
38
|
+
rescue Docopt::Exit => e
|
39
|
+
puts e.message
|
40
|
+
exit
|
41
|
+
end
|
42
|
+
|
43
|
+
puts Releaselog.generate_releaselog(
|
44
|
+
repo_path: ".",
|
45
|
+
from_ref: args["<from-ref>"],
|
46
|
+
to_ref: args["<to-ref>"],
|
47
|
+
scope: args["--scope"],
|
48
|
+
format: args["--format"] || "slack",
|
49
|
+
generate_complete: args["--complete"],
|
50
|
+
verbose: (args["--debug"] ? true : false)
|
51
|
+
)
|
data/lib/changelog.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
# A class for representing a changelog consisting of several changes
|
2
|
+
# over a certain timespan (between two commits)
|
3
|
+
class Changelog
|
4
|
+
def initialize(changes, tag_from = nil, tag_to = nil, from_commit = nil, to_commit = nil)
|
5
|
+
@changes_fix = changes.select { |c| c.type == Change::FIX }
|
6
|
+
@changes_feat = changes.select { |c| c.type == Change::FEAT }
|
7
|
+
@changes_gui = changes.select { |c| c.type == Change::GUI }
|
8
|
+
@changes_refactor = changes.select { |c| c.type == Change::REFACTOR }
|
9
|
+
@tag_from = tag_from
|
10
|
+
@tag_to = tag_to
|
11
|
+
@commit_from = from_commit
|
12
|
+
@commit_to = to_commit
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns a hash of the changes.
|
16
|
+
# The changes are grouped by change type into `fix`, `feature`, `gui`, `refactor`
|
17
|
+
# Each type is a list of changes where each change is the note of that change
|
18
|
+
def changes
|
19
|
+
{
|
20
|
+
fix: @changes_fix.map(&:note),
|
21
|
+
feature: @changes_feat.map(&:note),
|
22
|
+
gui: @changes_gui.map(&:note),
|
23
|
+
refactor: @changes_refactor.map(&:note)
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
# Display tag information about the tag that the changelog is created for
|
28
|
+
def tag_info
|
29
|
+
if @tag_to && @tag_to.name
|
30
|
+
yield("#{@tag_to.name}")
|
31
|
+
else
|
32
|
+
yield("Unreleased")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Display tinformation about the commit the changelog is created for
|
37
|
+
def commit_info
|
38
|
+
if @commit_to
|
39
|
+
yield(@commit_to.time.strftime("%d.%m.%Y"))
|
40
|
+
else
|
41
|
+
yield("")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Format each section from #sections.
|
46
|
+
#
|
47
|
+
# section_changes ... changes in the format of { section_1: [changes...], section_2: [changes...]}
|
48
|
+
# header_style ... is called for styling the header of each section
|
49
|
+
# entry_style ... is called for styling each item of a section
|
50
|
+
def sections(section_changes, header_style, entry_style)
|
51
|
+
str = ""
|
52
|
+
section_changes.each do |section_category, section_changes|
|
53
|
+
str << section(
|
54
|
+
section_changes,
|
55
|
+
section_category.to_s,
|
56
|
+
entry_style,
|
57
|
+
header_style
|
58
|
+
)
|
59
|
+
end
|
60
|
+
str
|
61
|
+
end
|
62
|
+
|
63
|
+
# Format a specific section.
|
64
|
+
#
|
65
|
+
# section_changes ... changes in the format of { section_1: [changes...], section_2: [changes...]}
|
66
|
+
# header ... header of the section
|
67
|
+
# entry_style ... is called for styling each item of a section
|
68
|
+
# header_style ... optional, since styled header can be passed directly; is called for styling the header of the section
|
69
|
+
def section(section_changes, header, entry_style, header_style = nil)
|
70
|
+
return "" unless section_changes.size > 0
|
71
|
+
str = ""
|
72
|
+
|
73
|
+
unless header.empty?
|
74
|
+
if header_style
|
75
|
+
str << header_style.call(header)
|
76
|
+
else
|
77
|
+
str << header
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
section_changes.each_with_index do |e, i|
|
82
|
+
str << entry_style.call(e, i)
|
83
|
+
end
|
84
|
+
str
|
85
|
+
end
|
86
|
+
|
87
|
+
# Render the Changelog with Slack Formatting
|
88
|
+
def to_slack
|
89
|
+
str = ""
|
90
|
+
|
91
|
+
str << tag_info { |t| t }
|
92
|
+
str << commit_info { |ci| " (_#{ci}_)\n" }
|
93
|
+
str << sections(
|
94
|
+
changes,
|
95
|
+
-> (header) { "*#{header.capitalize}*\n" },
|
96
|
+
-> (field, _index) { "\t- #{field}\n" }
|
97
|
+
)
|
98
|
+
|
99
|
+
str
|
100
|
+
end
|
101
|
+
|
102
|
+
# Render the Changelog with Markdown Formatting
|
103
|
+
def to_md
|
104
|
+
str = ""
|
105
|
+
|
106
|
+
str << tag_info { |t| "## #{t}" }
|
107
|
+
str << commit_info { |ci| " (_#{ci}_)" }
|
108
|
+
str << sections(
|
109
|
+
changes,
|
110
|
+
-> (header) { "\n*#{header.capitalize}*\n" },
|
111
|
+
-> (field, _index) { "* #{field}\n" }
|
112
|
+
)
|
113
|
+
|
114
|
+
str
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
#
|
2
|
+
# Helper Functions for git-changelog script
|
3
|
+
#
|
4
|
+
|
5
|
+
# A class for representing a change
|
6
|
+
# A change can have a type (fix or feature) and a note describing the change
|
7
|
+
class Change
|
8
|
+
FIX = 1
|
9
|
+
FEAT = 2
|
10
|
+
GUI = 3
|
11
|
+
REFACTOR = 4
|
12
|
+
|
13
|
+
TOKEN_FIX = "* fix:"
|
14
|
+
TOKEN_FEAT = "* feat:"
|
15
|
+
TOKEN_GUI = "* gui:"
|
16
|
+
TOKEN_REFACTOR = "* refactor:"
|
17
|
+
|
18
|
+
def initialize(type, note)
|
19
|
+
@type = type
|
20
|
+
@note = note.strip
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
@type
|
25
|
+
end
|
26
|
+
|
27
|
+
def note
|
28
|
+
@note
|
29
|
+
end
|
30
|
+
|
31
|
+
# Parse a single line as a `Change` entry
|
32
|
+
# If the line is formatte correctly as a change entry, a corresponding `Change` object will be created and returned,
|
33
|
+
# otherwise, nil will be returned.
|
34
|
+
#
|
35
|
+
# The additional scope can be used to skip changes of another scope. Changes without scope will always be included.
|
36
|
+
def self.parse(line, scope = nil)
|
37
|
+
if line.start_with? Change::TOKEN_FEAT
|
38
|
+
self.new(Change::FEAT, line.split(Change::TOKEN_FEAT).last).check_scope(scope)
|
39
|
+
elsif line.start_with? Change::TOKEN_FIX
|
40
|
+
self.new(Change::FIX, line.split(Change::TOKEN_FIX).last).check_scope(scope)
|
41
|
+
elsif line.start_with? Change::TOKEN_GUI
|
42
|
+
self.new(Change::GUI, line.split(Change::TOKEN_GUI).last).check_scope(scope)
|
43
|
+
elsif line.start_with? Change::TOKEN_REFACTOR
|
44
|
+
self.new(Change::REFACTOR, line.split(Change::TOKEN_REFACTOR).last).check_scope(scope)
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Checks the scope of the `Change` and the change out if the scope does not match.
|
51
|
+
def check_scope(scope = nil)
|
52
|
+
# If no scope is requested or the change has no scope include this change unchanged
|
53
|
+
return self unless scope
|
54
|
+
change_scope = /^\s*\[\w+\]/.match(@note)
|
55
|
+
return self unless change_scope
|
56
|
+
|
57
|
+
# change_scope is a string of format `[scope]`, need to strip the `[]` to compare the scope
|
58
|
+
if change_scope[0][1..-2] == scope
|
59
|
+
# Change has the scope that is requested, strip the whole scope scope from the change note
|
60
|
+
@note = change_scope.post_match.strip
|
61
|
+
return self
|
62
|
+
else
|
63
|
+
# Change has a different scope than requested
|
64
|
+
return nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# check if the given refString (tag name or commit-hash) exists in the repo
|
70
|
+
def commit(repo, refString, logger)
|
71
|
+
return unless refString != nil
|
72
|
+
begin
|
73
|
+
repo.lookup(refString)
|
74
|
+
rescue Rugged::OdbError => e
|
75
|
+
puts ("Commit `#{refString}` does not exist in Repo")
|
76
|
+
logger.error(e.message)
|
77
|
+
exit
|
78
|
+
rescue Exception => e
|
79
|
+
puts ("`#{refString}` is not a valid OID")
|
80
|
+
logger.error(e.message)
|
81
|
+
exit
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns the most recent tag
|
86
|
+
def latestTagID(repo, logger)
|
87
|
+
return nil unless repo.tags.count > 0
|
88
|
+
sorted_tags = repo.tags.sort { |t1, t2| t1.target.time <=> t2.target.time }
|
89
|
+
sorted_tags.last
|
90
|
+
end
|
91
|
+
|
92
|
+
# Returns the tag with the given name (if exists)
|
93
|
+
def tagWithName(repo, name)
|
94
|
+
tags = repo.tags.select { |t| t.name == name }
|
95
|
+
return tags.first unless tags.count < 1
|
96
|
+
end
|
97
|
+
|
98
|
+
# Parses a commit message and returns an array of Changes
|
99
|
+
def parseCommit(commit, scope, logger)
|
100
|
+
logger.debug("Parsing Commit #{commit.oid}")
|
101
|
+
# Sepaerate into lines, remove whitespaces and filter out empty lines
|
102
|
+
lines = commit.message.lines.map(&:strip).reject(&:empty?)
|
103
|
+
# Parse the lines
|
104
|
+
lines.map{|line| Change.parse(line, scope)}.reject(&:nil?)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Searches the commit log messages of all commits between `commit_from` and `commit_to` for changes
|
108
|
+
def searchGitLog(repo, commit_from, commit_to, scope, logger)
|
109
|
+
logger.info("Traversing git tree from commit #{commit_from.oid} to commit #{commit_to && commit_to.oid}")
|
110
|
+
|
111
|
+
# Initialize a walker that walks through the commits from the <from-commit> to the <to-commit>
|
112
|
+
walker = Rugged::Walker.new(repo)
|
113
|
+
walker.sorting(Rugged::SORT_DATE)
|
114
|
+
walker.push(commit_to)
|
115
|
+
commit_from.parents.each do |parent|
|
116
|
+
walker.hide(parent)
|
117
|
+
end unless commit_from == nil
|
118
|
+
|
119
|
+
# Parse all commits and extract changes
|
120
|
+
changes = walker.map{ |c| parseCommit(c, scope, logger)}.reduce(:+) || []
|
121
|
+
logger.debug("Found #{changes.count} changes")
|
122
|
+
return changes
|
123
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require "rugged"
|
2
|
+
require "changelog_helpers"
|
3
|
+
require "changelog"
|
4
|
+
require "logger"
|
5
|
+
|
6
|
+
class Releaselog
|
7
|
+
def self.generate_releaselog(options = {})
|
8
|
+
repo_path = options.fetch(:repo_path, '.')
|
9
|
+
from_ref_name = options.fetch(:from_ref, nil)
|
10
|
+
to_ref_name = options.fetch(:to_ref, nil)
|
11
|
+
scope = options.fetch(:scope, nil)
|
12
|
+
format = options.fetch(:format, 'slack')
|
13
|
+
generate_complete = options.fetch(:generate_complete, false)
|
14
|
+
verbose = options.fetch(:verbose, false)
|
15
|
+
|
16
|
+
# Initialize Logger
|
17
|
+
logger = Logger.new(STDOUT)
|
18
|
+
logger.level = verbose ? Logger::DEBUG : Logger::ERROR
|
19
|
+
|
20
|
+
# Initialize Repo
|
21
|
+
begin
|
22
|
+
repo = Rugged::Repository.discover(repo_path)
|
23
|
+
rescue Rugged::OSError => e
|
24
|
+
puts ("Current directory is not a git repo")
|
25
|
+
logger.error(e.message)
|
26
|
+
exit
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find if we're operating on tags
|
30
|
+
from_ref = tagWithName(repo, from_ref_name)
|
31
|
+
to_ref = tagWithName(repo, to_ref_name)
|
32
|
+
latest_tag = latestTagID(repo, logger)
|
33
|
+
|
34
|
+
if from_ref
|
35
|
+
logger.info("Found Tag #{from_ref.name} to start from")
|
36
|
+
end
|
37
|
+
|
38
|
+
if to_ref
|
39
|
+
logger.info("Found Tag #{to_ref.name} to end at")
|
40
|
+
end
|
41
|
+
|
42
|
+
if latest_tag
|
43
|
+
logger.info("Latest Tag found: #{latest_tag.name}")
|
44
|
+
end
|
45
|
+
|
46
|
+
if generate_complete && repo.tags.count > 0
|
47
|
+
sorted_tags = repo.tags.sort { |t1, t2| t1.target.time <=> t2.target.time }
|
48
|
+
changeLogs = []
|
49
|
+
sorted_tags.each_with_index do |tag, index|
|
50
|
+
if index == 0
|
51
|
+
# First Interval: Generate from start of Repo to the first Tag
|
52
|
+
changes = searchGitLog(repo, tag.target, repo.head.target, scope, logger)
|
53
|
+
logger.info("First Tag: #{tag.name}: #{changes.count} changes")
|
54
|
+
changeLogs += [Changelog.new(changes, tag, nil, nil, nil)]
|
55
|
+
else
|
56
|
+
# Normal interval: Generate from one Tag to the next Tag
|
57
|
+
previousTag = sorted_tags[index-1]
|
58
|
+
changes = searchGitLog(repo, tag.target, previousTag.target, scope, logger)
|
59
|
+
logger.info("Tag #{previousTag.name} to #{tag.name}: #{changes.count} changes")
|
60
|
+
changeLogs += [Changelog.new(changes, tag, previousTag, nil, nil)]
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if sorted_tags.count > 0
|
65
|
+
lastTag = sorted_tags.last
|
66
|
+
# Last Interval: Generate from last Tag to HEAD
|
67
|
+
changes = searchGitLog(repo, repo.head.target, lastTag.target, scope, logger)
|
68
|
+
logger.info("Tag #{lastTag.name} to HEAD: #{changes.count} changes")
|
69
|
+
changeLogs += [Changelog.new(changes, nil, lastTag, nil, nil)]
|
70
|
+
end
|
71
|
+
|
72
|
+
# Print the changelog
|
73
|
+
if format == "md"
|
74
|
+
changeLogs.reverse.map { |log| "#{log.to_md}\n" }
|
75
|
+
elsif format == "slack"
|
76
|
+
changeLogs.reduce("") { |log, version| log + "1) #{version.to_slack}\n" }
|
77
|
+
else
|
78
|
+
logger.error("Unknown Format: `#{format}`")
|
79
|
+
end
|
80
|
+
else
|
81
|
+
# From which commit should the log be followed? Will default to the latest tag
|
82
|
+
commit_from = (from_ref && from_ref.target) || commit(repo, from_ref, logger) || latest_tag && (latest_tag.target)
|
83
|
+
|
84
|
+
# To which commit should the log be followed? Will default to HEAD
|
85
|
+
commit_to = (to_ref && to_ref.target) || commit(repo, to_ref, logger) || repo.head.target
|
86
|
+
|
87
|
+
|
88
|
+
changes = searchGitLog(repo, commit_from, commit_to, scope, logger)
|
89
|
+
# Create the changelog
|
90
|
+
log = Changelog.new(changes, from_ref, to_ref || latest_tag, commit_from, commit_to)
|
91
|
+
|
92
|
+
# Print the changelog
|
93
|
+
case format
|
94
|
+
when "md"
|
95
|
+
log.to_md
|
96
|
+
when "slack"
|
97
|
+
log.to_slack
|
98
|
+
when "raw"
|
99
|
+
log
|
100
|
+
else
|
101
|
+
logger.error("Unknown Format: `#{format}`")
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
metadata
ADDED
@@ -0,0 +1,97 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: git-releaselog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.6.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Markus Chmelar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-09-15 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: docopt
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.5'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.5.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0.5'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 0.5.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: rugged
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 0.23.0
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.23.0
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.3.0
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: 3.3.0
|
61
|
+
description: Write your releaselog as part of your usual commit messages. This tool
|
62
|
+
generates a useful releaselog from marked lines in your git commit messages
|
63
|
+
email: markus.chmelar@innovaptor.com
|
64
|
+
executables:
|
65
|
+
- git-releaselog
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- bin/git-releaselog
|
70
|
+
- lib/changelog.rb
|
71
|
+
- lib/changelog_helpers.rb
|
72
|
+
- lib/git-releaselog.rb
|
73
|
+
homepage: https://github.com/iv-mexx/git-releaselog
|
74
|
+
licenses:
|
75
|
+
- MIT
|
76
|
+
metadata: {}
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubyforge_project:
|
93
|
+
rubygems_version: 2.4.8
|
94
|
+
signing_key:
|
95
|
+
specification_version: 4
|
96
|
+
summary: Generate a releaselog from a git repository
|
97
|
+
test_files: []
|