Compare commits
64 Commits
1.4.0
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4664e389d | ||
|
|
0e352886ea | ||
|
|
01e30d1132 | ||
|
|
a72ad326c4 | ||
|
|
da4bc8952d | ||
|
|
c8c633a65d | ||
|
|
d2cd79e710 | ||
|
|
8f4a931256 | ||
|
|
8e5e7de71d | ||
|
|
5aa4352acf | ||
|
|
171e536e76 | ||
|
|
757f6d40b4 | ||
|
|
c3b9455dfd | ||
|
|
134c18ba06 | ||
|
|
4ffb493787 | ||
|
|
b10707b259 | ||
|
|
3f076b3069 | ||
|
|
9a24eb1cb3 | ||
|
|
cf7ae57e3d | ||
|
|
3c289de79b | ||
|
|
4a96a7c0c9 | ||
|
|
7f696b6b09 | ||
|
|
536b39c961 | ||
|
|
944adc92cd | ||
|
|
3fc3e3e143 | ||
|
|
154ac01226 | ||
|
|
e2548e049a | ||
|
|
1ab6f2a5eb | ||
|
|
00d4242fa6 | ||
|
|
eeb03b031f | ||
|
|
a5d43b3d23 | ||
|
|
66152e59de | ||
|
|
ec7c98758c | ||
|
|
d0defc4c9b | ||
|
|
7cfb182a82 | ||
|
|
9a3c068615 | ||
|
|
0b04797171 | ||
|
|
659ef2fef5 | ||
|
|
e9cb010f09 | ||
|
|
1ee1dfd50f | ||
|
|
8a3ff1b799 | ||
|
|
7c9edcfedb | ||
|
|
dd230dd050 | ||
|
|
7a2d296f67 | ||
|
|
bd1bebc8c4 | ||
|
|
bbeebf7e28 | ||
|
|
583076f32c | ||
|
|
0ff48a3dba | ||
|
|
50ba2695fb | ||
|
|
866c9f95d3 | ||
|
|
7b356bf01a | ||
|
|
c67cbb31f2 | ||
|
|
149fba47ed | ||
|
|
819bcf5948 | ||
|
|
9cd7d64fc4 | ||
|
|
6448de26fa | ||
|
|
f3e484b508 | ||
|
|
22258b88b9 | ||
|
|
e829be38bd | ||
|
|
ba2da3e786 | ||
|
|
5e4473ffc9 | ||
|
|
cebbc80a29 | ||
|
|
b046235716 | ||
|
|
a0dce72151 |
36
.overcommit.yml
Normal file
36
.overcommit.yml
Normal file
@@ -0,0 +1,36 @@
|
||||
# Use this file to configure the Overcommit hooks you wish to use. This will
|
||||
# extend the default configuration defined in:
|
||||
# https://github.com/brigade/overcommit/blob/master/config/default.yml
|
||||
#
|
||||
# At the topmost level of this YAML file is a key representing type of hook
|
||||
# being run (e.g. pre-commit, commit-msg, etc.). Within each type you can
|
||||
# customize each hook, such as whether to only run it on certain files (via
|
||||
# `include`), whether to only display output if it fails (via `quiet`), etc.
|
||||
#
|
||||
# For a complete list of hooks, see:
|
||||
# https://github.com/brigade/overcommit/tree/master/lib/overcommit/hook
|
||||
#
|
||||
# For a complete list of options that you can use to customize hooks, see:
|
||||
# https://github.com/brigade/overcommit#configuration
|
||||
#
|
||||
# Uncomment the following lines to make the configuration take effect.
|
||||
|
||||
PreCommit:
|
||||
RuboCop:
|
||||
enabled: true
|
||||
#command: ['bundle', 'exec', 'rubocop']
|
||||
on_warn: fail # Treat all warnings as failures
|
||||
#
|
||||
# TrailingWhitespace:
|
||||
# exclude:
|
||||
# - '**/db/structure.sql' # Ignore trailing whitespace in generated files
|
||||
#
|
||||
#PostCheckout:
|
||||
# ALL: # Special hook name that customizes all hooks of this type
|
||||
# quiet: true # Change all post-checkout hooks to only display output on failure
|
||||
#
|
||||
# IndexTags:
|
||||
# enabled: true # Generate a tags file with `ctags` each time HEAD changes
|
||||
CommitMsg:
|
||||
CapitalizedSubject:
|
||||
enabled: false
|
||||
@@ -1,81 +1,52 @@
|
||||
# This configuration was generated by `rubocop --auto-gen-config`
|
||||
# on 2015-04-04 02:29:34 +0300 using RuboCop version 0.29.1.
|
||||
# on 2015-05-25 17:16:04 +0300 using RuboCop version 0.31.0.
|
||||
# The point is for the user to remove these configuration records
|
||||
# one by one as the offenses are removed from the code base.
|
||||
# Note that changes in the inspected code, or installation of new
|
||||
# versions of RuboCop, may require this file to be generated again.
|
||||
|
||||
# Offense count: 21
|
||||
# Offense count: 14
|
||||
Metrics/AbcSize:
|
||||
Max: 71
|
||||
Max: 57
|
||||
|
||||
# Offense count: 2
|
||||
Metrics/BlockNesting:
|
||||
Max: 4
|
||||
|
||||
# Offense count: 2
|
||||
# Offense count: 4
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/ClassLength:
|
||||
Max: 457
|
||||
Max: 182
|
||||
|
||||
# Offense count: 6
|
||||
# Offense count: 1
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 15
|
||||
Max: 7
|
||||
|
||||
# Offense count: 28
|
||||
# Offense count: 22
|
||||
# Configuration parameters: CountComments.
|
||||
Metrics/MethodLength:
|
||||
Max: 118
|
||||
Max: 84
|
||||
|
||||
# Offense count: 5
|
||||
# Offense count: 1
|
||||
Metrics/PerceivedComplexity:
|
||||
Max: 18
|
||||
Max: 8
|
||||
|
||||
# Offense count: 3
|
||||
# Offense count: 2
|
||||
Style/AccessorMethodName:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles.
|
||||
Style/AndOr:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 27
|
||||
# Cop supports --auto-correct.
|
||||
Style/Blocks:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 7
|
||||
# Configuration parameters: IndentWhenRelativeTo, SupportedStyles, IndentOneStep.
|
||||
Style/CaseIndentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 4
|
||||
# Offense count: 8
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 1
|
||||
# Configuration parameters: AllowedVariables.
|
||||
Style/GlobalVars:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 6
|
||||
# Configuration parameters: MinBodyLength.
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 17
|
||||
# Configuration parameters: MaxLineLength.
|
||||
Style/IfUnlessModifier:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 2
|
||||
# Offense count: 1
|
||||
# Configuration parameters: EnforcedStyle, MinBodyLength, SupportedStyles.
|
||||
Style/Next:
|
||||
Enabled: false
|
||||
|
||||
# Offense count: 5
|
||||
# Configuration parameters: MaxSlashes.
|
||||
# Offense count: 3
|
||||
# Cop supports --auto-correct.
|
||||
# Configuration parameters: EnforcedStyle, SupportedStyles, AllowInnerSlashes.
|
||||
Style/RegexpLiteral:
|
||||
Enabled: false
|
||||
|
||||
34
CHANGELOG.md
34
CHANGELOG.md
@@ -1,8 +1,32 @@
|
||||
# Change Log
|
||||
|
||||
## [Unreleased](https://github.com/skywinder/github-changelog-generator/tree/HEAD)
|
||||
## [1.4.1](https://github.com/skywinder/github-changelog-generator/tree/1.4.1) (2015-05-19)
|
||||
|
||||
[Full Changelog](https://github.com/skywinder/github-changelog-generator/compare/1.3.11...HEAD)
|
||||
[Full Changelog](https://github.com/skywinder/github-changelog-generator/compare/1.4.0...1.4.1)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
- Trees/Archives with missing change log notes for the current tag. [\#230](https://github.com/skywinder/github-changelog-generator/issues/230)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- github\_changelog\_generator.rb:220:in ``': No such file or directory - pwd \(Errno::ENOENT\) [\#237](https://github.com/skywinder/github-changelog-generator/issues/237)
|
||||
|
||||
- Doesnot generator changelog [\#235](https://github.com/skywinder/github-changelog-generator/issues/235)
|
||||
|
||||
- Exclude closed \(not merged\) PR's from changelog. [\#69](https://github.com/skywinder/github-changelog-generator/issues/69)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Wrap GitHub requests in function check\_github\_response [\#238](https://github.com/skywinder/github-changelog-generator/pull/238) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
- Add fetch token tests [\#236](https://github.com/skywinder/github-changelog-generator/pull/236) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
- Add future release option [\#231](https://github.com/skywinder/github-changelog-generator/pull/231) ([sildur](https://github.com/sildur))
|
||||
|
||||
## [1.4.0](https://github.com/skywinder/github-changelog-generator/tree/1.4.0) (2015-05-07)
|
||||
|
||||
[Full Changelog](https://github.com/skywinder/github-changelog-generator/compare/1.3.11...1.4.0)
|
||||
|
||||
**Implemented enhancements:**
|
||||
|
||||
@@ -18,6 +42,8 @@
|
||||
|
||||
- Cleanup [\#220](https://github.com/skywinder/github-changelog-generator/pull/220) ([tuexss](https://github.com/tuexss))
|
||||
|
||||
- Implement fetcher class [\#227](https://github.com/skywinder/github-changelog-generator/pull/227) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
- Add coveralls integration [\#223](https://github.com/skywinder/github-changelog-generator/pull/223) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
- Rspec & rubocop integration [\#217](https://github.com/skywinder/github-changelog-generator/pull/217) ([skywinder](https://github.com/skywinder))
|
||||
@@ -60,10 +86,6 @@
|
||||
|
||||
[Full Changelog](https://github.com/skywinder/github-changelog-generator/compare/1.3.6...1.3.8)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Fix `git remote` parsing in case, when script running without parameters inside destination directory [\#61](https://github.com/skywinder/github-changelog-generator/pull/61) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
## [1.3.6](https://github.com/skywinder/github-changelog-generator/tree/1.3.6) (2015-03-05)
|
||||
|
||||
[Full Changelog](https://github.com/skywinder/github-changelog-generator/compare/1.3.5...1.3.6)
|
||||
|
||||
6
Gemfile
6
Gemfile
@@ -1,9 +1,9 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "rake"
|
||||
gem "rake", ">=10.4.2"
|
||||
|
||||
gem "github_api"
|
||||
gem "colorize"
|
||||
gem "github_api", ">=0.12.3"
|
||||
gem "colorize", ">=0.7.7"
|
||||
|
||||
group :test do
|
||||
gem "rspec"
|
||||
|
||||
51
Gemfile.lock
51
Gemfile.lock
@@ -1,24 +1,24 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.3.7)
|
||||
addressable (2.3.8)
|
||||
ast (2.0.0)
|
||||
astrolabe (1.3.0)
|
||||
parser (>= 2.2.0.pre.3, < 3.0)
|
||||
codeclimate-test-reporter (0.4.7)
|
||||
simplecov (>= 0.7.1, < 1.0.0)
|
||||
colorize (0.7.5)
|
||||
coveralls (0.7.12)
|
||||
multi_json (~> 1.10)
|
||||
colorize (0.7.7)
|
||||
coveralls (0.8.1)
|
||||
json (~> 1.8)
|
||||
rest-client (>= 1.6.8, < 2)
|
||||
simplecov (~> 0.9.1)
|
||||
simplecov (~> 0.10.0)
|
||||
term-ansicolor (~> 1.3)
|
||||
thor (~> 0.19.1)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
diff-lcs (1.2.5)
|
||||
docile (1.1.5)
|
||||
domain_name (0.5.23)
|
||||
domain_name (0.5.24)
|
||||
unf (>= 0.0.5, < 1.0.0)
|
||||
faraday (0.9.1)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
@@ -30,11 +30,12 @@ GEM
|
||||
multi_json (>= 1.7.5, < 2.0)
|
||||
nokogiri (~> 1.6.3)
|
||||
oauth2
|
||||
hashie (3.4.0)
|
||||
hashie (3.4.1)
|
||||
http-cookie (1.0.2)
|
||||
domain_name (~> 0.5)
|
||||
jwt (1.4.1)
|
||||
mime-types (2.4.3)
|
||||
json (1.8.2)
|
||||
jwt (1.5.0)
|
||||
mime-types (2.5)
|
||||
mini_portile (0.6.2)
|
||||
multi_json (1.11.0)
|
||||
multi_xml (0.5.5)
|
||||
@@ -48,10 +49,10 @@ GEM
|
||||
multi_json (~> 1.3)
|
||||
multi_xml (~> 0.5)
|
||||
rack (~> 1.2)
|
||||
parser (2.2.0.3)
|
||||
parser (2.2.2.2)
|
||||
ast (>= 1.1, < 3.0)
|
||||
powerpack (0.1.0)
|
||||
rack (1.6.0)
|
||||
powerpack (0.1.1)
|
||||
rack (1.6.1)
|
||||
rainbow (2.0.0)
|
||||
rake (10.4.2)
|
||||
rest-client (1.8.0)
|
||||
@@ -62,45 +63,45 @@ GEM
|
||||
rspec-core (~> 3.2.0)
|
||||
rspec-expectations (~> 3.2.0)
|
||||
rspec-mocks (~> 3.2.0)
|
||||
rspec-core (3.2.2)
|
||||
rspec-core (3.2.3)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-expectations (3.2.0)
|
||||
rspec-expectations (3.2.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-mocks (3.2.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.2.0)
|
||||
rspec-support (3.2.2)
|
||||
rubocop (0.29.1)
|
||||
rubocop (0.31.0)
|
||||
astrolabe (~> 1.3)
|
||||
parser (>= 2.2.0.1, < 3.0)
|
||||
parser (>= 2.2.2.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.4)
|
||||
ruby-progressbar (1.7.5)
|
||||
simplecov (0.9.2)
|
||||
simplecov (0.10.0)
|
||||
docile (~> 1.1.0)
|
||||
multi_json (~> 1.0)
|
||||
simplecov-html (~> 0.9.0)
|
||||
simplecov-html (0.9.0)
|
||||
json (~> 1.8)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.0)
|
||||
term-ansicolor (1.3.0)
|
||||
tins (~> 1.0)
|
||||
thor (0.19.1)
|
||||
thread_safe (0.3.5)
|
||||
tins (1.3.5)
|
||||
tins (1.5.1)
|
||||
unf (0.1.4)
|
||||
unf_ext
|
||||
unf_ext (0.0.6)
|
||||
unf_ext (0.0.7.1)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
codeclimate-test-reporter
|
||||
colorize
|
||||
colorize (>= 0.7.7)
|
||||
coveralls
|
||||
github_api
|
||||
rake
|
||||
github_api (>= 0.12.3)
|
||||
rake (>= 10.4.2)
|
||||
rspec
|
||||
rubocop
|
||||
simplecov
|
||||
|
||||
45
README.md
45
README.md
@@ -83,28 +83,29 @@ As output you will get `CHANGELOG.md` file with pretty *Markdown-formatted* chan
|
||||
Type `github_changelog_generator --help` for detailed usage.
|
||||
|
||||
Usage: changelog_generator [options]
|
||||
-u, --user [USER] Username of the owner of target GitHub repo
|
||||
-p, --project [PROJECT] Name of project on GitHub
|
||||
-t, --token [TOKEN] To make more than 50 requests per hour your GitHub token required. You can generate it here: https://github.com/settings/tokens/new
|
||||
-f, --date-format [FORMAT] Date format. Default is %d/%m/%y
|
||||
-o, --output [NAME] Output file. Default is CHANGELOG.md
|
||||
--[no-]verbose Run verbosely. Default is true
|
||||
--[no-]issues Include closed issues to changelog. Default is true
|
||||
--[no-]issues-wo-labels Include closed issues without labels to changelog. Default is true
|
||||
--[no-]pr-wo-labels Include pull requests without labels to changelog. Default is true
|
||||
--[no-]pull-requests Include pull-requests to changelog. Default is true
|
||||
--[no-]filter-by-milestone Use milestone to detect when issue was resolved. Default is true
|
||||
--[no-]author Add author of pull-request in the end. Default is true
|
||||
--unreleased-only Generate log from unreleased closed issues only.
|
||||
--[no-]unreleased Add to log unreleased closed issues. Default is true
|
||||
--[no-]compare-link Include compare link between older version and newer version. Default is true
|
||||
--include-labels x,y,z Issues only with that labels will be included to changelog. Default is 'bug,enhancement'
|
||||
--exclude-labels x,y,z Issues with that labels will be always excluded from changelog. Default is 'duplicate,question,invalid,wontfix'
|
||||
--max-issues [NUMBER] Max number of issues to fetch from GitHub. Default is unlimited.
|
||||
--github-site [URL] The Enterprise Github site on which your project is hosted.
|
||||
--github-api [URL] The enterprise endpoint to use for your Github API.
|
||||
-v, --version Print version number
|
||||
-h, --help Displays Help
|
||||
-u, --user [USER] Username of the owner of target GitHub repo
|
||||
-p, --project [PROJECT] Name of project on GitHub
|
||||
-t, --token [TOKEN] To make more than 50 requests per hour your GitHub token required. You can generate it here: https://github.com/settings/tokens/new
|
||||
-f, --date-format [FORMAT] Date format. Default is %d/%m/%y
|
||||
-o, --output [NAME] Output file. Default is CHANGELOG.md
|
||||
--[no-]verbose Run verbosely. Default is true
|
||||
--[no-]issues Include closed issues to changelog. Default is true
|
||||
--[no-]issues-wo-labels Include closed issues without labels to changelog. Default is true
|
||||
--[no-]pr-wo-labels Include pull requests without labels to changelog. Default is true
|
||||
--[no-]pull-requests Include pull-requests to changelog. Default is true
|
||||
--[no-]filter-by-milestone Use milestone to detect when issue was resolved. Default is true
|
||||
--[no-]author Add author of pull-request in the end. Default is true
|
||||
--unreleased-only Generate log from unreleased closed issues only.
|
||||
--[no-]unreleased Add to log unreleased closed issues. Default is true
|
||||
--[no-]compare-link Include compare link between older version and newer version. Default is true
|
||||
--include-labels x,y,z Issues only with that labels will be included to changelog. Default is 'bug,enhancement'
|
||||
--exclude-labels x,y,z Issues with that labels will be always excluded from changelog. Default is 'duplicate,question,invalid,wontfix'
|
||||
--max-issues [NUMBER] Max number of issues to fetch from GitHub. Default is unlimited.
|
||||
--github-site [URL] The Enterprise Github site on which your project is hosted.
|
||||
--github-api [URL] The enterprise endpoint to use for your Github API.
|
||||
--future-release [RELEASE-VERSION] Put the unreleased changes in the specified release number.
|
||||
-v, --version Print version number
|
||||
-h, --help Displays Help
|
||||
|
||||
|
||||
### GitHub token
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#! /usr/bin/env ruby
|
||||
|
||||
require_relative "../lib/github_changelog_generator"
|
||||
GitHubChangelogGenerator::ChangelogGenerator.new.compound_changelog
|
||||
GitHubChangelogGenerator::ChangelogGenerator.new.run
|
||||
|
||||
221
bump_gemfile.rb
221
bump_gemfile.rb
@@ -1,221 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
require "optparse"
|
||||
|
||||
SPEC_TYPE = "gemspec"
|
||||
|
||||
:major
|
||||
:minor
|
||||
:patch
|
||||
|
||||
@options = { dry_run: false, bump_number: :patch }
|
||||
|
||||
OptionParser.new { |opts|
|
||||
opts.banner = "Usage: bump.rb [options]"
|
||||
|
||||
opts.on("-d", "--dry-run", "Dry run") do |v|
|
||||
@options[:dry_run] = v
|
||||
end
|
||||
opts.on("-a", "--major", "Bump major version") do |_v|
|
||||
@options[:bump_number] = :major
|
||||
end
|
||||
opts.on("-m", "--minor", "Bump minor version") do |_v|
|
||||
@options[:bump_number] = :minor
|
||||
end
|
||||
opts.on("-p", "--patch", "Bump patch version") do |_v|
|
||||
@options[:bump_number] = :patch
|
||||
end
|
||||
opts.on("-r", "--revert", "Revert last bump") do |v|
|
||||
@options[:revert] = v
|
||||
end
|
||||
}.parse!
|
||||
|
||||
p @options
|
||||
|
||||
def check_repo_is_clean_or_dry_run
|
||||
value = `#{"git status --porcelain"}`
|
||||
|
||||
if value.empty?
|
||||
puts "Repo is clean -> continue"
|
||||
else
|
||||
if @options[:dry_run]
|
||||
puts 'Repo not clean, "Dry run" enabled -> continue'
|
||||
else
|
||||
puts "Repository not clean -> exit"
|
||||
exit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_spec_file
|
||||
list_of_specs = execute_line("find . -name '*.#{SPEC_TYPE}'")
|
||||
arr = list_of_specs.split("\n")
|
||||
|
||||
spec_file = ""
|
||||
|
||||
case arr.count
|
||||
when 0
|
||||
puts "No #{SPEC_TYPE} files found. -> Exit."
|
||||
exit
|
||||
when 1
|
||||
spec_file = arr[0]
|
||||
else
|
||||
puts "Which spec should be used?"
|
||||
arr.each_with_index { |file, index| puts "#{index + 1}. #{file}" }
|
||||
input_index = Integer(gets.chomp)
|
||||
spec_file = arr[input_index - 1]
|
||||
end
|
||||
|
||||
if spec_file.nil?
|
||||
puts "Can't find specified spec file -> exit"
|
||||
exit
|
||||
end
|
||||
|
||||
spec_file.sub("./", "")
|
||||
end
|
||||
|
||||
def find_current_gem_file
|
||||
list_of_specs = execute_line("find . -name '*.gem'")
|
||||
arr = list_of_specs.split("\n")
|
||||
|
||||
spec_file = ""
|
||||
|
||||
case arr.count
|
||||
when 0
|
||||
puts "No #{SPEC_TYPE} files found. -> Exit."
|
||||
exit
|
||||
when 1
|
||||
spec_file = arr[0]
|
||||
else
|
||||
puts "Which spec should be used?"
|
||||
arr.each_with_index { |file, index| puts "#{index + 1}. #{file}" }
|
||||
input_index = Integer(gets.chomp)
|
||||
spec_file = arr[input_index - 1]
|
||||
end
|
||||
|
||||
if spec_file.nil?
|
||||
puts "Can't find specified spec file -> exit"
|
||||
exit
|
||||
end
|
||||
|
||||
spec_file.sub("./", "")
|
||||
end
|
||||
|
||||
def find_version_in_podspec(podspec)
|
||||
readme = File.read(podspec)
|
||||
|
||||
# try to find version in format 1.22.333
|
||||
re = /(\d+)\.(\d+)\.(\d+)/m
|
||||
|
||||
match_result = re.match(readme)
|
||||
|
||||
unless match_result
|
||||
puts "Not found any versions"
|
||||
exit
|
||||
end
|
||||
|
||||
puts "Found version #{match_result[0]}"
|
||||
[match_result[0], match_result.captures]
|
||||
end
|
||||
|
||||
def bump_version(versions_array)
|
||||
bumped_result = versions_array.dup
|
||||
bumped_result.map!(&:to_i)
|
||||
|
||||
case @options[:bump_number]
|
||||
when :major
|
||||
bumped_result[0] += 1
|
||||
bumped_result[1] = 0
|
||||
bumped_result[2] = 0
|
||||
when :minor
|
||||
bumped_result[1] += 1
|
||||
bumped_result[2] = 0
|
||||
when :patch
|
||||
bumped_result[2] += 1
|
||||
else
|
||||
fail("unknown bump_number")
|
||||
end
|
||||
|
||||
bumped_version = bumped_result.join(".")
|
||||
puts "Bump version: #{versions_array.join('.')} -> #{bumped_version}"
|
||||
bumped_version
|
||||
end
|
||||
|
||||
def execute_line(line)
|
||||
output = `#{line}`
|
||||
check_exit_status(output)
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
def execute_line_if_not_dry_run(line)
|
||||
if @options[:dry_run]
|
||||
puts "Dry run: #{line}"
|
||||
nil
|
||||
else
|
||||
puts line
|
||||
value = `#{line}`
|
||||
puts value
|
||||
check_exit_status(value)
|
||||
value
|
||||
end
|
||||
end
|
||||
|
||||
def check_exit_status(output)
|
||||
if $CHILD_STATUS.exitstatus != 0
|
||||
puts "Output:\n#{output}\nExit status = #{$CHILD_STATUS.exitstatus} ->Terminate script."
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
def run_bumping_script
|
||||
check_repo_is_clean_or_dry_run
|
||||
spec_file = find_spec_file
|
||||
result, versions_array = find_version_in_podspec(spec_file)
|
||||
bumped_version = bump_version(versions_array)
|
||||
|
||||
unless @options[:dry_run]
|
||||
puts "Are you sure? Press Y to continue:"
|
||||
str = gets.chomp
|
||||
if str != "Y"
|
||||
puts "-> exit"
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
execute_line_if_not_dry_run("sed -i \"\" \"s/#{result}/#{bumped_version}/\" README.md")
|
||||
execute_line_if_not_dry_run("sed -i \"\" \"s/#{result}/#{bumped_version}/\" #{spec_file}")
|
||||
execute_line_if_not_dry_run("git commit --all -m \"Update #{$SPEC_TYPE} to version #{bumped_version}\"")
|
||||
execute_line_if_not_dry_run("git tag #{bumped_version}")
|
||||
execute_line_if_not_dry_run("git push")
|
||||
execute_line_if_not_dry_run("git push --tags")
|
||||
execute_line_if_not_dry_run("gem build #{spec_file}")
|
||||
|
||||
gem = find_current_gem_file
|
||||
execute_line_if_not_dry_run("gem push #{gem}")
|
||||
# execute_line_if_not_dry_run("pod trunk push #{spec_file}")
|
||||
end
|
||||
|
||||
def revert_last_bump
|
||||
spec_file = find_spec_file
|
||||
result, _ = find_version_in_podspec(spec_file)
|
||||
|
||||
puts "DELETE tag #{result} and HARD reset HEAD~1?\nPress Y to continue:"
|
||||
str = gets.chomp
|
||||
if str != "Y"
|
||||
puts "-> exit"
|
||||
exit
|
||||
end
|
||||
execute_line_if_not_dry_run("git tag -d #{result}")
|
||||
execute_line_if_not_dry_run("git reset --hard HEAD~1")
|
||||
execute_line_if_not_dry_run("git push --delete origin #{result}")
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
|
||||
if @options[:revert]
|
||||
revert_last_bump
|
||||
else
|
||||
run_bumping_script
|
||||
end
|
||||
|
||||
end
|
||||
@@ -1,13 +1,15 @@
|
||||
# Change Log
|
||||
|
||||
## [Unreleased](https://github.com/skywinder/changelog_test/tree/HEAD)
|
||||
## [0.0.4](https://github.com/skywinder/changelog_test/tree/0.0.4) (2015-05-22)
|
||||
|
||||
[Full Changelog](https://github.com/skywinder/changelog_test/compare/v0.0.3...HEAD)
|
||||
[Full Changelog](https://github.com/skywinder/changelog_test/compare/v0.0.3...0.0.4)
|
||||
|
||||
**Closed issues:**
|
||||
|
||||
- Test issue, that should appear in 0.0.4 [\#3](https://github.com/skywinder/changelog_test/issues/3)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- This PR SHOULD NOT appear in change log! [\#6](https://github.com/skywinder/changelog_test/pull/6) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
- Add automatically generated change log file. [\#5](https://github.com/skywinder/changelog_test/pull/5) ([skywinder](https://github.com/skywinder))
|
||||
|
||||
## [v0.0.3](https://github.com/skywinder/changelog_test/tree/v0.0.3) (2015-03-04)
|
||||
|
||||
@@ -6,510 +6,34 @@ require "colorize"
|
||||
require "benchmark"
|
||||
|
||||
require_relative "github_changelog_generator/parser"
|
||||
require_relative "github_changelog_generator/generator"
|
||||
require_relative "github_changelog_generator/generator/generator"
|
||||
require_relative "github_changelog_generator/version"
|
||||
require_relative "github_changelog_generator/reader"
|
||||
require_relative "github_changelog_generator/fetcher"
|
||||
|
||||
# The main module, where placed all classes (now, at least)
|
||||
module GitHubChangelogGenerator
|
||||
# Default error for ChangelogGenerator
|
||||
class ChangelogGeneratorError < StandardError
|
||||
end
|
||||
|
||||
# Main class and entry point for this script.
|
||||
class ChangelogGenerator
|
||||
attr_accessor :options, :all_tags, :github
|
||||
|
||||
# Class, responsible for whole change log generation cycle
|
||||
# @return initialised instance of ChangelogGenerator
|
||||
def initialize
|
||||
@options = Parser.parse_options
|
||||
|
||||
@fetcher = GitHubChangelogGenerator::Fetcher.new @options
|
||||
|
||||
@generator = Generator.new @options
|
||||
|
||||
# @all_tags = get_filtered_tags
|
||||
@all_tags = @fetcher.get_all_tags
|
||||
|
||||
@issues, @pull_requests = @fetcher.fetch_issues_and_pull_requests
|
||||
|
||||
@pull_requests = @options[:pulls] ? get_filtered_pull_requests : []
|
||||
|
||||
@issues = @options[:issues] ? get_filtered_issues : []
|
||||
|
||||
fetch_event_for_issues_and_pr
|
||||
detect_actual_closed_dates
|
||||
end
|
||||
|
||||
# Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags
|
||||
#
|
||||
# @return [Array]
|
||||
def get_filtered_tags
|
||||
all_tags = @fetcher.get_all_tags
|
||||
filtered_tags = []
|
||||
if @options[:between_tags]
|
||||
@options[:between_tags].each do |tag|
|
||||
unless all_tags.include? tag
|
||||
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
|
||||
end
|
||||
end
|
||||
filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag }
|
||||
end
|
||||
filtered_tags
|
||||
end
|
||||
|
||||
def detect_actual_closed_dates
|
||||
if @options[:verbose]
|
||||
print "Fetching closed dates for issues...\r"
|
||||
end
|
||||
|
||||
threads = []
|
||||
|
||||
@issues.each { |issue|
|
||||
threads << Thread.new {
|
||||
find_closed_date_by_commit(issue)
|
||||
}
|
||||
}
|
||||
|
||||
@pull_requests.each { |pull_request|
|
||||
threads << Thread.new {
|
||||
find_closed_date_by_commit(pull_request)
|
||||
}
|
||||
}
|
||||
threads.each(&:join)
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Fetching closed dates for issues: Done!"
|
||||
end
|
||||
end
|
||||
|
||||
# Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit.
|
||||
# @param [Hash] issue
|
||||
def find_closed_date_by_commit(issue)
|
||||
unless issue["events"].nil?
|
||||
# if it's PR -> then find "merged event", in case of usual issue -> fond closed date
|
||||
compare_string = issue[:merged_at].nil? ? "closed" : "merged"
|
||||
# reverse! - to find latest closed event. (event goes in date order)
|
||||
issue["events"].reverse!.each { |event|
|
||||
if event[:event].eql? compare_string
|
||||
if event[:commit_id].nil?
|
||||
issue[:actual_date] = issue[:closed_at]
|
||||
else
|
||||
begin
|
||||
commit = @fetcher.fetch_commit(event)
|
||||
issue[:actual_date] = commit[:author][:date]
|
||||
rescue
|
||||
puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow
|
||||
issue[:actual_date] = issue[:closed_at]
|
||||
end
|
||||
end
|
||||
break
|
||||
end
|
||||
}
|
||||
end
|
||||
# TODO: assert issues, that remain without 'actual_date' hash for some reason.
|
||||
end
|
||||
|
||||
def print_json(json)
|
||||
puts JSON.pretty_generate(json)
|
||||
end
|
||||
|
||||
# This method fetches missing params for PR and filter them by specified options
|
||||
# It include add all PR's with labels from @options[:include_labels] array
|
||||
# And exclude all from :exclude_labels array.
|
||||
# @return [Array] filtered PR's
|
||||
def get_filtered_pull_requests
|
||||
fetch_merged_at_pull_requests
|
||||
|
||||
filtered_pull_requests = include_issues_by_labels(@pull_requests)
|
||||
|
||||
filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests)
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Filtered pull requests: #{filtered_pull_requests.count}"
|
||||
end
|
||||
|
||||
filtered_pull_requests
|
||||
end
|
||||
|
||||
# This method fetch missing required attributes for pull requests
|
||||
# :merged_at - is a date, when issue PR was merged.
|
||||
# More correct to use this date, not closed date.
|
||||
def fetch_merged_at_pull_requests
|
||||
if @options[:verbose]
|
||||
print "Fetching merged dates...\r"
|
||||
end
|
||||
pull_requests = @fetcher.fetch_pull_requests
|
||||
|
||||
@pull_requests.each { |pr|
|
||||
fetched_pr = pull_requests.find { |fpr|
|
||||
fpr.number == pr.number
|
||||
}
|
||||
pr[:merged_at] = fetched_pr[:merged_at]
|
||||
pull_requests.delete(fetched_pr)
|
||||
}
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Fetching merged dates: Done!"
|
||||
end
|
||||
end
|
||||
|
||||
# Include issues with labels, specified in :include_labels
|
||||
# @param [Array] issues to filter
|
||||
# @return [Array] filtered array of issues
|
||||
def include_issues_by_labels(issues)
|
||||
filtered_issues = @options[:include_labels].nil? ? issues : issues.select { |issue| (issue.labels.map(&:name) & @options[:include_labels]).any? }
|
||||
|
||||
if @options[:add_issues_wo_labels]
|
||||
issues_wo_labels = issues.select { |issue|
|
||||
!issue.labels.map(&:name).any?
|
||||
}
|
||||
filtered_issues |= issues_wo_labels
|
||||
end
|
||||
filtered_issues
|
||||
end
|
||||
|
||||
# delete all labels with labels from @options[:exclude_labels] array
|
||||
# @param [Array] issues
|
||||
# @return [Array] filtered array
|
||||
def exclude_issues_by_labels(issues)
|
||||
unless @options[:exclude_labels].nil?
|
||||
issues = issues.select { |issue|
|
||||
!(issue.labels.map(&:name) & @options[:exclude_labels]).any?
|
||||
}
|
||||
end
|
||||
issues
|
||||
end
|
||||
|
||||
# The entry point of this script to generate change log
|
||||
# @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags.
|
||||
def compound_changelog
|
||||
log = "# Change Log\n\n"
|
||||
|
||||
if @options[:unreleased_only]
|
||||
log += generate_log_between_tags(all_tags[0], nil)
|
||||
elsif @options[:tag1] and @options[:tag2]
|
||||
tag1 = @options[:tag1]
|
||||
tag2 = @options[:tag2]
|
||||
tags_strings = []
|
||||
all_tags.each { |x| tags_strings.push(x["name"]) }
|
||||
|
||||
if tags_strings.include?(tag1)
|
||||
if tags_strings.include?(tag2)
|
||||
to_a = tags_strings.map.with_index.to_a
|
||||
hash = Hash[to_a]
|
||||
index1 = hash[tag1]
|
||||
index2 = hash[tag2]
|
||||
log += generate_log_between_tags(all_tags[index1], all_tags[index2])
|
||||
else
|
||||
fail ChangelogGeneratorError, "Can't find tag #{tag2} -> exit".red
|
||||
end
|
||||
else
|
||||
fail ChangelogGeneratorError, "Can't find tag #{tag1} -> exit".red
|
||||
end
|
||||
else
|
||||
log += generate_log_for_all_tags
|
||||
end
|
||||
|
||||
log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
|
||||
def run
|
||||
log = @generator.compound_changelog
|
||||
|
||||
output_filename = "#{@options[:output]}"
|
||||
File.open(output_filename, "w") { |file| file.write(log) }
|
||||
puts "Done!"
|
||||
puts "Generated log placed in #{`pwd`.strip!}/#{output_filename}"
|
||||
end
|
||||
|
||||
# The full cycle of generation for whole project
|
||||
# @return [String] The complete change log
|
||||
def generate_log_for_all_tags
|
||||
fetch_tags_dates
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Sorting tags..."
|
||||
end
|
||||
|
||||
@all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse!
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Generating log..."
|
||||
end
|
||||
|
||||
log = ""
|
||||
|
||||
if @options[:unreleased] && @all_tags.count != 0
|
||||
unreleased_log = generate_log_between_tags(all_tags[0], nil)
|
||||
if unreleased_log
|
||||
log += unreleased_log
|
||||
end
|
||||
end
|
||||
|
||||
(1...all_tags.size).each { |index|
|
||||
log += generate_log_between_tags(all_tags[index], all_tags[index - 1])
|
||||
}
|
||||
if @all_tags.count != 0
|
||||
log += generate_log_between_tags(nil, all_tags.last)
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# Async fetching of all tags dates
|
||||
def fetch_tags_dates
|
||||
if @options[:verbose]
|
||||
print "Fetching tag dates...\r"
|
||||
end
|
||||
|
||||
# Async fetching tags:
|
||||
threads = []
|
||||
i = 0
|
||||
all = @all_tags.count
|
||||
@all_tags.each { |tag|
|
||||
threads << Thread.new {
|
||||
@fetcher.get_time_of_tag(tag)
|
||||
if @options[:verbose]
|
||||
print "Fetching tags dates: #{i + 1}/#{all}\r"
|
||||
i += 1
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
print " \r"
|
||||
|
||||
threads.each(&:join)
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Fetching tags dates: #{i} Done!"
|
||||
end
|
||||
end
|
||||
|
||||
# Generate log only between 2 specified tags
|
||||
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
||||
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
||||
def generate_log_between_tags(older_tag, newer_tag)
|
||||
filtered_pull_requests = delete_by_time(@pull_requests, :actual_date, older_tag, newer_tag)
|
||||
filtered_issues = delete_by_time(@issues, :actual_date, older_tag, newer_tag)
|
||||
|
||||
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
||||
older_tag_name = older_tag.nil? ? nil : older_tag["name"]
|
||||
|
||||
if @options[:filter_issues_by_milestone]
|
||||
# delete excess irrelevant issues (according milestones)
|
||||
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
||||
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
||||
end
|
||||
|
||||
if filtered_issues.empty? && filtered_pull_requests.empty? && newer_tag.nil?
|
||||
# do not generate empty unreleased section
|
||||
return ""
|
||||
end
|
||||
|
||||
create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
|
||||
end
|
||||
|
||||
def filter_by_milestone(filtered_issues, newer_tag_name, src_array)
|
||||
filtered_issues.select! { |issue|
|
||||
# leave issues without milestones
|
||||
if issue.milestone.nil?
|
||||
true
|
||||
else
|
||||
# check, that this milestone in tag list:
|
||||
@all_tags.find { |tag| tag.name == issue.milestone.title }.nil?
|
||||
end
|
||||
}
|
||||
unless newer_tag_name.nil?
|
||||
|
||||
# add missed issues (according milestones)
|
||||
issues_to_add = src_array.select { |issue|
|
||||
if issue.milestone.nil?
|
||||
false
|
||||
else
|
||||
# check, that this milestone in tag list:
|
||||
milestone_is_tag = @all_tags.find { |tag|
|
||||
tag.name == issue.milestone.title
|
||||
}
|
||||
|
||||
if milestone_is_tag.nil?
|
||||
false
|
||||
else
|
||||
issue.milestone.title == newer_tag_name
|
||||
end
|
||||
end
|
||||
}
|
||||
|
||||
filtered_issues |= issues_to_add
|
||||
end
|
||||
filtered_issues
|
||||
end
|
||||
|
||||
# Method filter issues, that belong only specified tag range
|
||||
# @param [Array] array of issues to filter
|
||||
# @param [Symbol] hash_key key of date value default is :actual_date
|
||||
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
||||
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
||||
# @return [Array] filtered issues
|
||||
def delete_by_time(array, hash_key = :actual_date, older_tag = nil, newer_tag = nil)
|
||||
fail ChangelogGeneratorError, "At least one of the tags should be not nil!".red if older_tag.nil? && newer_tag.nil?
|
||||
|
||||
newer_tag_time = newer_tag && @fetcher.get_time_of_tag(newer_tag)
|
||||
older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag)
|
||||
|
||||
array.select { |req|
|
||||
if req[hash_key]
|
||||
t = Time.parse(req[hash_key]).utc
|
||||
|
||||
if older_tag_time.nil?
|
||||
tag_in_range_old = true
|
||||
else
|
||||
tag_in_range_old = t > older_tag_time
|
||||
end
|
||||
|
||||
if newer_tag_time.nil?
|
||||
tag_in_range_new = true
|
||||
else
|
||||
tag_in_range_new = t <= newer_tag_time
|
||||
end
|
||||
|
||||
tag_in_range = (tag_in_range_old) && (tag_in_range_new)
|
||||
|
||||
tag_in_range
|
||||
else
|
||||
false
|
||||
end
|
||||
}
|
||||
end
|
||||
|
||||
# Generates log for section with header and body
|
||||
#
|
||||
# @param [Array] pull_requests List or PR's in new section
|
||||
# @param [Array] issues List of issues in new section
|
||||
# @param [String] newer_tag Name of the newer tag. Could be nil for `Unreleased` section
|
||||
# @param [String] older_tag_name Older tag, used for the links. Could be nil for last tag.
|
||||
# @return [String] Ready and parsed section
|
||||
def create_log(pull_requests, issues, newer_tag, older_tag_name = nil)
|
||||
newer_tag_time = newer_tag.nil? ? Time.new : @fetcher.get_time_of_tag(newer_tag)
|
||||
newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
|
||||
newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
|
||||
|
||||
github_site = options[:github_site] || "https://github.com"
|
||||
project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
|
||||
|
||||
log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
||||
|
||||
if @options[:issues]
|
||||
# Generate issues:
|
||||
issues_a = []
|
||||
enhancement_a = []
|
||||
bugs_a = []
|
||||
|
||||
issues.each { |dict|
|
||||
added = false
|
||||
dict.labels.each { |label|
|
||||
if label.name == "bug"
|
||||
bugs_a.push dict
|
||||
added = true
|
||||
next
|
||||
end
|
||||
if label.name == "enhancement"
|
||||
enhancement_a.push dict
|
||||
added = true
|
||||
next
|
||||
end
|
||||
}
|
||||
unless added
|
||||
issues_a.push dict
|
||||
end
|
||||
}
|
||||
|
||||
log += generate_sub_section(enhancement_a, @options[:enhancement_prefix])
|
||||
log += generate_sub_section(bugs_a, @options[:bug_prefix])
|
||||
log += generate_sub_section(issues_a, @options[:issue_prefix])
|
||||
end
|
||||
|
||||
if @options[:pulls]
|
||||
# Generate pull requests:
|
||||
log += generate_sub_section(pull_requests, @options[:merge_prefix])
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# @param [Array] issues List of issues on sub-section
|
||||
# @param [String] prefix Nae of sub-section
|
||||
# @return [String] Generate ready-to-go sub-section
|
||||
def generate_sub_section(issues, prefix)
|
||||
log = ""
|
||||
|
||||
if options[:simple_list] != true && issues.any?
|
||||
log += "#{prefix}\n\n"
|
||||
end
|
||||
|
||||
if issues.any?
|
||||
issues.each { |issue|
|
||||
merge_string = @generator.get_string_for_issue(issue)
|
||||
log += "- #{merge_string}\n\n"
|
||||
}
|
||||
end
|
||||
log
|
||||
end
|
||||
|
||||
# It generate one header for section with specific parameters.
|
||||
#
|
||||
# @param [String] newer_tag_name - name of newer tag
|
||||
# @param [String] newer_tag_link - used for links. Could be same as #newer_tag_name or some specific value, like HEAD
|
||||
# @param [Time] newer_tag_time - time, when newer tag created
|
||||
# @param [String] older_tag_link - tag name, used for links.
|
||||
# @param [String] project_url - url for current project.
|
||||
# @return [String] - Generate one ready-to-add section.
|
||||
def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_link, project_url)
|
||||
log = ""
|
||||
|
||||
# Generate date string:
|
||||
time_string = newer_tag_time.strftime @options[:dateformat]
|
||||
|
||||
# Generate tag name and link
|
||||
if newer_tag_name.equal? @options[:unreleased_label]
|
||||
log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n"
|
||||
else
|
||||
log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{time_string})\n\n"
|
||||
end
|
||||
|
||||
if @options[:compare_link] && older_tag_link
|
||||
# Generate compare link
|
||||
log += "[Full Changelog](#{project_url}/compare/#{older_tag_link}...#{newer_tag_link})\n\n"
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# Filter issues according labels
|
||||
# @return [Array] Filtered issues
|
||||
def get_filtered_issues
|
||||
filtered_issues = include_issues_by_labels(@issues)
|
||||
|
||||
filtered_issues = exclude_issues_by_labels(filtered_issues)
|
||||
|
||||
if @options[:verbose]
|
||||
puts "Filtered issues: #{filtered_issues.count}"
|
||||
end
|
||||
|
||||
filtered_issues
|
||||
end
|
||||
|
||||
# Fetch event for issues and pull requests
|
||||
# @return [Array] array of fetched issues
|
||||
def fetch_event_for_issues_and_pr
|
||||
if @options[:verbose]
|
||||
print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r"
|
||||
end
|
||||
|
||||
# Async fetching events:
|
||||
|
||||
@fetcher.fetch_events_async(@issues + @pull_requests)
|
||||
puts "Generated log placed in #{Dir.pwd}/#{output_filename}"
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
GitHubChangelogGenerator::ChangelogGenerator.new.compound_changelog
|
||||
GitHubChangelogGenerator::ChangelogGenerator.new.run
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,31 +8,30 @@ module GitHubChangelogGenerator
|
||||
# fetcher = GitHubChangelogGenerator::Fetcher.new options
|
||||
class Fetcher
|
||||
PER_PAGE_NUMBER = 30
|
||||
GH_RATE_LIMIT_EXCEEDED_MSG = "Warning: GitHub API rate limit (5000 per hour) exceeded, change log may be " \
|
||||
"missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument."
|
||||
CHANGELOG_GITHUB_TOKEN = "CHANGELOG_GITHUB_TOKEN"
|
||||
GH_RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, change log may be " \
|
||||
"missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument."
|
||||
NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found. " \
|
||||
"This script can make only 50 requests to GitHub API per hour without token!"
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
|
||||
@user = @options[:user]
|
||||
@project = @options[:project]
|
||||
@github_token = fetch_github_token
|
||||
@tag_times_hash = {}
|
||||
@options = options || {}
|
||||
|
||||
@logger = Logger.new(STDOUT)
|
||||
@logger.formatter = proc do |_severity, _datetime, _progname, msg|
|
||||
"#{msg}\n"
|
||||
end
|
||||
|
||||
@user = @options[:user]
|
||||
@project = @options[:project]
|
||||
@github_token = fetch_github_token
|
||||
@tag_times_hash = {}
|
||||
github_options = { per_page: PER_PAGE_NUMBER }
|
||||
github_options[:oauth_token] = @github_token unless @github_token.nil?
|
||||
github_options[:endpoint] = options[:github_endpoint] unless options[:github_endpoint].nil?
|
||||
github_options[:site] = options[:github_endpoint] unless options[:github_site].nil?
|
||||
github_options[:endpoint] = @options[:github_endpoint] unless @options[:github_endpoint].nil?
|
||||
github_options[:site] = @options[:github_endpoint] unless @options[:github_site].nil?
|
||||
|
||||
begin
|
||||
@github = Github.new github_options
|
||||
rescue
|
||||
@logger.warn GH_RATE_LIMIT_EXCEEDED_MSG.yellow
|
||||
end
|
||||
@github = check_github_response { Github.new github_options }
|
||||
end
|
||||
|
||||
# Returns GitHub token. First try to use variable, provided by --token option,
|
||||
@@ -40,12 +39,9 @@ module GitHubChangelogGenerator
|
||||
#
|
||||
# @return [String]
|
||||
def fetch_github_token
|
||||
env_var = @options[:token] ? @options[:token] : (ENV.fetch "CHANGELOG_GITHUB_TOKEN", nil)
|
||||
env_var = @options[:token] ? @options[:token] : (ENV.fetch CHANGELOG_GITHUB_TOKEN, nil)
|
||||
|
||||
unless env_var
|
||||
@logger.warn "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found.".yellow
|
||||
@logger.warn "This script can make only 50 requests to GitHub API per hour without token!".yellow
|
||||
end
|
||||
@logger.warn NO_TOKEN_PROVIDED.yellow unless env_var
|
||||
|
||||
env_var
|
||||
end
|
||||
@@ -53,44 +49,52 @@ module GitHubChangelogGenerator
|
||||
# Fetch all tags from repo
|
||||
# @return [Array] array of tags
|
||||
def get_all_tags
|
||||
if @options[:verbose]
|
||||
print "Fetching tags...\r"
|
||||
end
|
||||
print "Fetching tags...\r" if @options[:verbose]
|
||||
|
||||
tags = []
|
||||
|
||||
begin
|
||||
response = @github.repos.tags @options[:user], @options[:project]
|
||||
page_i = 0
|
||||
count_pages = response.count_pages
|
||||
response.each_page do |page|
|
||||
page_i += PER_PAGE_NUMBER
|
||||
print "Fetching tags... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
|
||||
tags.concat(page)
|
||||
end
|
||||
print " \r"
|
||||
|
||||
if tags.count == 0
|
||||
@logger.warn "Warning: Can't find any tags in repo.\
|
||||
Make sure, that you push tags to remote repo via 'git push --tags'".yellow
|
||||
elsif @options[:verbose]
|
||||
@logger.info "Found #{tags.count} tags"
|
||||
end
|
||||
|
||||
rescue
|
||||
@logger.warn GH_RATE_LIMIT_EXCEEDED_MSG.yellow
|
||||
end
|
||||
check_github_response { github_fetch_tags(tags) }
|
||||
|
||||
tags
|
||||
end
|
||||
|
||||
def check_github_response
|
||||
begin
|
||||
value = yield
|
||||
rescue Github::Error::Unauthorized => e
|
||||
@logger.error e.body.red
|
||||
abort "Error: wrong GitHub token"
|
||||
rescue Github::Error::Forbidden => e
|
||||
@logger.warn e.body.red
|
||||
@logger.warn GH_RATE_LIMIT_EXCEEDED_MSG.yellow
|
||||
end
|
||||
value
|
||||
end
|
||||
|
||||
def github_fetch_tags(tags)
|
||||
response = @github.repos.tags @options[:user], @options[:project]
|
||||
page_i = 0
|
||||
count_pages = response.count_pages
|
||||
response.each_page do |page|
|
||||
page_i += PER_PAGE_NUMBER
|
||||
print_in_same_line("Fetching tags... #{page_i}/#{count_pages * PER_PAGE_NUMBER}")
|
||||
tags.concat(page)
|
||||
end
|
||||
print_empty_line
|
||||
|
||||
if tags.count == 0
|
||||
@logger.warn "Warning: Can't find any tags in repo.\
|
||||
Make sure, that you push tags to remote repo via 'git push --tags'".yellow
|
||||
else
|
||||
@logger.info "Found #{tags.count} tags"
|
||||
end
|
||||
end
|
||||
|
||||
# This method fetch all closed issues and separate them to pull requests and pure issues
|
||||
# (pull request is kind of issue in term of GitHub)
|
||||
# @return [Tuple] with issues and pull requests
|
||||
def fetch_issues_and_pull_requests
|
||||
if @options[:verbose]
|
||||
print "Fetching closed issues...\r"
|
||||
end
|
||||
# @return [Tuple] with (issues, pull-requests)
|
||||
def fetch_closed_issues_and_pr
|
||||
print "Fetching closed issues...\r" if @options[:verbose]
|
||||
issues = []
|
||||
|
||||
begin
|
||||
@@ -103,47 +107,54 @@ Make sure, that you push tags to remote repo via 'git push --tags'".yellow
|
||||
count_pages = response.count_pages
|
||||
response.each_page do |page|
|
||||
page_i += PER_PAGE_NUMBER
|
||||
print "Fetching issues... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
|
||||
print_in_same_line("Fetching issues... #{page_i}/#{count_pages * PER_PAGE_NUMBER}")
|
||||
issues.concat(page)
|
||||
break if @options[:max_issues] && issues.length >= @options[:max_issues]
|
||||
end
|
||||
print_empty_line
|
||||
@logger.info "Received issues: #{issues.count}"
|
||||
|
||||
rescue
|
||||
@logger.warn GH_RATE_LIMIT_EXCEEDED_MSG.yellow
|
||||
end
|
||||
|
||||
print " \r"
|
||||
|
||||
if @options[:verbose]
|
||||
@logger.info "Received issues: #{issues.count}"
|
||||
end
|
||||
|
||||
# remove pull request from issues:
|
||||
issues.partition { |x|
|
||||
# separate arrays of issues and pull requests:
|
||||
issues.partition do |x|
|
||||
x[:pull_request].nil?
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Fetch all pull requests. We need them to detect :merged_at parameter
|
||||
# @return [Array] all pull requests
|
||||
def fetch_pull_requests
|
||||
def fetch_closed_pull_requests
|
||||
pull_requests = []
|
||||
begin
|
||||
response = @github.pull_requests.list @options[:user], @options[:project], state: "closed"
|
||||
page_i = 0
|
||||
count_pages = response.count_pages
|
||||
response.each_page do |page|
|
||||
page_i += PER_PAGE_NUMBER
|
||||
count_pages = response.count_pages
|
||||
print "Fetching merged dates... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
|
||||
log_string = "Fetching merged dates... #{page_i}/#{count_pages * PER_PAGE_NUMBER}"
|
||||
print_in_same_line(log_string)
|
||||
pull_requests.concat(page)
|
||||
end
|
||||
print_empty_line
|
||||
rescue
|
||||
@logger.warn GH_RATE_LIMIT_EXCEEDED_MSG.yellow
|
||||
end
|
||||
|
||||
print " \r"
|
||||
@logger.info "Fetching merged dates: #{pull_requests.count}"
|
||||
pull_requests
|
||||
end
|
||||
|
||||
def print_in_same_line(log_string)
|
||||
print log_string + "\r"
|
||||
end
|
||||
|
||||
def print_empty_line
|
||||
print_in_same_line(" ")
|
||||
end
|
||||
|
||||
# Fetch event for all issues and add them to :events
|
||||
# @param [Array] issues
|
||||
# @return [Void]
|
||||
@@ -151,9 +162,9 @@ Make sure, that you push tags to remote repo via 'git push --tags'".yellow
|
||||
i = 0
|
||||
max_thread_number = 50
|
||||
threads = []
|
||||
issues.each_slice(max_thread_number) { |issues_slice|
|
||||
issues_slice.each { |issue|
|
||||
threads << Thread.new {
|
||||
issues.each_slice(max_thread_number) do |issues_slice|
|
||||
issues_slice.each do |issue|
|
||||
threads << Thread.new do
|
||||
begin
|
||||
obj = @github.issues.events.list user: @options[:user],
|
||||
repo: @options[:project],
|
||||
@@ -162,20 +173,18 @@ Make sure, that you push tags to remote repo via 'git push --tags'".yellow
|
||||
@logger.warn GH_RATE_LIMIT_EXCEEDED_MSG.yellow
|
||||
end
|
||||
issue[:events] = obj.body
|
||||
print "Fetching events for issues and PR: #{i + 1}/#{issues.count}\r"
|
||||
print_in_same_line("Fetching events for issues and PR: #{i + 1}/#{issues.count}")
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
threads.each(&:join)
|
||||
threads = []
|
||||
}
|
||||
end
|
||||
|
||||
# to clear line from prev print
|
||||
print " \r"
|
||||
print_empty_line
|
||||
|
||||
if @options[:verbose]
|
||||
@logger.info "Fetching events for issues and PR: #{i} Done!"
|
||||
end
|
||||
@logger.info "Fetching events for issues and PR: #{i}"
|
||||
end
|
||||
|
||||
# Try to find tag date in local hash.
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
module GitHubChangelogGenerator
|
||||
class Generator
|
||||
def initialize(options = nil)
|
||||
@options = options
|
||||
end
|
||||
|
||||
# Parse issue and generate single line formatted issue line.
|
||||
#
|
||||
# Example output:
|
||||
# - Add coveralls integration [\#223](https://github.com/skywinder/github-changelog-generator/pull/223) ([skywinder](https://github.com/skywinder))
|
||||
#
|
||||
# @param [Hash] issue Fetched issue from GitHub
|
||||
# @return [String] Markdown-formatted single issue
|
||||
def get_string_for_issue(issue)
|
||||
encapsulated_title = encapsulate_string issue[:title]
|
||||
|
||||
title_with_number = "#{encapsulated_title} [\\##{issue[:number]}](#{issue.html_url})"
|
||||
|
||||
unless issue.pull_request.nil?
|
||||
if @options[:author]
|
||||
if issue.user.nil?
|
||||
title_with_number += " ({Null user})"
|
||||
else
|
||||
title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))"
|
||||
end
|
||||
end
|
||||
end
|
||||
title_with_number
|
||||
end
|
||||
|
||||
def encapsulate_string(string)
|
||||
string.gsub! '\\', '\\\\'
|
||||
|
||||
encpas_chars = %w(> * _ \( \) [ ] #)
|
||||
encpas_chars.each do |char|
|
||||
string.gsub! char, "\\#{char}"
|
||||
end
|
||||
|
||||
string
|
||||
end
|
||||
end
|
||||
end
|
||||
123
lib/github_changelog_generator/generator/generator.rb
Normal file
123
lib/github_changelog_generator/generator/generator.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
require "github_changelog_generator/fetcher"
|
||||
require_relative "generator_generation"
|
||||
require_relative "generator_fetcher"
|
||||
require_relative "generator_processor"
|
||||
require_relative "generator_tags"
|
||||
|
||||
module GitHubChangelogGenerator
|
||||
# Default error for ChangelogGenerator
|
||||
class ChangelogGeneratorError < StandardError
|
||||
end
|
||||
|
||||
class Generator
|
||||
attr_accessor :options, :all_tags, :github
|
||||
|
||||
# A Generator responsible for all logic, related with change log generation from ready-to-parse issues
|
||||
#
|
||||
# Example:
|
||||
# generator = GitHubChangelogGenerator::Generator.new
|
||||
# content = generator.compound_changelog
|
||||
def initialize(options = nil)
|
||||
@options = options || {}
|
||||
|
||||
@fetcher = GitHubChangelogGenerator::Fetcher.new @options
|
||||
end
|
||||
|
||||
def fetch_issues_and_pr
|
||||
issues, pull_requests = @fetcher.fetch_closed_issues_and_pr
|
||||
|
||||
@pull_requests = @options[:pulls] ? get_filtered_pull_requests(pull_requests) : []
|
||||
|
||||
@issues = @options[:issues] ? get_filtered_issues(issues) : []
|
||||
|
||||
fetch_events_for_issues_and_pr
|
||||
detect_actual_closed_dates(@issues + @pull_requests)
|
||||
end
|
||||
|
||||
# Encapsulate characters to make markdown look as expected.
|
||||
#
|
||||
# @param [String] string
|
||||
# @return [String] encapsulated input string
|
||||
def encapsulate_string(string)
|
||||
string.gsub! '\\', '\\\\'
|
||||
|
||||
encpas_chars = %w(> * _ \( \) [ ] #)
|
||||
encpas_chars.each do |char|
|
||||
string.gsub! char, "\\#{char}"
|
||||
end
|
||||
|
||||
string
|
||||
end
|
||||
|
||||
# Generates log for section with header and body
|
||||
#
|
||||
# @param [Array] pull_requests List or PR's in new section
|
||||
# @param [Array] issues List of issues in new section
|
||||
# @param [String] newer_tag Name of the newer tag. Could be nil for `Unreleased` section
|
||||
# @param [String] older_tag_name Older tag, used for the links. Could be nil for last tag.
|
||||
# @return [String] Ready and parsed section
|
||||
def create_log(pull_requests, issues, newer_tag, older_tag_name = nil)
|
||||
newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
|
||||
|
||||
github_site = options[:github_site] || "https://github.com"
|
||||
project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
|
||||
|
||||
log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
||||
|
||||
if @options[:issues]
|
||||
# Generate issues:
|
||||
log += issues_to_log(issues)
|
||||
end
|
||||
|
||||
if @options[:pulls]
|
||||
# Generate pull requests:
|
||||
log += generate_sub_section(pull_requests, @options[:merge_prefix])
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# Generate ready-to-paste log from list of issues.
|
||||
#
|
||||
# @param [Array] issues
|
||||
# @return [String] generated log for issues
|
||||
def issues_to_log(issues)
|
||||
log = ""
|
||||
bugs_a, enhancement_a, issues_a = parse_by_sections(issues)
|
||||
|
||||
log += generate_sub_section(enhancement_a, @options[:enhancement_prefix])
|
||||
log += generate_sub_section(bugs_a, @options[:bug_prefix])
|
||||
log += generate_sub_section(issues_a, @options[:issue_prefix])
|
||||
log
|
||||
end
|
||||
|
||||
# This method sort issues by types
|
||||
# (bugs, features, or just closed issues) by labels
|
||||
#
|
||||
# @param [Array] issues
|
||||
# @return [Array] tuple of filtered arrays: (Bugs, Enhancements Issues)
|
||||
def parse_by_sections(issues)
|
||||
issues_a = []
|
||||
enhancement_a = []
|
||||
bugs_a = []
|
||||
|
||||
issues.each do |dict|
|
||||
added = false
|
||||
dict.labels.each do |label|
|
||||
if label.name == "bug"
|
||||
bugs_a.push dict
|
||||
added = true
|
||||
next
|
||||
end
|
||||
if label.name == "enhancement"
|
||||
enhancement_a.push dict
|
||||
added = true
|
||||
next
|
||||
end
|
||||
end
|
||||
issues_a.push dict unless added
|
||||
end
|
||||
[bugs_a, enhancement_a, issues_a]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,83 @@
|
||||
module GitHubChangelogGenerator
|
||||
class Generator
|
||||
# Fetch event for issues and pull requests
|
||||
# @return [Array] array of fetched issues
|
||||
def fetch_events_for_issues_and_pr
|
||||
if @options[:verbose]
|
||||
print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r"
|
||||
end
|
||||
|
||||
# Async fetching events:
|
||||
@fetcher.fetch_events_async(@issues + @pull_requests)
|
||||
end
|
||||
|
||||
# Async fetching of all tags dates
|
||||
def fetch_tags_dates
|
||||
print "Fetching tag dates...\r" if @options[:verbose]
|
||||
# Async fetching tags:
|
||||
threads = []
|
||||
i = 0
|
||||
all = @all_tags.count
|
||||
@all_tags.each do |tag|
|
||||
print " \r"
|
||||
threads << Thread.new do
|
||||
@fetcher.get_time_of_tag(tag)
|
||||
print "Fetching tags dates: #{i + 1}/#{all}\r" if @options[:verbose]
|
||||
i += 1
|
||||
end
|
||||
end
|
||||
threads.each(&:join)
|
||||
puts "Fetching tags dates: #{i}" if @options[:verbose]
|
||||
end
|
||||
|
||||
# Find correct closed dates, if issues was closed by commits
|
||||
def detect_actual_closed_dates(issues)
|
||||
print "Fetching closed dates for issues...\r" if @options[:verbose]
|
||||
|
||||
max_thread_number = 50
|
||||
issues.each_slice(max_thread_number) do |issues_slice|
|
||||
threads = []
|
||||
issues_slice.each do |issue|
|
||||
threads << Thread.new { find_closed_date_by_commit(issue) }
|
||||
end
|
||||
threads.each(&:join)
|
||||
puts "Fetching closed dates for issues: Done!" if @options[:verbose]
|
||||
end
|
||||
end
|
||||
|
||||
# Fill :actual_date parameter of specified issue by closed date of the commit, if it was closed by commit.
|
||||
# @param [Hash] issue
|
||||
def find_closed_date_by_commit(issue)
|
||||
unless issue["events"].nil?
|
||||
# if it's PR -> then find "merged event", in case of usual issue -> fond closed date
|
||||
compare_string = issue[:merged_at].nil? ? "closed" : "merged"
|
||||
# reverse! - to find latest closed event. (event goes in date order)
|
||||
issue["events"].reverse!.each do |event|
|
||||
if event[:event].eql? compare_string
|
||||
set_date_from_event(event, issue)
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
# TODO: assert issues, that remain without 'actual_date' hash for some reason.
|
||||
end
|
||||
|
||||
# Set closed date from this issue
|
||||
#
|
||||
# @param [Hash] event
|
||||
# @param [Hash] issue
|
||||
def set_date_from_event(event, issue)
|
||||
if event[:commit_id].nil?
|
||||
issue[:actual_date] = issue[:closed_at]
|
||||
else
|
||||
begin
|
||||
commit = @fetcher.fetch_commit(event)
|
||||
issue[:actual_date] = commit[:author][:date]
|
||||
rescue
|
||||
puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow
|
||||
issue[:actual_date] = issue[:closed_at]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
174
lib/github_changelog_generator/generator/generator_generation.rb
Normal file
174
lib/github_changelog_generator/generator/generator_generation.rb
Normal file
@@ -0,0 +1,174 @@
|
||||
module GitHubChangelogGenerator
|
||||
class Generator
|
||||
# Main function to start change log generation
|
||||
#
|
||||
# @return [String] Generated change log file
|
||||
def compound_changelog
|
||||
fetch_and_filter_tags
|
||||
fetch_issues_and_pr
|
||||
|
||||
log = "# Change Log\n\n"
|
||||
|
||||
if @options[:unreleased_only]
|
||||
log += generate_log_between_tags(all_tags[0], nil)
|
||||
else
|
||||
log += generate_log_for_all_tags
|
||||
end
|
||||
|
||||
log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
|
||||
@log = log
|
||||
end
|
||||
|
||||
# @return [String] temp method should be removed soon
|
||||
def generate_for_2_tags(log)
|
||||
tag1 = @options[:tag1]
|
||||
tag2 = @options[:tag2]
|
||||
tags_strings = []
|
||||
all_tags.each { |x| tags_strings.push(x["name"]) }
|
||||
|
||||
if tags_strings.include?(tag1)
|
||||
if tags_strings.include?(tag2)
|
||||
to_a = tags_strings.map.with_index.to_a
|
||||
hash = Hash[to_a]
|
||||
index1 = hash[tag1]
|
||||
index2 = hash[tag2]
|
||||
log += generate_log_between_tags(all_tags[index1], all_tags[index2])
|
||||
else
|
||||
fail ChangelogGeneratorError, "Can't find tag #{tag2} -> exit".red
|
||||
end
|
||||
else
|
||||
fail ChangelogGeneratorError, "Can't find tag #{tag1} -> exit".red
|
||||
end
|
||||
log
|
||||
end
|
||||
|
||||
# @param [Array] issues List of issues on sub-section
|
||||
# @param [String] prefix Nae of sub-section
|
||||
# @return [String] Generate ready-to-go sub-section
|
||||
def generate_sub_section(issues, prefix)
|
||||
log = ""
|
||||
|
||||
log += "#{prefix}\n\n" if options[:simple_list] != true && issues.any?
|
||||
|
||||
if issues.any?
|
||||
issues.each do |issue|
|
||||
merge_string = get_string_for_issue(issue)
|
||||
log += "- #{merge_string}\n\n"
|
||||
end
|
||||
end
|
||||
log
|
||||
end
|
||||
|
||||
# It generate one header for section with specific parameters.
|
||||
#
|
||||
# @param [String] newer_tag_name - name of newer tag
|
||||
# @param [String] newer_tag_link - used for links. Could be same as #newer_tag_name or some specific value, like HEAD
|
||||
# @param [Time] newer_tag_time - time, when newer tag created
|
||||
# @param [String] older_tag_link - tag name, used for links.
|
||||
# @param [String] project_url - url for current project.
|
||||
# @return [String] - Generate one ready-to-add section.
|
||||
def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_link, project_url)
|
||||
log = ""
|
||||
|
||||
# Generate date string:
|
||||
time_string = newer_tag_time.strftime @options[:date_format]
|
||||
|
||||
# Generate tag name and link
|
||||
if newer_tag_name.equal? @options[:unreleased_label]
|
||||
log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n"
|
||||
else
|
||||
log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{time_string})\n\n"
|
||||
end
|
||||
|
||||
if @options[:compare_link] && older_tag_link
|
||||
# Generate compare link
|
||||
log += "[Full Changelog](#{project_url}/compare/#{older_tag_link}...#{newer_tag_link})\n\n"
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
# Generate log only between 2 specified tags
|
||||
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
||||
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
||||
def generate_log_between_tags(older_tag, newer_tag)
|
||||
filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
|
||||
|
||||
older_tag_name = older_tag.nil? ? nil : older_tag["name"]
|
||||
|
||||
if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
|
||||
# do not generate empty unreleased section
|
||||
return ""
|
||||
end
|
||||
|
||||
create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
|
||||
end
|
||||
|
||||
# Apply all filters to issues and pull requests
|
||||
#
|
||||
# @return [Array] filtered issues and pull requests
|
||||
def filter_issues_for_tags(newer_tag, older_tag)
|
||||
filtered_pull_requests = delete_by_time(@pull_requests, :actual_date, older_tag, newer_tag)
|
||||
filtered_issues = delete_by_time(@issues, :actual_date, older_tag, newer_tag)
|
||||
|
||||
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
||||
|
||||
if @options[:filter_issues_by_milestone]
|
||||
# delete excess irrelevant issues (according milestones). Issue #22.
|
||||
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
||||
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
||||
end
|
||||
[filtered_issues, filtered_pull_requests]
|
||||
end
|
||||
|
||||
# The full cycle of generation for whole project
|
||||
# @return [String] The complete change log
|
||||
def generate_log_for_all_tags
|
||||
puts "Generating log..." if @options[:verbose]
|
||||
|
||||
log = generate_unreleased_section
|
||||
|
||||
(1...all_tags.size).each do |index|
|
||||
log += generate_log_between_tags(all_tags[index], all_tags[index - 1])
|
||||
end
|
||||
if @all_tags.count != 0
|
||||
log += generate_log_between_tags(nil, all_tags.last)
|
||||
end
|
||||
|
||||
log
|
||||
end
|
||||
|
||||
def generate_unreleased_section
|
||||
log = ""
|
||||
if @options[:unreleased]
|
||||
unreleased_log = generate_log_between_tags(all_tags[0], nil)
|
||||
log += unreleased_log if unreleased_log
|
||||
end
|
||||
log
|
||||
end
|
||||
|
||||
# Parse issue and generate single line formatted issue line.
|
||||
#
|
||||
# Example output:
|
||||
# - Add coveralls integration [\#223](https://github.com/skywinder/github-changelog-generator/pull/223) ([skywinder](https://github.com/skywinder))
|
||||
#
|
||||
# @param [Hash] issue Fetched issue from GitHub
|
||||
# @return [String] Markdown-formatted single issue
|
||||
def get_string_for_issue(issue)
|
||||
encapsulated_title = encapsulate_string issue[:title]
|
||||
|
||||
title_with_number = "#{encapsulated_title} [\\##{issue[:number]}](#{issue.html_url})"
|
||||
|
||||
unless issue.pull_request.nil?
|
||||
if @options[:author]
|
||||
if issue.user.nil?
|
||||
title_with_number += " ({Null user})"
|
||||
else
|
||||
title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))"
|
||||
end
|
||||
end
|
||||
end
|
||||
title_with_number
|
||||
end
|
||||
end
|
||||
end
|
||||
192
lib/github_changelog_generator/generator/generator_processor.rb
Normal file
192
lib/github_changelog_generator/generator/generator_processor.rb
Normal file
@@ -0,0 +1,192 @@
|
||||
module GitHubChangelogGenerator
|
||||
class Generator
|
||||
# delete all labels with labels from @options[:exclude_labels] array
|
||||
# @param [Array] issues
|
||||
# @return [Array] filtered array
|
||||
def exclude_issues_by_labels(issues)
|
||||
unless @options[:exclude_labels].nil?
|
||||
issues = issues.select do |issue|
|
||||
var = issue.labels.map(&:name) & @options[:exclude_labels]
|
||||
!(var).any?
|
||||
end
|
||||
end
|
||||
issues
|
||||
end
|
||||
|
||||
# @return [Array] filtered issues accourding milestone
|
||||
def filter_by_milestone(filtered_issues, tag_name, all_issues)
|
||||
remove_issues_in_milestones(filtered_issues)
|
||||
unless tag_name.nil?
|
||||
# add missed issues (according milestones)
|
||||
issues_to_add = find_issues_to_add(all_issues, tag_name)
|
||||
|
||||
filtered_issues |= issues_to_add
|
||||
end
|
||||
filtered_issues
|
||||
end
|
||||
|
||||
# Add all issues, that should be in that tag, according milestone
|
||||
#
|
||||
# @param [Array] all_issues
|
||||
# @param [String] tag_name
|
||||
# @return [Array] issues with milestone #tag_name
|
||||
def find_issues_to_add(all_issues, tag_name)
|
||||
all_issues.select do |issue|
|
||||
if issue.milestone.nil?
|
||||
false
|
||||
else
|
||||
# check, that this milestone in tag list:
|
||||
milestone_is_tag = @all_tags.find do |tag|
|
||||
tag.name == issue.milestone.title
|
||||
end
|
||||
|
||||
if milestone_is_tag.nil?
|
||||
false
|
||||
else
|
||||
issue.milestone.title == tag_name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# @return [Array] array with removed issues, that contain milestones with same name as a tag
|
||||
def remove_issues_in_milestones(filtered_issues)
|
||||
filtered_issues.select! do |issue|
|
||||
# leave issues without milestones
|
||||
if issue.milestone.nil?
|
||||
true
|
||||
else
|
||||
# check, that this milestone in tag list:
|
||||
@all_tags.find { |tag| tag.name == issue.milestone.title }.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Method filter issues, that belong only specified tag range
|
||||
# @param [Array] array of issues to filter
|
||||
# @param [Symbol] hash_key key of date value default is :actual_date
|
||||
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
||||
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
||||
# @return [Array] filtered issues
|
||||
def delete_by_time(array, hash_key = :actual_date, older_tag = nil, newer_tag = nil)
|
||||
# in case if not tags specified - return unchanged array
|
||||
return array if older_tag.nil? && newer_tag.nil?
|
||||
|
||||
newer_tag_time = newer_tag && @fetcher.get_time_of_tag(newer_tag)
|
||||
older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag)
|
||||
|
||||
array.select do |req|
|
||||
if req[hash_key]
|
||||
time = Time.parse(req[hash_key]).utc
|
||||
|
||||
tag_in_range_old = tag_newer_old_tag?(older_tag_time, time)
|
||||
|
||||
tag_in_range_new = tag_older_new_tag?(newer_tag_time, time)
|
||||
|
||||
tag_in_range = (tag_in_range_old) && (tag_in_range_new)
|
||||
|
||||
tag_in_range
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def tag_older_new_tag?(newer_tag_time, time)
|
||||
if newer_tag_time.nil?
|
||||
tag_in_range_new = true
|
||||
else
|
||||
tag_in_range_new = time <= newer_tag_time
|
||||
end
|
||||
tag_in_range_new
|
||||
end
|
||||
|
||||
def tag_newer_old_tag?(older_tag_time, t)
|
||||
if older_tag_time.nil?
|
||||
tag_in_range_old = true
|
||||
else
|
||||
tag_in_range_old = t > older_tag_time
|
||||
end
|
||||
tag_in_range_old
|
||||
end
|
||||
|
||||
# Include issues with labels, specified in :include_labels
|
||||
# @param [Array] issues to filter
|
||||
# @return [Array] filtered array of issues
|
||||
def include_issues_by_labels(issues)
|
||||
filtered_issues = filter_by_include_labels(issues)
|
||||
filtered_issues |= filter_wo_labels(issues)
|
||||
filtered_issues
|
||||
end
|
||||
|
||||
# @return [Array] issues without labels or empty array if add_issues_wo_labels is false
|
||||
def filter_wo_labels(issues)
|
||||
if @options[:add_issues_wo_labels]
|
||||
issues_wo_labels = issues.select do |issue|
|
||||
!issue.labels.map(&:name).any?
|
||||
end
|
||||
return issues_wo_labels
|
||||
end
|
||||
[]
|
||||
end
|
||||
|
||||
def filter_by_include_labels(issues)
|
||||
filtered_issues = @options[:include_labels].nil? ? issues : issues.select do |issue|
|
||||
labels = issue.labels.map(&:name) & @options[:include_labels]
|
||||
(labels).any?
|
||||
end
|
||||
filtered_issues
|
||||
end
|
||||
|
||||
# General filtered function
|
||||
#
|
||||
# @param [Array] all_issues
|
||||
# @return [Array] filtered issues
|
||||
def filter_array_by_labels(all_issues)
|
||||
filtered_issues = include_issues_by_labels(all_issues)
|
||||
exclude_issues_by_labels(filtered_issues)
|
||||
end
|
||||
|
||||
# Filter issues according labels
|
||||
# @return [Array] Filtered issues
|
||||
def get_filtered_issues(issues)
|
||||
issues = filter_array_by_labels(issues)
|
||||
puts "Filtered issues: #{issues.count}" if @options[:verbose]
|
||||
issues
|
||||
end
|
||||
|
||||
# This method fetches missing params for PR and filter them by specified options
|
||||
# It include add all PR's with labels from @options[:include_labels] array
|
||||
# And exclude all from :exclude_labels array.
|
||||
# @return [Array] filtered PR's
|
||||
def get_filtered_pull_requests(pull_requests)
|
||||
pull_requests = filter_array_by_labels(pull_requests)
|
||||
pull_requests = filter_merged_pull_requests(pull_requests)
|
||||
puts "Filtered pull requests: #{pull_requests.count}" if @options[:verbose]
|
||||
pull_requests
|
||||
end
|
||||
|
||||
# This method filter only merged PR and
|
||||
# fetch missing required attributes for pull requests
|
||||
# :merged_at - is a date, when issue PR was merged.
|
||||
# More correct to use merged date, rather than closed date.
|
||||
def filter_merged_pull_requests(pull_requests)
|
||||
print "Fetching merged dates...\r" if @options[:verbose]
|
||||
closed_pull_requests = @fetcher.fetch_closed_pull_requests
|
||||
|
||||
pull_requests.each do |pr|
|
||||
fetched_pr = closed_pull_requests.find do |fpr|
|
||||
fpr.number == pr.number
|
||||
end
|
||||
pr[:merged_at] = fetched_pr[:merged_at]
|
||||
closed_pull_requests.delete(fetched_pr)
|
||||
end
|
||||
|
||||
pull_requests.select! do |pr|
|
||||
!pr[:merged_at].nil?
|
||||
end
|
||||
|
||||
pull_requests
|
||||
end
|
||||
end
|
||||
end
|
||||
53
lib/github_changelog_generator/generator/generator_tags.rb
Normal file
53
lib/github_changelog_generator/generator/generator_tags.rb
Normal file
@@ -0,0 +1,53 @@
|
||||
module GitHubChangelogGenerator
|
||||
class Generator
|
||||
# fetch, filter tags, fetch dates and sort them in time order
|
||||
def fetch_and_filter_tags
|
||||
@all_tags = get_filtered_tags(@fetcher.get_all_tags)
|
||||
fetch_tags_dates
|
||||
sort_tags_by_date
|
||||
end
|
||||
|
||||
# Sort all tags by date
|
||||
def sort_tags_by_date
|
||||
puts "Sorting tags..." if @options[:verbose]
|
||||
@all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse!
|
||||
end
|
||||
|
||||
# Detect link, name and time for specified tag.
|
||||
#
|
||||
# @param [Hash] newer_tag newer tag. Can be nil, if it's Unreleased section.
|
||||
# @return [Array] link, name and time of the tag
|
||||
def detect_link_tag_time(newer_tag)
|
||||
# if tag is nil - set current time
|
||||
newer_tag_time = newer_tag.nil? ? Time.new : @fetcher.get_time_of_tag(newer_tag)
|
||||
|
||||
# if it's future release tag - set this value
|
||||
if newer_tag.nil? && @options[:future_release]
|
||||
newer_tag_name = @options[:future_release]
|
||||
newer_tag_link = @options[:future_release]
|
||||
else
|
||||
# put unreleased label if there is no name for the tag
|
||||
newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
|
||||
newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
|
||||
end
|
||||
[newer_tag_link, newer_tag_name, newer_tag_time]
|
||||
end
|
||||
|
||||
# Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags
|
||||
#
|
||||
# @return [Array]
|
||||
def get_filtered_tags(all_tags)
|
||||
all_tags = all_tags
|
||||
filtered_tags = all_tags
|
||||
if @options[:between_tags]
|
||||
@options[:between_tags].each do |tag|
|
||||
unless all_tags.include? tag
|
||||
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
|
||||
end
|
||||
end
|
||||
filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag }
|
||||
end
|
||||
filtered_tags
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,34 +5,32 @@ require_relative "version"
|
||||
|
||||
module GitHubChangelogGenerator
|
||||
class Parser
|
||||
# parse options with optparse
|
||||
def self.parse_options
|
||||
options = {
|
||||
tag1: nil,
|
||||
tag2: nil,
|
||||
dateformat: "%Y-%m-%d",
|
||||
output: "CHANGELOG.md",
|
||||
issues: true,
|
||||
add_issues_wo_labels: true,
|
||||
add_pr_wo_labels: true,
|
||||
pulls: true,
|
||||
filter_issues_by_milestone: true,
|
||||
author: true,
|
||||
unreleased: true,
|
||||
unreleased_label: "Unreleased",
|
||||
compare_link: true,
|
||||
include_labels: %w(bug enhancement),
|
||||
exclude_labels: %w(duplicate question invalid wontfix),
|
||||
max_issues: nil,
|
||||
simple_list: false,
|
||||
verbose: true,
|
||||
options = get_default_options
|
||||
|
||||
merge_prefix: "**Merged pull requests:**",
|
||||
issue_prefix: "**Closed issues:**",
|
||||
bug_prefix: "**Fixed bugs:**",
|
||||
enhancement_prefix: "**Implemented enhancements:**",
|
||||
branch: "origin"
|
||||
}
|
||||
parser = setup_parser(options)
|
||||
|
||||
parser.parse!
|
||||
|
||||
detect_user_and_project(options)
|
||||
|
||||
if !options[:user] || !options[:project]
|
||||
puts parser.banner
|
||||
exit
|
||||
end
|
||||
|
||||
if options[:verbose]
|
||||
puts "Performing task with options:"
|
||||
pp options
|
||||
puts ""
|
||||
end
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
# setup parsing options
|
||||
def self.setup_parser(options)
|
||||
parser = OptionParser.new do |opts|
|
||||
opts.banner = "Usage: github_changelog_generator [options]"
|
||||
opts.on("-u", "--user [USER]", "Username of the owner of target GitHub repo") do |last|
|
||||
@@ -45,7 +43,7 @@ module GitHubChangelogGenerator
|
||||
options[:token] = last
|
||||
end
|
||||
opts.on("-f", "--date-format [FORMAT]", "Date format. Default is %Y-%m-%d") do |last|
|
||||
options[:dateformat] = last
|
||||
options[:date_format] = last
|
||||
end
|
||||
opts.on("-o", "--output [NAME]", "Output file. Default is CHANGELOG.md") do |last|
|
||||
options[:output] = last
|
||||
@@ -86,6 +84,9 @@ module GitHubChangelogGenerator
|
||||
opts.on("--exclude-labels x,y,z", Array, 'Issues with the specified labels will be always excluded from changelog. Default is \'duplicate,question,invalid,wontfix\'') do |list|
|
||||
options[:exclude_labels] = list
|
||||
end
|
||||
opts.on("--between-tags x,y,z", Array, "Change log will be filed only between specified tags") do |list|
|
||||
options[:between_tags] = list
|
||||
end
|
||||
opts.on("--max-issues [NUMBER]", Integer, "Max number of issues to fetch from GitHub. Default is unlimited") do |max|
|
||||
options[:max_issues] = max
|
||||
end
|
||||
@@ -98,6 +99,9 @@ module GitHubChangelogGenerator
|
||||
opts.on("--simple-list", "Create simple list from issues and pull requests. Default is false.") do |v|
|
||||
options[:simple_list] = v
|
||||
end
|
||||
opts.on("--future-release [RELEASE-VERSION]", "Put the unreleased changes in the specified release number.") do |future_release|
|
||||
options[:future_release] = future_release
|
||||
end
|
||||
opts.on("--[no-]verbose", "Run verbosely. Default is true") do |v|
|
||||
options[:verbose] = v
|
||||
end
|
||||
@@ -110,77 +114,120 @@ module GitHubChangelogGenerator
|
||||
exit
|
||||
end
|
||||
end
|
||||
parser
|
||||
end
|
||||
|
||||
parser.parse!
|
||||
|
||||
detect_user_and_project(options)
|
||||
|
||||
if !options[:user] || !options[:project]
|
||||
puts parser.banner
|
||||
exit
|
||||
end
|
||||
|
||||
if ARGV[1]
|
||||
options[:tag1] = ARGV[0]
|
||||
options[:tag2] = ARGV[1]
|
||||
end
|
||||
|
||||
if options[:verbose]
|
||||
puts "Performing task with options:"
|
||||
pp options
|
||||
puts ""
|
||||
end
|
||||
# just get default options
|
||||
def self.get_default_options
|
||||
options = {
|
||||
tag1: nil,
|
||||
tag2: nil,
|
||||
date_format: "%Y-%m-%d",
|
||||
output: "CHANGELOG.md",
|
||||
issues: true,
|
||||
add_issues_wo_labels: true,
|
||||
add_pr_wo_labels: true,
|
||||
pulls: true,
|
||||
filter_issues_by_milestone: true,
|
||||
author: true,
|
||||
unreleased: true,
|
||||
unreleased_label: "Unreleased",
|
||||
compare_link: true,
|
||||
include_labels: %w(bug enhancement),
|
||||
exclude_labels: %w(duplicate question invalid wontfix),
|
||||
max_issues: nil,
|
||||
simple_list: false,
|
||||
verbose: true,
|
||||
merge_prefix: "**Merged pull requests:**",
|
||||
issue_prefix: "**Closed issues:**",
|
||||
bug_prefix: "**Fixed bugs:**",
|
||||
enhancement_prefix: "**Implemented enhancements:**",
|
||||
git_remote: "origin"
|
||||
}
|
||||
|
||||
options
|
||||
end
|
||||
|
||||
# Detects user and project from git
|
||||
def self.detect_user_and_project(options)
|
||||
if ARGV[0] && !ARGV[1]
|
||||
github_site = options[:github_site] ? options[:github_site] : "github.com"
|
||||
options[:user], options[:project] = user_project_from_option(ARGV[0], ARGV[1], options[:github_site])
|
||||
if !options[:user] || !options[:project]
|
||||
if ENV["RUBYLIB"] =~ /ruby-debug-ide/
|
||||
options[:user] = "skywinder"
|
||||
options[:project] = "changelog_test"
|
||||
else
|
||||
remote = `git config --get remote.#{options[:git_remote]}.url`
|
||||
options[:user], options[:project] = user_project_from_remote(remote)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Try to find user and project name from git remote output
|
||||
#
|
||||
# @param [String] output of git remote command
|
||||
# @return [Array] user and project
|
||||
def self.user_project_from_option(arg0, arg1, github_site = nil)
|
||||
user = nil
|
||||
project = nil
|
||||
github_site ||= "github.com"
|
||||
if arg0 && !arg1
|
||||
# this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name
|
||||
match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(ARGV[0])
|
||||
puts arg0
|
||||
match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0)
|
||||
|
||||
begin
|
||||
param = match[2].nil?
|
||||
rescue
|
||||
puts "Can't detect user and name from first parameter: '#{ARGV[0]}' -> exit'"
|
||||
puts "Can't detect user and name from first parameter: '#{arg0}' -> exit'"
|
||||
exit
|
||||
end
|
||||
if param
|
||||
exit
|
||||
else
|
||||
options[:user] = match[1]
|
||||
options[:project] = match[2]
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
if !options[:user] && !options[:project]
|
||||
if ENV["RUBYLIB"] =~ /ruby-debug-ide/
|
||||
options[:user] = "skywinder"
|
||||
options[:project] = "changelog_test"
|
||||
else
|
||||
remote = `git config --get remote.#{options[:branch]}.url`
|
||||
# try to find repo in format:
|
||||
# origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch)
|
||||
# git@github.com:skywinder/Github-Changelog-Generator.git
|
||||
match = /.*(?:[:\/])((?:-|\w|\.)*)\/((?:-|\w|\.)*)(?:\.git).*/.match(remote)
|
||||
|
||||
if match && match[1] && match[2]
|
||||
puts "Detected user:#{match[1]}, project:#{match[2]}"
|
||||
options[:user], options[:project] = match[1], match[2]
|
||||
else
|
||||
# try to find repo in format:
|
||||
# origin https://github.com/skywinder/ChangelogMerger (fetch)
|
||||
# https://github.com/skywinder/ChangelogMerger
|
||||
match = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/.match(remote)
|
||||
if match && match[1] && match[2]
|
||||
puts "Detected user:#{match[1]}, project:#{match[2]}"
|
||||
options[:user], options[:project] = match[1], match[2]
|
||||
end
|
||||
end
|
||||
user = match[1]
|
||||
project = match[2]
|
||||
end
|
||||
end
|
||||
[user, project]
|
||||
end
|
||||
|
||||
# Try to find user and project name from git remote output
|
||||
#
|
||||
# @param [String] output of git remote command
|
||||
# @return [Array] user and project
|
||||
def self.user_project_from_remote(remote)
|
||||
# try to find repo in format:
|
||||
# origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch)
|
||||
# git@github.com:skywinder/Github-Changelog-Generator.git
|
||||
regex1 = /.*(?:[:\/])((?:-|\w|\.)*)\/((?:-|\w|\.)*)(?:\.git).*/
|
||||
|
||||
# try to find repo in format:
|
||||
# origin https://github.com/skywinder/ChangelogMerger (fetch)
|
||||
# https://github.com/skywinder/ChangelogMerger
|
||||
regex2 = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/
|
||||
|
||||
remote_structures = [regex1, regex2]
|
||||
|
||||
user = nil
|
||||
project = nil
|
||||
remote_structures.each do |regex|
|
||||
matches = Regexp.new(regex).match(remote)
|
||||
|
||||
if matches && matches[1] && matches[2]
|
||||
puts "Detected user:#{matches[1]}, project:#{matches[2]}"
|
||||
user = matches[1]
|
||||
project = matches[2]
|
||||
end
|
||||
|
||||
break unless matches.nil?
|
||||
end
|
||||
|
||||
[user, project]
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
remote = "invalid reference to project"
|
||||
p user_project_from_option(ARGV[0], ARGV[1], remote)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
module GitHubChangelogGenerator
|
||||
VERSION = "1.4.0"
|
||||
VERSION = "1.4.1"
|
||||
end
|
||||
|
||||
59
spec/unit/fetcher_spec.rb
Normal file
59
spec/unit/fetcher_spec.rb
Normal file
@@ -0,0 +1,59 @@
|
||||
VALID_TOKEN = "0123456789abcdef"
|
||||
INVALID_TOKEN = "0000000000000000"
|
||||
|
||||
DEFAULT_OPTIONS = { user: "skywinder",
|
||||
project: "changelog_test" }
|
||||
|
||||
def options_with_invalid_token
|
||||
options = DEFAULT_OPTIONS
|
||||
options[:token] = INVALID_TOKEN
|
||||
options
|
||||
end
|
||||
|
||||
describe GitHubChangelogGenerator::Fetcher do
|
||||
before(:all) do
|
||||
@fetcher = GitHubChangelogGenerator::Fetcher.new
|
||||
end
|
||||
|
||||
describe "#fetch_github_token" do
|
||||
token = GitHubChangelogGenerator::Fetcher::CHANGELOG_GITHUB_TOKEN
|
||||
context "when token in ENV exist" do
|
||||
before { stub_const("ENV", ENV.to_hash.merge(token => VALID_TOKEN)) }
|
||||
subject { @fetcher.fetch_github_token }
|
||||
it { is_expected.to eq(VALID_TOKEN) }
|
||||
end
|
||||
context "when token in ENV is nil" do
|
||||
before { stub_const("ENV", ENV.to_hash.merge(token => nil)) }
|
||||
subject { @fetcher.fetch_github_token }
|
||||
it { is_expected.to be_nil }
|
||||
end
|
||||
context "when token in options and ENV is nil" do
|
||||
before do
|
||||
stub_const("ENV", ENV.to_hash.merge(token => nil))
|
||||
@fetcher = GitHubChangelogGenerator::Fetcher.new(token: VALID_TOKEN)
|
||||
end
|
||||
subject { @fetcher.fetch_github_token }
|
||||
it { is_expected.to eq(VALID_TOKEN) }
|
||||
end
|
||||
context "when token in options and ENV specified" do
|
||||
before do
|
||||
stub_const("ENV", ENV.to_hash.merge(token => "no_matter_what"))
|
||||
@fetcher = GitHubChangelogGenerator::Fetcher.new(token: VALID_TOKEN)
|
||||
end
|
||||
subject { @fetcher.fetch_github_token }
|
||||
it { is_expected.to eq(VALID_TOKEN) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#github_fetch_tags" do
|
||||
context "when wrong token provided" do
|
||||
before do
|
||||
options = options_with_invalid_token
|
||||
@fetcher = GitHubChangelogGenerator::Fetcher.new(options)
|
||||
end
|
||||
it "should raise Unauthorized error" do
|
||||
expect { @fetcher.github_fetch_tags [] }.to raise_error Github::Error::Unauthorized
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
29
spec/unit/generator/generator_tags_spec.rb
Normal file
29
spec/unit/generator/generator_tags_spec.rb
Normal file
@@ -0,0 +1,29 @@
|
||||
describe GitHubChangelogGenerator::Generator do
|
||||
describe "#get_filtered_tags" do
|
||||
before(:all) do
|
||||
@generator = GitHubChangelogGenerator::Generator.new
|
||||
end
|
||||
|
||||
context "when between_tags nil" do
|
||||
# before(:each) do
|
||||
# @generator.options = {}
|
||||
# end
|
||||
subject { @generator.get_filtered_tags(%w(1 2 3)) }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(%w(1 2 3)) }
|
||||
end
|
||||
|
||||
context "when between_tags 1" do
|
||||
# before(:each) do
|
||||
# @generator.options = {between_tags: ["1"]}
|
||||
# end
|
||||
|
||||
subject do
|
||||
@generator.instance_variable_set("@options", between_tags: ["1"])
|
||||
@generator.get_filtered_tags(%w(1 2 3))
|
||||
end
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(%w(1)) }
|
||||
end
|
||||
end
|
||||
end
|
||||
60
spec/unit/parser_spec.rb
Normal file
60
spec/unit/parser_spec.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
describe GitHubChangelogGenerator::Parser do
|
||||
describe "#self.user_project_from_remote" do
|
||||
context "when remote is 1" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin https://github.com/skywinder/ActionSheetPicker-3.0 (fetch)") }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when remote is 2" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("https://github.com/skywinder/ActionSheetPicker-3.0") }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when remote is 3" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("https://github.com/skywinder/ActionSheetPicker-3.0") }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when remote is 4" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("origin git@github.com:skywinder/ActionSheetPicker-3.0.git (fetch)") }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when remote is invalid" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_remote("some invalid text") }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array([nil, nil]) }
|
||||
end
|
||||
end
|
||||
describe "#self.user_project_from_option" do
|
||||
# context "when option is invalid" do
|
||||
# it("should exit") { expect { GitHubChangelogGenerator::Parser.user_project_from_option("blah", nil) }.to raise_error(SystemExit) }
|
||||
# end
|
||||
|
||||
context "when option is valid" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil) }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when option nil" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_option(nil, nil) }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array([nil, nil]) }
|
||||
end
|
||||
context "when site is nil" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil, nil) }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when site is valid" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", nil, "https://codeclimate.com") }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array(["skywinder", "ActionSheetPicker-3.0"]) }
|
||||
end
|
||||
context "when second arg is not nil" do
|
||||
subject { GitHubChangelogGenerator::Parser.user_project_from_option("skywinder/ActionSheetPicker-3.0", "blah", nil) }
|
||||
it { is_expected.to be_a(Array) }
|
||||
it { is_expected.to match_array([nil, nil]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user