#!/usr/bin/env ruby require 'github_api' require 'json' require 'httparty' require 'colorize' require_relative 'github_changelog_generator/parser' class ChangelogGenerator attr_accessor :options, :all_tags, :github def initialize() @options = Parser.parse_options github_token if @github_token.nil? @github = Github.new else @github = Github.new oauth_token: @github_token end @all_tags = self.get_all_tags @pull_requests = self.get_all_closed_pull_requests @issues = self.get_all_issues @tag_times_hash = {} end def print_json(json) puts JSON.pretty_generate(json) end def exec_command(cmd) exec_cmd = "cd #{$project_path} && #{cmd}" %x[#{exec_cmd}] end def get_all_closed_pull_requests request = @github.pull_requests.list @options[:user], @options[:project], :state => 'closed' pull_requests = request.body if @options[:verbose] puts 'Receive all pull requests' end pull_requests end def compund_changelog if @options[:verbose] puts 'Generating changelog:' end log = "# Changelog\n\n" if @options[:last] log += self.generate_log_between_tags(self.all_tags[0], self.all_tags[1]) elsif @options[:tag1] && @options[:tag2] tag1 = @options[:tag1] tag2 = @options[:tag2] tags_strings = [] self.all_tags.each { |x| tags_strings.push(x['name']) } if tags_strings.include?(tag1) if tags_strings.include?(tag2) hash = Hash[tags_strings.map.with_index.to_a] index1 = hash[tag1] index2 = hash[tag2] log += self.generate_log_between_tags(self.all_tags[index1], self.all_tags[index2]) else puts "Can't find tag #{tag2} -> exit" exit end else puts "Can't find tag #{tag1} -> exit" exit end else log += self.generate_log_for_all_tags end log += "\n\n\\* *This changelog was generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*" output_filename = "#{@options[:output]}" File.open(output_filename, 'w') { |file| file.write(log) } puts "Done! Generated log placed in #{output_filename}" end def generate_log_for_all_tags log = '' for index in 1 ... self.all_tags.size log += self.generate_log_between_tags(self.all_tags[index-1], self.all_tags[index]) end log += self.generate_log_before_tag(self.all_tags.last) log end def is_megred(number) @github.pull_requests.merged? @options[:user], @options[:project], number end def get_all_tags url = "https://api.github.com/repos/#{@options[:user]}/#{@options[:project]}/tags" if @options[:verbose] puts "Receive tags for repo #{url}" end if @github_token.nil? response = HTTParty.get(url, :headers => {'User-Agent' => 'Changelog-Generator'}) else response = HTTParty.get(url, :headers => {'Authorization' => "token #{@github_token}", 'User-Agent' => 'Changelog-Generator'}) end json_parse = JSON.parse(response.body) if @options[:verbose] puts "Found #{json_parse.count} tags" end json_parse end def github_token if @options[:token] return @github_token ||= @options[:token] end env_var = ENV.fetch 'CHANGELOG_GITHUB_TOKEN', nil unless env_var 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 end @github_token ||= env_var end def generate_log_between_tags(since_tag, till_tag) since_tag_time = self.get_time_of_tag(since_tag) till_tag_time = self.get_time_of_tag(till_tag) # if we mix up tags order - lits fix it! if since_tag_time > till_tag_time since_tag, till_tag = till_tag, since_tag since_tag_time, till_tag_time = till_tag_time, since_tag_time end till_tag_name = till_tag['name'] pull_requests = Array.new(@pull_requests) pull_requests.delete_if { |req| if req[:merged_at] t = Time.parse(req[:merged_at]).utc tag_is_later_since = t > since_tag_time tag_is_before_till = t <= till_tag_time in_range = (tag_is_later_since) && (tag_is_before_till) !in_range else true end } issues = Array.new(@issues) issues.delete_if { |issue| if issue[:closed_at] t = Time.parse(issue[:closed_at]).utc tag_is_later_since = t > since_tag_time tag_is_before_till = t <= till_tag_time in_range = (tag_is_later_since) && (tag_is_before_till) !in_range else true end } self.create_log(pull_requests, issues, till_tag_name, till_tag_time) end def generate_log_before_tag(tag) tag_time = self.get_time_of_tag(tag) tag_name = tag['name'] pull_requests = Array.new(@pull_requests) pull_requests.delete_if { |req| if req[:merged_at] t = Time.parse(req[:merged_at]).utc t > tag_time else true end } issues = Array.new(@issues) issues.delete_if { |issue| if issue[:closed_at] t = Time.parse(issue[:closed_at]).utc t > tag_time else true end } self.create_log(pull_requests, issues, tag_name, tag_time) end def create_log(pull_requests, issues, tag_name, tag_time) # Generate tag name and link trimmed_tag = tag_name.tr('v', '') log = "## [#{trimmed_tag}] (https://github.com/#{@options[:user]}/#{@options[:project]}/tree/#{tag_name})\n" #Generate date string: time_string = tag_time.strftime @options[:format] log += "#### #{time_string}\n" if @options[:pulls] # Generate pull requests: if pull_requests pull_requests.each { |dict| merge = "#{@options[:merge_prefix]}#{dict[:title]} [\\##{dict[:number]}](#{dict.html_url})\n\n" log += "- #{merge}" } end end if @options[:issues] # Generate issues: if issues issues.sort! { |x, y| if x.labels.any? && y.labels.any? x.labels[0].name <=> y.labels[0].name else if x.labels.any? 1 else if y.labels.any? -1 else 0 end end end }.reverse! end issues.each { |dict| is_bug = false is_enhancement = false dict.labels.each { |label| if label.name == 'bug' is_bug = true end if label.name == 'enhancement' is_enhancement = true end } intro = 'Closed issue' if is_bug intro = 'Fixed bug' end if is_enhancement intro = 'Implemented enhancement' end merge = "*#{intro}:* #{dict[:title]} [\\##{dict[:number]}](#{dict.html_url})\n\n" log += "- #{merge}" } end log end def get_time_of_tag(prev_tag) if @tag_times_hash[prev_tag['name']] return @tag_times_hash[prev_tag['name']] end if @options[:verbose] puts "Get time for tag #{prev_tag['name']}" end github_git_data_commits_get = @github.git_data.commits.get @options[:user], @options[:project], prev_tag['commit']['sha'] time_string = github_git_data_commits_get['committer']['date'] Time.parse(time_string) @tag_times_hash[prev_tag['name']] = Time.parse(time_string) end def get_all_issues issues_req = @github.issues.list user: @options[:user], repo: @options[:project], state: 'closed', filter: 'all', labels: nil filtered_issues = issues_req.body.select { |issues| #compare is there any labels from @options[:labels] array (issues.labels.map { |issue| issue.name } & @options[:labels]).any? } if @options[:add_issues_wo_labels] issues_wo_labels = issues_req.body.select { |issues| !issues.labels.map { |issue| issue.name }.any? } filtered_issues.concat(issues_wo_labels) end # remove pull request from issues: filtered_issues.select! { |x| x.pull_request == nil } if @options[:verbose] puts "Receive all closed issues with labels #{@options[:labels]}: #{filtered_issues.count} issues" end filtered_issues end end if __FILE__ == $0 changelog_generator = ChangelogGenerator.new # changelog_generator.compund_changelog end