|
| 1 | +# frozen_string_literal: true |
| 2 | + |
| 3 | +require "optparse" |
| 4 | +require "json" |
| 5 | + |
| 6 | +# @return [String] |
| 7 | +def search_git_tags |
| 8 | + `git tag`.each_line.map(&:strip).sort_by { |tag| Gem::Version.create(tag.delete_prefix("v")) }.reverse |
| 9 | +end |
| 10 | + |
| 11 | +# @param before [String] |
| 12 | +# @param after [String] |
| 13 | +# @return [Array<Integer>] |
| 14 | +def search_pr_numbers(before:, after:) |
| 15 | + commits = `git rev-list --merges --right-only #{before}...#{after}`.each_line.map(&:strip) |
| 16 | + commits.map do |commit| |
| 17 | + commit_message = `git show -q #{commit}` |
| 18 | + commit_message =~ /Merge pull request #([0-9]+)/ |
| 19 | + Regexp.last_match(1).to_i |
| 20 | + end |
| 21 | +end |
| 22 | + |
| 23 | +before = nil |
| 24 | +after = nil |
| 25 | + |
| 26 | +opt = OptionParser.new |
| 27 | +opt.on("--before=BEFORE", "Before tag or sha1 (default: latest tag)") { |v| before = v } |
| 28 | +opt.on("--after=AFTER", "After tag or sha1 (default: HEAD)") { |v| after = v } |
| 29 | + |
| 30 | +opt.parse!(ARGV) |
| 31 | + |
| 32 | +after ||= "HEAD" |
| 33 | + |
| 34 | +unless before |
| 35 | + git_tags = search_git_tags |
| 36 | + |
| 37 | + if after == "HEAD" |
| 38 | + # Use latest tag |
| 39 | + before = git_tags[0] |
| 40 | + else |
| 41 | + index = git_tags.index(after) |
| 42 | + before = |
| 43 | + if index |
| 44 | + # Use 1 previous tag of index |
| 45 | + git_tags[index + 1] |
| 46 | + else |
| 47 | + # Use latest tag |
| 48 | + git_tags[0] |
| 49 | + end |
| 50 | + end |
| 51 | +end |
| 52 | + |
| 53 | +pr_numbers = search_pr_numbers(before:, after:) |
| 54 | +all_prs = pr_numbers.map do |pr_number| |
| 55 | + pr = JSON.parse(`gh pr view --json number,title,author,labels,url #{pr_number}`) |
| 56 | + pr["label_names"] = pr["labels"].map { |label| label["name"] } |
| 57 | + pr |
| 58 | +end |
| 59 | + |
| 60 | +# @param prs [Array<Hash>] |
| 61 | +# |
| 62 | +# @return [String] |
| 63 | +def generate_category_changelog(prs) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity |
| 64 | + lines = [] |
| 65 | + found_pr_numbers = [] |
| 66 | + prs = prs.dup |
| 67 | + |
| 68 | + ["breaking change", "bug", "enhancement"].each do |label| |
| 69 | + label_prs = prs.find_all { |pr| pr["label_names"].include?(label) } |
| 70 | + |
| 71 | + next if label_prs.empty? |
| 72 | + |
| 73 | + case label |
| 74 | + when "breaking change" |
| 75 | + lines << "### :bomb: Breaking changes" |
| 76 | + when "bug" |
| 77 | + lines << "### Bugfixes" |
| 78 | + when "enhancement" |
| 79 | + lines << "### New Features" |
| 80 | + end |
| 81 | + |
| 82 | + label_prs.each do |pr| |
| 83 | + lines < generate_changelog_line(pr) |
| 84 | + found_pr_numbers << pr["number"] |
| 85 | + end |
| 86 | + |
| 87 | + found_pr_numbers.push(*label_prs.map { |pr| pr["number"] }) |
| 88 | + |
| 89 | + prs.reject! { |pr| found_pr_numbers.include?(pr["number"]) } |
| 90 | + |
| 91 | + lines << "" |
| 92 | + end |
| 93 | + |
| 94 | + other_prs = prs.reject do |pr| |
| 95 | + found_pr_numbers.include?(pr["number"]) || pr["label_names"].include?("chore") |
| 96 | + end |
| 97 | + |
| 98 | + lines << "### Other changes" |
| 99 | + other_prs.each do |pr| |
| 100 | + lines << generate_changelog_line(pr) |
| 101 | + end |
| 102 | + found_pr_numbers.push(*other_prs.map { |pr| pr["number"] }) |
| 103 | + lines << "" |
| 104 | + |
| 105 | + return "* No changes\n\n" if found_pr_numbers.empty? |
| 106 | + |
| 107 | + "#{lines.join("\n")}\n" |
| 108 | +end |
| 109 | + |
| 110 | +# @param pull_request [Hash] |
| 111 | +# |
| 112 | +# @return [String] |
| 113 | +def generate_changelog_line(pull_request) |
| 114 | + author = pull_request["author"]["login"].delete_prefix("app/") |
| 115 | + |
| 116 | + "* #{pull_request["title"]} by @#{author} in #{pull_request["url"]}" |
| 117 | +end |
| 118 | + |
| 119 | +changelog_body = +"" |
| 120 | + |
| 121 | +changelog_body << "## Go\n" |
| 122 | +go_prs = all_prs.find_all { |pr| pr["label_names"].include?("go") } |
| 123 | +changelog_body << generate_category_changelog(go_prs) |
| 124 | + |
| 125 | +changelog_body << "## Ruby\n" |
| 126 | +ruby_prs = all_prs.find_all { |pr| pr["label_names"].include?("ruby") } |
| 127 | +changelog_body << generate_category_changelog(ruby_prs) |
| 128 | + |
| 129 | +changelog_body << "## ruby_h_to_go\n" |
| 130 | +ruby_h_to_go_prs = all_prs.find_all { |pr| pr["label_names"].include?("ruby_h_to_go") } |
| 131 | +changelog_body << generate_category_changelog(ruby_h_to_go_prs) |
| 132 | + |
| 133 | +changelog_body << "## Other\n" |
| 134 | +other_prs = all_prs.reject { |pr| pr["label_names"].any? { |label| %w[go ruby ruby_h_to_go chore].include?(label) } } |
| 135 | +changelog_body << generate_category_changelog(other_prs) |
| 136 | + |
| 137 | +changelog_body << "**Full Changelog**: https://github.com/ruby-go-gem/go-gem-wrapper/compare/#{before}...#{after}" |
| 138 | + |
| 139 | +puts changelog_body |
0 commit comments