Merge branch 'feature/add-rspec'

This commit is contained in:
Petr Korolev 2015-03-26 16:34:18 +02:00
commit 990b32cf71
13 changed files with 289 additions and 182 deletions

2
.rspec Normal file
View File

@ -0,0 +1,2 @@
--color
--require spec_helper

2
.rubocop.yml Normal file
View File

@ -0,0 +1,2 @@
Metrics/LineLength:
Enabled: false

View File

@ -1,11 +1,13 @@
---
sudo: false
cache: bundler
language: ruby language: ruby
before_install:
- gem update --system
- gem --version
rvm: rvm:
- 2.1.0 - 2.1.0
gemfile:
- Gemfile script:
- bundle exec rake
notifications: notifications:
email: email:

View File

@ -1,4 +1,11 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem 'rake'
gem 'github_api' gem 'github_api'
gem 'colorize' gem 'colorize'
gem 'rake'
group :test do
gem 'rspec'
gem 'rubocop'
end

View File

@ -2,9 +2,13 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
addressable (2.3.6) addressable (2.3.6)
ast (2.0.0)
astrolabe (1.3.0)
parser (>= 2.2.0.pre.3, < 3.0)
colorize (0.7.4) colorize (0.7.4)
descendants_tracker (0.0.4) descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1) thread_safe (~> 0.3, >= 0.3.1)
diff-lcs (1.2.5)
faraday (0.9.0) faraday (0.9.0)
multipart-post (>= 1.2, < 3) multipart-post (>= 1.2, < 3)
github_api (0.12.2) github_api (0.12.2)
@ -29,8 +33,32 @@ GEM
multi_json (~> 1.3) multi_json (~> 1.3)
multi_xml (~> 0.5) multi_xml (~> 0.5)
rack (~> 1.2) rack (~> 1.2)
parser (2.2.0.3)
ast (>= 1.1, < 3.0)
powerpack (0.1.0)
rack (1.5.2) rack (1.5.2)
rainbow (2.0.0)
rake (10.4.2) rake (10.4.2)
rspec (3.2.0)
rspec-core (~> 3.2.0)
rspec-expectations (~> 3.2.0)
rspec-mocks (~> 3.2.0)
rspec-core (3.2.2)
rspec-support (~> 3.2.0)
rspec-expectations (3.2.0)
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)
astrolabe (~> 1.3)
parser (>= 2.2.0.1, < 3.0)
powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
ruby-progressbar (1.7.5)
thread_safe (0.3.4) thread_safe (0.3.4)
PLATFORMS PLATFORMS
@ -40,3 +68,5 @@ DEPENDENCIES
colorize colorize
github_api github_api
rake rake
rspec
rubocop

View File

@ -1,9 +1,7 @@
require "rake/testtask" require 'rubocop/rake_task'
require 'rspec/core/rake_task'
task :default => [:test] RuboCop::RakeTask.new
RSpec::Core::RakeTask.new(:rspec)
Rake::TestTask.new do |t| task default: [:rubocop, :rspec]
t.verbose = true
t.libs.push("demo", "test")
t.pattern = "test/**/*_test.rb"
end

View File

@ -7,7 +7,7 @@ SPEC_TYPE = 'gemspec'
:minor :minor
:patch :patch
@options = {:dry_run => false, :bump_number => :patch} @options = { dry_run: false, bump_number: :patch }
OptionParser.new { |opts| OptionParser.new { |opts|
opts.banner = 'Usage: bump.rb [options]' opts.banner = 'Usage: bump.rb [options]'
@ -15,13 +15,13 @@ OptionParser.new { |opts|
opts.on('-d', '--dry-run', 'Dry run') do |v| opts.on('-d', '--dry-run', 'Dry run') do |v|
@options[:dry_run] = v @options[:dry_run] = v
end end
opts.on('-a', '--major', 'Bump major version') do |v| opts.on('-a', '--major', 'Bump major version') do |_v|
@options[:bump_number] = :major @options[:bump_number] = :major
end end
opts.on('-m', '--minor', 'Bump minor version') do |v| opts.on('-m', '--minor', 'Bump minor version') do |_v|
@options[:bump_number] = :minor @options[:bump_number] = :minor
end end
opts.on('-p', '--patch', 'Bump patch version') do |v| opts.on('-p', '--patch', 'Bump patch version') do |_v|
@options[:bump_number] = :patch @options[:bump_number] = :patch
end end
opts.on('-r', '--revert', 'Revert last bump') do |v| opts.on('-r', '--revert', 'Revert last bump') do |v|
@ -32,7 +32,7 @@ OptionParser.new { |opts|
p @options p @options
def check_repo_is_clean_or_dry_run def check_repo_is_clean_or_dry_run
value =%x[#{'git status --porcelain'}] value = `#{'git status --porcelain'}`
if value.empty? if value.empty?
puts 'Repo is clean -> continue' puts 'Repo is clean -> continue'
@ -46,7 +46,6 @@ def check_repo_is_clean_or_dry_run
end end
end end
def find_spec_file def find_spec_file
list_of_specs = execute_line("find . -name '*.#{SPEC_TYPE}'") list_of_specs = execute_line("find . -name '*.#{SPEC_TYPE}'")
arr = list_of_specs.split("\n") arr = list_of_specs.split("\n")
@ -61,18 +60,17 @@ def find_spec_file
spec_file = arr[0] spec_file = arr[0]
else else
puts 'Which spec should be used?' puts 'Which spec should be used?'
arr.each_with_index { |file, index| puts "#{index+1}. #{file}" } arr.each_with_index { |file, index| puts "#{index + 1}. #{file}" }
input_index = Integer(gets.chomp) input_index = Integer(gets.chomp)
spec_file = arr[input_index-1] spec_file = arr[input_index - 1]
end end
if spec_file == nil if spec_file.nil?
puts "Can't find specified spec file -> exit" puts "Can't find specified spec file -> exit"
exit exit
end end
spec_file.sub('./', '') spec_file.sub('./', '')
end end
def find_current_gem_file def find_current_gem_file
@ -89,24 +87,23 @@ def find_current_gem_file
spec_file = arr[0] spec_file = arr[0]
else else
puts 'Which spec should be used?' puts 'Which spec should be used?'
arr.each_with_index { |file, index| puts "#{index+1}. #{file}" } arr.each_with_index { |file, index| puts "#{index + 1}. #{file}" }
input_index = Integer(gets.chomp) input_index = Integer(gets.chomp)
spec_file = arr[input_index-1] spec_file = arr[input_index - 1]
end end
if spec_file == nil if spec_file.nil?
puts "Can't find specified spec file -> exit" puts "Can't find specified spec file -> exit"
exit exit
end end
spec_file.sub('./', '') spec_file.sub('./', '')
end end
def find_version_in_podspec(podspec) def find_version_in_podspec(podspec)
readme = File.read(podspec) readme = File.read(podspec)
#try to find version in format 1.22.333 # try to find version in format 1.22.333
re = /(\d+)\.(\d+)\.(\d+)/m re = /(\d+)\.(\d+)\.(\d+)/m
match_result = re.match(readme) match_result = re.match(readme)
@ -117,12 +114,12 @@ def find_version_in_podspec(podspec)
end end
puts "Found version #{match_result[0]}" puts "Found version #{match_result[0]}"
return match_result[0], match_result.captures [match_result[0], match_result.captures]
end end
def bump_version(versions_array) def bump_version(versions_array)
bumped_result = versions_array.dup bumped_result = versions_array.dup
bumped_result.map! { |x| x.to_i } bumped_result.map!(&:to_i)
case @options[:bump_number] case @options[:bump_number]
when :major when :major
@ -135,10 +132,9 @@ def bump_version(versions_array)
when :patch when :patch
bumped_result[2] += 1 bumped_result[2] += 1
else else
raise('unknown bump_number') fail('unknown bump_number')
end end
bumped_version = bumped_result.join('.') bumped_version = bumped_result.join('.')
puts "Bump version: #{versions_array.join('.')} -> #{bumped_version}" puts "Bump version: #{versions_array.join('.')} -> #{bumped_version}"
bumped_version bumped_version
@ -157,7 +153,7 @@ def execute_line_if_not_dry_run(line)
nil nil
else else
puts line puts line
value = %x[#{line}] value = `#{line}`
puts value puts value
check_exit_status(value) check_exit_status(value)
value value
@ -165,14 +161,13 @@ def execute_line_if_not_dry_run(line)
end end
def check_exit_status(output) def check_exit_status(output)
if $?.exitstatus != 0 if $CHILD_STATUS.exitstatus != 0
puts "Output:\n#{output}\nExit status = #{$?.exitstatus} ->Terminate script." puts "Output:\n#{output}\nExit status = #{$CHILD_STATUS.exitstatus} ->Terminate script."
exit exit
end end
end end
def run_bumping_script def run_bumping_script
check_repo_is_clean_or_dry_run check_repo_is_clean_or_dry_run
spec_file = find_spec_file spec_file = find_spec_file
result, versions_array = find_version_in_podspec(spec_file) result, versions_array = find_version_in_podspec(spec_file)
@ -198,7 +193,6 @@ def run_bumping_script
gem = find_current_gem_file gem = find_current_gem_file
execute_line_if_not_dry_run("gem push #{gem}") execute_line_if_not_dry_run("gem push #{gem}")
# execute_line_if_not_dry_run("pod trunk push #{spec_file}") # execute_line_if_not_dry_run("pod trunk push #{spec_file}")
end end
def revert_last_bump def revert_last_bump
@ -216,7 +210,7 @@ def revert_last_bump
execute_line_if_not_dry_run("git push --delete origin #{result}") execute_line_if_not_dry_run("git push --delete origin #{result}")
end end
if __FILE__ == $0 if __FILE__ == $PROGRAM_NAME
if @options[:revert] if @options[:revert]
revert_last_bump revert_last_bump

View File

@ -5,28 +5,27 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'github_changelog_generator/version' require 'github_changelog_generator/version'
Gem::Specification.new do |spec| Gem::Specification.new do |spec|
spec.name = "github_changelog_generator" spec.name = 'github_changelog_generator'
spec.version = GitHubChangelogGenerator::VERSION spec.version = GitHubChangelogGenerator::VERSION
spec.default_executable = "github_changelog_generator" spec.default_executable = 'github_changelog_generator'
spec.required_ruby_version = '>= 1.9.3' spec.required_ruby_version = '>= 1.9.3'
spec.authors = ["Petr Korolev"] spec.authors = ['Petr Korolev']
spec.email = %q{sky4winder+github_changelog_generator@gmail.com} spec.email = 'sky4winder+github_changelog_generator@gmail.com'
spec.date = `date +"%Y-%m-%d"`.strip! spec.date = `date +"%Y-%m-%d"`.strip!
spec.summary = %q{Script, that automatically generate changelog from your tags, issues, labels and pull requests.} spec.summary = 'Script, that automatically generate changelog from your tags, issues, labels and pull requests.'
spec.description = %q{Changelog generation has never been so easy. Fully automate changelog generation - this gem generate change log file based on tags, issues and merged pull requests from Github issue tracker.} spec.description = 'Changelog generation has never been so easy. Fully automate changelog generation - this gem generate change log file based on tags, issues and merged pull requests from Github issue tracker.'
spec.homepage = %q{https://github.com/skywinder/Github-Changelog-Generator} spec.homepage = 'https://github.com/skywinder/Github-Changelog-Generator'
spec.license = "MIT" spec.license = 'MIT'
spec.files = `git ls-files -z`.split("\x0") spec.files = `git ls-files -z`.split("\x0")
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
spec.require_paths = ["lib"] spec.require_paths = ['lib']
spec.add_development_dependency "bundler", "~> 1.7" spec.add_development_dependency 'bundler', '~> 1.7'
spec.add_development_dependency "rake", "~> 10.0" spec.add_development_dependency 'rake', '~> 10.0'
spec.add_runtime_dependency(%q<github_api>, ["~> 0.12"])
spec.add_runtime_dependency(%q<colorize>, ["~> 0.7"])
spec.add_runtime_dependency('github_api', ['~> 0.12'])
spec.add_runtime_dependency('colorize', ['~> 0.7'])
end end

View File

@ -11,20 +11,18 @@ require_relative 'github_changelog_generator/version'
module GitHubChangelogGenerator module GitHubChangelogGenerator
class ChangelogGenerator class ChangelogGenerator
attr_accessor :options, :all_tags, :github attr_accessor :options, :all_tags, :github
PER_PAGE_NUMBER = 30 PER_PAGE_NUMBER = 30
GH_RATE_LIMIT_EXCEEDED_MSG = 'Warning: GitHub API rate limit exceed (5000 per hour), change log may not ' + GH_RATE_LIMIT_EXCEEDED_MSG = 'Warning: GitHub API rate limit exceed (5000 per hour), change log may not ' \
'contain some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument' 'contain some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument'
def initialize def initialize
@options = Parser.parse_options @options = Parser.parse_options
fetch_github_token fetch_github_token
github_options = {per_page: PER_PAGE_NUMBER} github_options = { per_page: PER_PAGE_NUMBER }
github_options[:oauth_token] = @github_token unless @github_token.nil? github_options[:oauth_token] = @github_token unless @github_token.nil?
github_options[:endpoint] = options[:github_endpoint] unless options[:github_endpoint].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[:site] = options[:github_endpoint] unless options[:github_site].nil?
@ -37,17 +35,17 @@ module GitHubChangelogGenerator
@generator = Generator.new(@options) @generator = Generator.new(@options)
@all_tags = self.get_all_tags @all_tags = get_all_tags
@issues, @pull_requests = self.fetch_issues_and_pull_requests @issues, @pull_requests = fetch_issues_and_pull_requests
if @options[:pulls] if @options[:pulls]
@pull_requests = self.get_filtered_pull_requests @pull_requests = get_filtered_pull_requests
else else
@pull_requests = [] @pull_requests = []
end end
if @options[:issues] if @options[:issues]
@issues = self.get_filtered_issues @issues = get_filtered_issues
else else
@issues = [] @issues = []
end end
@ -58,7 +56,6 @@ module GitHubChangelogGenerator
end end
def detect_actual_closed_dates def detect_actual_closed_dates
if @options[:verbose] if @options[:verbose]
print "Fetching closed dates for issues...\r" print "Fetching closed dates for issues...\r"
end end
@ -76,7 +73,7 @@ module GitHubChangelogGenerator
find_closed_date_by_commit(pull_request) find_closed_date_by_commit(pull_request)
} }
} }
threads.each { |thr| thr.join } threads.each(&:join)
if @options[:verbose] if @options[:verbose]
puts 'Fetching closed dates for issues: Done!' puts 'Fetching closed dates for issues: Done!'
@ -85,7 +82,7 @@ module GitHubChangelogGenerator
def find_closed_date_by_commit(issue) def find_closed_date_by_commit(issue)
unless issue['events'].nil? unless issue['events'].nil?
#if it's PR -> then find "merged event", in case of usual issue -> fond closed date # if it's PR -> then find "merged event", in case of usual issue -> fond closed date
compare_string = issue[:merged_at].nil? ? 'closed' : 'merged' compare_string = issue[:merged_at].nil? ? 'closed' : 'merged'
# reverse! - to find latest closed event. (event goes in date order) # reverse! - to find latest closed event. (event goes in date order)
issue['events'].reverse!.each { |event| issue['events'].reverse!.each { |event|
@ -105,7 +102,7 @@ module GitHubChangelogGenerator
end end
} }
end end
#TODO: assert issues, that remain without 'actual_date' hash for some reason. # TODO: assert issues, that remain without 'actual_date' hash for some reason.
end end
def print_json(json) def print_json(json)
@ -118,7 +115,7 @@ module GitHubChangelogGenerator
end end
pull_requests = [] pull_requests = []
begin begin
response = @github.pull_requests.list @options[:user], @options[:project], :state => 'closed' response = @github.pull_requests.list @options[:user], @options[:project], state: 'closed'
page_i = 0 page_i = 0
response.each_page do |page| response.each_page do |page|
page_i += PER_PAGE_NUMBER page_i += PER_PAGE_NUMBER
@ -134,7 +131,8 @@ module GitHubChangelogGenerator
@pull_requests.each { |pr| @pull_requests.each { |pr|
fetched_pr = pull_requests.find { |fpr| fetched_pr = pull_requests.find { |fpr|
fpr.number == pr.number } fpr.number == pr.number
}
pr[:merged_at] = fetched_pr[:merged_at] pr[:merged_at] = fetched_pr[:merged_at]
pull_requests.delete(fetched_pr) pull_requests.delete(fetched_pr)
} }
@ -142,38 +140,34 @@ module GitHubChangelogGenerator
if @options[:verbose] if @options[:verbose]
puts 'Fetching merged dates... Done!' puts 'Fetching merged dates... Done!'
end end
end end
def get_filtered_pull_requests def get_filtered_pull_requests
fetch_merged_at_pull_requests
self.fetch_merged_at_pull_requests filtered_pull_requests = @pull_requests.select { |pr| !pr[:merged_at].nil? }
filtered_pull_requests = @pull_requests.select { |pr| pr[:merged_at] != nil }
unless @options[:include_labels].nil? unless @options[:include_labels].nil?
filtered_pull_requests = @pull_requests.select { |issue| filtered_pull_requests = @pull_requests.select { |issue|
#add all labels from @options[:incluse_labels] array # add all labels from @options[:incluse_labels] array
(issue.labels.map { |label| label.name } & @options[:include_labels]).any? (issue.labels.map(&:name) & @options[:include_labels]).any?
} }
end end
unless @options[:exclude_labels].nil? unless @options[:exclude_labels].nil?
filtered_pull_requests = filtered_pull_requests.select { |issue| filtered_pull_requests = filtered_pull_requests.select { |issue|
#delete all labels from @options[:exclude_labels] array # delete all labels from @options[:exclude_labels] array
!(issue.labels.map { |label| label.name } & @options[:exclude_labels]).any? !(issue.labels.map(&:name) & @options[:exclude_labels]).any?
} }
end end
if @options[:add_issues_wo_labels] if @options[:add_issues_wo_labels]
issues_wo_labels = @pull_requests.select { issues_wo_labels = @pull_requests.select { |issue|
# add issues without any labels !issue.labels.map(&:name).any?
|issue| !issue.labels.map { |label| label.name }.any?
} }
filtered_pull_requests |= issues_wo_labels filtered_pull_requests |= issues_wo_labels
end end
if @options[:verbose] if @options[:verbose]
puts "Filtered pull requests: #{filtered_pull_requests.count}" puts "Filtered pull requests: #{filtered_pull_requests.count}"
end end
@ -182,16 +176,15 @@ module GitHubChangelogGenerator
end end
def compund_changelog def compund_changelog
log = "# Change Log\n\n" log = "# Change Log\n\n"
if @options[:unreleased_only] if @options[:unreleased_only]
log += self.generate_log_between_tags(self.all_tags[0], nil) log += generate_log_between_tags(all_tags[0], nil)
elsif @options[:tag1] and @options[:tag2] elsif @options[:tag1] and @options[:tag2]
tag1 = @options[:tag1] tag1 = @options[:tag1]
tag2 = @options[:tag2] tag2 = @options[:tag2]
tags_strings = [] tags_strings = []
self.all_tags.each { |x| tags_strings.push(x['name']) } all_tags.each { |x| tags_strings.push(x['name']) }
if tags_strings.include?(tag1) if tags_strings.include?(tag1)
if tags_strings.include?(tag2) if tags_strings.include?(tag2)
@ -199,7 +192,7 @@ module GitHubChangelogGenerator
hash = Hash[to_a] hash = Hash[to_a]
index1 = hash[tag1] index1 = hash[tag1]
index2 = hash[tag2] index2 = hash[tag2]
log += self.generate_log_between_tags(self.all_tags[index1], self.all_tags[index2]) log += generate_log_between_tags(all_tags[index1], all_tags[index2])
else else
puts "Can't find tag #{tag2} -> exit" puts "Can't find tag #{tag2} -> exit"
exit exit
@ -209,7 +202,7 @@ module GitHubChangelogGenerator
exit exit
end end
else else
log += self.generate_log_for_all_tags log += generate_log_for_all_tags
end end
log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*" log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
@ -218,38 +211,35 @@ module GitHubChangelogGenerator
File.open(output_filename, 'w') { |file| file.write(log) } File.open(output_filename, 'w') { |file| file.write(log) }
puts 'Done!' puts 'Done!'
puts "Generated log placed in #{`pwd`.strip!}/#{output_filename}" puts "Generated log placed in #{`pwd`.strip!}/#{output_filename}"
end end
def generate_log_for_all_tags def generate_log_for_all_tags
fetch_tags_dates fetch_tags_dates
if @options[:verbose] if @options[:verbose]
puts "Sorting tags.." puts 'Sorting tags..'
end end
@all_tags.sort_by! { |x| self.get_time_of_tag(x) }.reverse! @all_tags.sort_by! { |x| get_time_of_tag(x) }.reverse!
if @options[:verbose] if @options[:verbose]
puts "Generating log.." puts 'Generating log..'
end end
log = '' log = ''
if @options[:unreleased] && @all_tags.count != 0 if @options[:unreleased] && @all_tags.count != 0
unreleased_log = self.generate_log_between_tags(self.all_tags[0], nil) unreleased_log = generate_log_between_tags(all_tags[0], nil)
if unreleased_log if unreleased_log
log += unreleased_log log += unreleased_log
end end
end end
(1 ... self.all_tags.size).each { |index| (1...all_tags.size).each { |index|
log += self.generate_log_between_tags(self.all_tags[index], self.all_tags[index-1]) log += generate_log_between_tags(all_tags[index], all_tags[index - 1])
} }
if @all_tags.count != 0 if @all_tags.count != 0
log += generate_log_between_tags(nil, self.all_tags.last) log += generate_log_between_tags(nil, all_tags.last)
end end
log log
@ -267,18 +257,17 @@ module GitHubChangelogGenerator
@all_tags.each { |tag| @all_tags.each { |tag|
# explicit set @tag_times_hash to write data safety. # explicit set @tag_times_hash to write data safety.
threads << Thread.new { threads << Thread.new {
self.get_time_of_tag(tag, @tag_times_hash) get_time_of_tag(tag, @tag_times_hash)
if @options[:verbose] if @options[:verbose]
print "Fetching tags dates: #{i+1}/#{all}\r" print "Fetching tags dates: #{i + 1}/#{all}\r"
i+=1 i += 1
end end
} }
} }
print " \r" print " \r"
threads.each { |thr| thr.join } threads.each(&:join)
if @options[:verbose] if @options[:verbose]
puts "Fetching tags dates: #{i} Done!" puts "Fetching tags dates: #{i} Done!"
@ -292,7 +281,6 @@ module GitHubChangelogGenerator
end end
def get_all_tags def get_all_tags
if @options[:verbose] if @options[:verbose]
print "Fetching tags...\r" print "Fetching tags...\r"
end end
@ -328,12 +316,11 @@ module GitHubChangelogGenerator
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 unless env_var
puts "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found.".yellow puts 'Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found.'.yellow
puts "This script can make only 50 requests to GitHub API per hour without token!".yellow puts 'This script can make only 50 requests to GitHub API per hour without token!'.yellow
end end
@github_token ||= env_var @github_token ||= env_var
end end
def generate_log_between_tags(older_tag, newer_tag) def generate_log_between_tags(older_tag, newer_tag)
@ -345,7 +332,7 @@ module GitHubChangelogGenerator
older_tag_name = older_tag.nil? ? nil : older_tag['name'] older_tag_name = older_tag.nil? ? nil : older_tag['name']
if @options[:filter_issues_by_milestone] if @options[:filter_issues_by_milestone]
#delete excess irrelevant issues (according milestones) # delete excess irrelevant issues (according milestones)
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues) 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) filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
end end
@ -355,7 +342,7 @@ module GitHubChangelogGenerator
return '' return ''
end end
self.create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name) create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
end end
def filter_by_milestone(filtered_issues, newer_tag_name, src_array) def filter_by_milestone(filtered_issues, newer_tag_name, src_array)
@ -364,18 +351,18 @@ module GitHubChangelogGenerator
if issue.milestone.nil? if issue.milestone.nil?
true true
else else
#check, that this milestone in tag list: # check, that this milestone in tag list:
@all_tags.find { |tag| tag.name == issue.milestone.title }.nil? @all_tags.find { |tag| tag.name == issue.milestone.title }.nil?
end end
} }
unless newer_tag_name.nil? unless newer_tag_name.nil?
#add missed issues (according milestones) # add missed issues (according milestones)
issues_to_add = src_array.select { |issue| issues_to_add = src_array.select { |issue|
if issue.milestone.nil? if issue.milestone.nil?
false false
else else
#check, that this milestone in tag list: # check, that this milestone in tag list:
milestone_is_tag = @all_tags.find { |tag| milestone_is_tag = @all_tags.find { |tag|
tag.name == issue.milestone.title tag.name == issue.milestone.title
} }
@ -394,11 +381,10 @@ module GitHubChangelogGenerator
end end
def delete_by_time(array, hash_key, older_tag = nil, newer_tag = nil) def delete_by_time(array, hash_key, older_tag = nil, newer_tag = nil)
fail 'At least one of the tags should be not nil!' if older_tag.nil? && newer_tag.nil?
raise 'At least one of the tags should be not nil!' if (older_tag.nil? && newer_tag.nil?) newer_tag_time = get_time_of_tag(newer_tag)
older_tag_time = get_time_of_tag(older_tag)
newer_tag_time = self.get_time_of_tag(newer_tag)
older_tag_time = self.get_time_of_tag(older_tag)
array.select { |req| array.select { |req|
if req[hash_key] if req[hash_key]
@ -416,7 +402,6 @@ module GitHubChangelogGenerator
tag_in_range_new = t <= newer_tag_time tag_in_range_new = t <= newer_tag_time
end end
tag_in_range = (tag_in_range_old) && (tag_in_range_new) tag_in_range = (tag_in_range_old) && (tag_in_range_new)
tag_in_range tag_in_range
@ -431,8 +416,7 @@ module GitHubChangelogGenerator
# @param [String] older_tag_name # @param [String] older_tag_name
# @return [String] # @return [String]
def create_log(pull_requests, issues, newer_tag, older_tag_name = nil) def create_log(pull_requests, issues, newer_tag, older_tag_name = nil)
newer_tag_time = newer_tag.nil? ? nil : get_time_of_tag(newer_tag)
newer_tag_time = newer_tag.nil? ? nil : self.get_time_of_tag(newer_tag)
newer_tag_name = newer_tag.nil? ? nil : newer_tag['name'] newer_tag_name = newer_tag.nil? ? nil : newer_tag['name']
github_site = options[:github_site] || 'https://github.com' github_site = options[:github_site] || 'https://github.com'
@ -454,7 +438,7 @@ module GitHubChangelogGenerator
# Generate issues: # Generate issues:
issues_a = [] issues_a = []
enhancement_a = [] enhancement_a = []
bugs_a =[] bugs_a = []
issues.each { |dict| issues.each { |dict|
added = false added = false
@ -504,8 +488,7 @@ module GitHubChangelogGenerator
end end
def generate_header(log, newer_tag_name, newer_tag_name2, newer_tag_time, older_tag_name, project_url) def generate_header(log, newer_tag_name, newer_tag_name2, newer_tag_time, older_tag_name, project_url)
# Generate date string:
#Generate date string:
time_string = newer_tag_time.strftime @options[:format] time_string = newer_tag_time.strftime @options[:format]
# Generate tag name and link # Generate tag name and link
@ -524,7 +507,6 @@ module GitHubChangelogGenerator
end end
def get_time_of_tag(tag_name, tag_times_hash = @tag_times_hash) def get_time_of_tag(tag_name, tag_times_hash = @tag_times_hash)
if tag_name.nil? if tag_name.nil?
return nil return nil
end end
@ -543,40 +525,36 @@ module GitHubChangelogGenerator
end end
def get_filtered_issues def get_filtered_issues
issues = @issues issues = @issues
filtered_issues = issues filtered_issues = issues
unless @options[:include_labels].nil? unless @options[:include_labels].nil?
filtered_issues = issues.select { |issue| filtered_issues = issues.select { |issue|
#add all labels from @options[:incluse_labels] array # add all labels from @options[:incluse_labels] array
(issue.labels.map { |label| label.name } & @options[:include_labels]).any? (issue.labels.map(&:name) & @options[:include_labels]).any?
} }
end end
unless @options[:exclude_labels].nil? unless @options[:exclude_labels].nil?
filtered_issues = filtered_issues.select { |issue| filtered_issues = filtered_issues.select { |issue|
#delete all labels from @options[:exclude_labels] array # delete all labels from @options[:exclude_labels] array
!(issue.labels.map { |label| label.name } & @options[:exclude_labels]).any? !(issue.labels.map(&:name) & @options[:exclude_labels]).any?
} }
end end
if @options[:add_issues_wo_labels] if @options[:add_issues_wo_labels]
issues_wo_labels = issues.select { issues_wo_labels = issues.select { |issue|
# add issues without any labels !issue.labels.map(&:name).any?
|issue| !issue.labels.map { |label| label.name }.any?
} }
filtered_issues |= issues_wo_labels filtered_issues |= issues_wo_labels
end end
if @options[:verbose] if @options[:verbose]
puts "Filtered issues: #{filtered_issues.count}" puts "Filtered issues: #{filtered_issues.count}"
end end
filtered_issues filtered_issues
end end
def fetch_issues_and_pull_requests def fetch_issues_and_pull_requests
@ -607,12 +585,12 @@ module GitHubChangelogGenerator
# remove pull request from issues: # remove pull request from issues:
issues_wo_pr = issues.select { |x| issues_wo_pr = issues.select { |x|
x.pull_request == nil x.pull_request.nil?
} }
pull_requests = issues.select { |x| pull_requests = issues.select { |x|
x.pull_request != nil !x.pull_request.nil?
} }
return issues_wo_pr, pull_requests [issues_wo_pr, pull_requests]
end end
def fetch_event_for_issues_and_pr def fetch_event_for_issues_and_pr
@ -623,8 +601,6 @@ module GitHubChangelogGenerator
# Async fetching events: # Async fetching events:
fetch_events_async(@issues + @pull_requests) fetch_events_async(@issues + @pull_requests)
end end
def fetch_events_async(issues) def fetch_events_async(issues)
@ -640,27 +616,24 @@ module GitHubChangelogGenerator
puts GH_RATE_LIMIT_EXCEEDED_MSG.yellow puts GH_RATE_LIMIT_EXCEEDED_MSG.yellow
end end
issue[:events] = obj.body issue[:events] = obj.body
print "Fetching events for issues and PR: #{i+1}/#{@issues.count + @pull_requests.count}\r" print "Fetching events for issues and PR: #{i + 1}/#{@issues.count + @pull_requests.count}\r"
i +=1 i += 1
} }
} }
threads.each { |thr| thr.join } threads.each(&:join)
threads = [] threads = []
} }
#to clear line from prev print # to clear line from prev print
print " \r" print " \r"
if @options[:verbose] if @options[:verbose]
puts "Fetching events for issues and PR: #{i} Done!" puts "Fetching events for issues and PR: #{i} Done!"
end end
end end
end end
if __FILE__ == $0 if __FILE__ == $PROGRAM_NAME
GitHubChangelogGenerator::ChangelogGenerator.new.compund_changelog GitHubChangelogGenerator::ChangelogGenerator.new.compund_changelog
end end
end end

View File

@ -1,19 +1,18 @@
module GitHubChangelogGenerator module GitHubChangelogGenerator
class Generator class Generator
def initialize(options = nil) def initialize(options = nil)
@options = options @options = options
end end
def get_string_for_issue(issue) def get_string_for_issue(issue)
encapsulated_title = self.encapsulate_string issue[:title] encapsulated_title = encapsulate_string issue[:title]
title_with_number = "#{encapsulated_title} [\\##{issue[:number]}](#{issue.html_url})" title_with_number = "#{encapsulated_title} [\\##{issue[:number]}](#{issue.html_url})"
unless issue.pull_request.nil? unless issue.pull_request.nil?
if @options[:author] if @options[:author]
if issue.user.nil? if issue.user.nil?
title_with_number += " ({Null user})" title_with_number += ' ({Null user})'
else else
title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))" title_with_number += " ([#{issue.user.login}](#{issue.user.html_url}))"
end end
@ -23,17 +22,14 @@ module GitHubChangelogGenerator
end end
def encapsulate_string(string) def encapsulate_string(string)
string.gsub! '\\', '\\\\' string.gsub! '\\', '\\\\'
encpas_chars = %w(> * _ \( \) [ ] #) encpas_chars = %w(> * _ \( \) [ ] #)
encpas_chars.each { |char| encpas_chars.each do |char|
string.gsub! char, "\\#{char}" string.gsub! char, "\\#{char}"
} end
string string
end end
end end
end end

View File

@ -6,32 +6,31 @@ require_relative 'version'
module GitHubChangelogGenerator module GitHubChangelogGenerator
class Parser class Parser
def self.parse_options def self.parse_options
options = { options = {
:tag1 => nil, tag1: nil,
:tag2 => nil, tag2: nil,
:format => '%Y-%m-%d', format: '%Y-%m-%d',
:output => 'CHANGELOG.md', output: 'CHANGELOG.md',
:exclude_labels => %w(duplicate question invalid wontfix), exclude_labels: %w(duplicate question invalid wontfix),
:pulls => true, pulls: true,
:issues => true, issues: true,
:verbose => true, verbose: true,
:add_issues_wo_labels => true, add_issues_wo_labels: true,
:add_pr_wo_labels => true, add_pr_wo_labels: true,
:merge_prefix => '**Merged pull requests:**', merge_prefix: '**Merged pull requests:**',
:issue_prefix => '**Closed issues:**', issue_prefix: '**Closed issues:**',
:bug_prefix => '**Fixed bugs:**', bug_prefix: '**Fixed bugs:**',
:enhancement_prefix => '**Implemented enhancements:**', enhancement_prefix: '**Implemented enhancements:**',
:author => true, author: true,
:filter_issues_by_milestone => true, filter_issues_by_milestone: true,
:max_issues => nil, max_issues: nil,
:compare_link => true, compare_link: true,
:unreleased => true, unreleased: true,
:unreleased_label => 'Unreleased', unreleased_label: 'Unreleased',
:branch => 'origin' branch: 'origin'
} }
parser = OptionParser.new { |opts| parser = OptionParser.new do |opts|
opts.banner = 'Usage: github_changelog_generator [options]' opts.banner = 'Usage: github_changelog_generator [options]'
opts.on('-u', '--user [USER]', 'Username of the owner of target GitHub repo') do |last| opts.on('-u', '--user [USER]', 'Username of the owner of target GitHub repo') do |last|
options[:user] = last options[:user] = last
@ -99,7 +98,7 @@ module GitHubChangelogGenerator
opts.on('--[no-]verbose', 'Run verbosely. Default is true') do |v| opts.on('--[no-]verbose', 'Run verbosely. Default is true') do |v|
options[:verbose] = v options[:verbose] = v
end end
opts.on('-v', '--version', 'Print version number') do |v| opts.on('-v', '--version', 'Print version number') do |_v|
puts "Version: #{GitHubChangelogGenerator::VERSION}" puts "Version: #{GitHubChangelogGenerator::VERSION}"
exit exit
end end
@ -107,7 +106,7 @@ module GitHubChangelogGenerator
puts opts puts opts
exit exit
end end
} end
parser.parse! parser.parse!
@ -126,10 +125,9 @@ module GitHubChangelogGenerator
exit exit
else else
options[:user] = match[1] options[:user] = match[1]
options[:project]= match[2] options[:project] = match[2]
end end
end end
if !options[:user] && !options[:project] if !options[:user] && !options[:project]
@ -143,9 +141,9 @@ module GitHubChangelogGenerator
puts "Detected user:#{match[1]}, project:#{match[2]}" puts "Detected user:#{match[1]}, project:#{match[2]}"
options[:user], options[:project] = match[1], match[2] options[:user], options[:project] = match[1], match[2]
else else
# try to find repo in format: # try to find repo in format:
# origin https://github.com/skywinder/ChangelogMerger (fetch) # origin https://github.com/skywinder/ChangelogMerger (fetch)
# https://github.com/skywinder/ChangelogMerger # https://github.com/skywinder/ChangelogMerger
match = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/.match(remote) match = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/.match(remote)
if match && match[1] && match[2] if match && match[1] && match[2]
puts "Detected user:#{match[1]}, project:#{match[2]}" puts "Detected user:#{match[1]}, project:#{match[2]}"
@ -154,7 +152,6 @@ module GitHubChangelogGenerator
end end
end end
if !options[:user] || !options[:project] if !options[:user] || !options[:project]
puts parser.banner puts parser.banner
exit exit

107
spec/spec_helper.rb Normal file
View File

@ -0,0 +1,107 @@
#
# Author:: Enrico Stahn <mail@enricostahn.com>
#
# Copyright 2014, Zanui, <engineering@zanui.com.au>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
require 'github_changelog_generator'
# This file was generated by the `rspec --init` command. Conventionally, all
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
# The generated `.rspec` file contains `--require spec_helper` which will cause
# this file to always be loaded, without a need to explicitly require it in any
# files.
#
# Given that it is always loaded, you are encouraged to keep this file as
# light-weight as possible. Requiring heavyweight dependencies from this file
# will add to the boot time of your test suite on EVERY test run, even for an
# individual file that may not need all of that loaded. Instead, consider making
# a separate helper file that requires the additional dependencies and performs
# the additional setup, and require it from the spec files that actually need
# it.
#
# The `.rspec` file also contains a few flags that are not defaults but that
# users commonly want.
#
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
RSpec.configure do |config|
# rspec-expectations config goes here. You can use an alternate
# assertion/expectation library such as wrong or the stdlib/minitest
# assertions if you prefer.
config.expect_with :rspec do |expectations|
# This option will default to `true` in RSpec 4. It makes the `description`
# and `failure_message` of custom matchers include text for helper methods
# defined using `chain`, e.g.:
# be_bigger_than(2).and_smaller_than(4).description
# # => "be bigger than 2 and smaller than 4"
# ...rather than:
# # => "be bigger than 2"
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
end
# rspec-mocks config goes here. You can use an alternate test double
# library (such as bogus or mocha) by changing the `mock_with` option here.
config.mock_with :rspec do |mocks|
# Prevents you from mocking or stubbing a method that does not exist on
# a real object. This is generally recommended, and will default to
# `true` in RSpec 4.
mocks.verify_partial_doubles = true
end
# These two settings work together to allow you to limit a spec run
# to individual examples or groups you care about by tagging them with
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
# get run.
config.filter_run :focus
config.run_all_when_everything_filtered = true
# Limits the available syntax to the non-monkey patched syntax that is
# recommended. For more details, see:
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
# config.disable_monkey_patching!
# This setting enables warnings. It's recommended, but in some cases may
# be too noisy due to issues in dependencies.
config.warnings = true
# Many RSpec users commonly either run the entire suite or an individual
# file, and it's useful to allow more verbose output when running an
# individual spec file.
if config.files_to_run.one?
# Use the documentation formatter for detailed output,
# unless a formatter has already been configured
# (e.g. via a command-line flag).
config.default_formatter = 'doc'
end
# Print the 10 slowest examples and example groups at the
# end of the spec run, to help surface which specs are running
# particularly slow.
# config.profile_examples = 10
# Run specs in random order to surface order dependencies. If you find an
# order dependency and want to debug it, you can fix the order by providing
# the seed, which is printed after each run.
# --seed 1234
config.order = :random
# Seed global randomization in this process using the `--seed` CLI option.
# Setting this allows you to use `--seed` to deterministically reproduce
# test failures related to randomization by passing the same `--seed` value
# as the one that triggered the failure.
Kernel.srand config.seed
end