-
-
Notifications
You must be signed in to change notification settings - Fork 844
/
generator_fetcher.rb
202 lines (183 loc) · 8.15 KB
/
generator_fetcher.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# frozen_string_literal: true
module GitHubChangelogGenerator
class Generator
# Fetch event for issues and pull requests
# @return [Array] array of fetched issues
def fetch_events_for_issues_and_pr
print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r" if options[:verbose]
# Async fetching events:
@fetcher.fetch_events_async(@issues + @pull_requests)
end
# Async fetching of all tags dates
def fetch_tags_dates(tags)
print "Fetching tag dates...\r" if options[:verbose]
i = 0
tags.each do |tag|
get_time_of_tag(tag)
i += 1
end
puts "Fetching tags dates: #{i}/#{tags.count}" 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]
i = 0
issues.each do |issue|
find_closed_date_by_commit(issue)
i += 1
end
puts "Fetching closed dates for issues: #{i}/#{issues.count}" if options[:verbose]
end
# Adds a key "first_occurring_tag" to each PR with a value of the oldest
# tag that a PR's merge commit occurs in in the git history. This should
# indicate the release of each PR by git's history regardless of dates and
# divergent branches.
#
# @param [Array] tags The tags sorted by time, newest to oldest.
# @param [Array] prs The PRs to discover the tags of.
# @return [Nil] No return; PRs are updated in-place.
def add_first_occurring_tag_to_prs(tags, prs)
total = prs.count
prs_left = associate_tagged_prs(tags, prs, total)
prs_left = associate_release_branch_prs(prs_left, total)
prs_left = associate_rebase_comment_prs(tags, prs_left, total) if prs_left.any?
# PRs in prs_left will be untagged, not in release branch, and not
# rebased. They should not be included in the changelog as they probably
# have been merged to a branch other than the release branch.
@pull_requests -= prs_left
Helper.log.info "Associating PRs with tags: #{total}/#{total}"
end
# Associate merged PRs by the merge SHA contained in each tag. If the
# merge_commit_sha is not found in any tag's history, skip association.
#
# @param [Array] tags The tags sorted by time, newest to oldest.
# @param [Array] prs The PRs to associate.
# @return [Array] PRs without their merge_commit_sha in a tag.
def associate_tagged_prs(tags, prs, total)
@fetcher.fetch_tag_shas(tags)
i = 0
prs.reject do |pr|
found = false
# XXX Wish I could use merge_commit_sha, but gcg doesn't currently
# fetch that. See
# https://developer.github.com/v3/pulls/#get-a-single-pull-request vs.
# https://developer.github.com/v3/pulls/#list-pull-requests
if pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" })
# Iterate tags.reverse (oldest to newest) to find first tag of each PR.
if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(event["commit_id"]) })
pr["first_occurring_tag"] = oldest_tag["name"]
found = true
i += 1
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
end
else
# Either there were no events or no merged event. Github's api can be
# weird like that apparently. Check for a rebased comment before erroring.
no_events_pr = associate_rebase_comment_prs(tags, [pr], total)
raise StandardError, "No merge sha found for PR #{pr['number']} via the GitHub API" unless no_events_pr.empty?
found = true
i += 1
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
end
found
end
end
# Associate merged PRs by the HEAD of the release branch. If no
# --release-branch was specified, then the github default branch is used.
#
# @param [Array] prs_left PRs not associated with any tag.
# @param [Integer] total The total number of PRs to associate; used for verbose printing.
# @return [Array] PRs without their merge_commit_sha in the branch.
def associate_release_branch_prs(prs_left, total)
if prs_left.any?
i = total - prs_left.count
prs_left.reject do |pr|
found = false
if pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" }) && sha_in_release_branch?(event["commit_id"])
found = true
i += 1
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
end
found
end
else
prs_left
end
end
# Associate merged PRs by the SHA detected in github comments of the form
# "rebased commit: <sha>". For use when the merge_commit_sha is not in the
# actual git history due to rebase.
#
# @param [Array] tags The tags sorted by time, newest to oldest.
# @param [Array] prs_left The PRs not yet associated with any tag or branch.
# @return [Array] PRs without rebase comments.
def associate_rebase_comment_prs(tags, prs_left, total)
i = total - prs_left.count
# Any remaining PRs were not found in the list of tags by their merge
# commit and not found in any specified release branch. Fallback to
# rebased commit comment.
@fetcher.fetch_comments_async(prs_left)
prs_left.reject do |pr|
found = false
if pr["comments"] && (rebased_comment = pr["comments"].reverse.find { |c| c["body"].match(%r{rebased commit: ([0-9a-f]{40})}i) })
rebased_sha = rebased_comment["body"].match(%r{rebased commit: ([0-9a-f]{40})}i)[1]
if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(rebased_sha) })
pr["first_occurring_tag"] = oldest_tag["name"]
found = true
i += 1
elsif sha_in_release_branch?(rebased_sha)
found = true
i += 1
else
raise StandardError, "PR #{pr['number']} has a rebased SHA comment but that SHA was not found in the release branch or any tags"
end
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
else
puts "Warning: PR #{pr['number']} merge commit was not found in the release branch or tagged git history and no rebased SHA comment was found"
end
found
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)
return if issue["events"].nil?
# if it's PR -> then find "merged event", in case of usual issue -> found 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"] == compare_string
set_date_from_event(event, issue)
break
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"]
return
end
commit = @fetcher.fetch_commit(event["commit_id"])
issue["actual_date"] = commit["commit"]["author"]["date"]
# issue['actual_date'] = commit['author']['date']
rescue StandardError
puts "Warning: Can't fetch commit #{event['commit_id']}. It is probably referenced from another repo."
issue["actual_date"] = issue["closed_at"]
end
private
# Detect if a sha occurs in the --release-branch. Uses the github repo
# default branch if not specified.
#
# @param [String] sha SHA to check.
# @return [Boolean] True if SHA is in the branch git history.
def sha_in_release_branch?(sha)
branch = @options[:release_branch] || @fetcher.default_branch
@fetcher.commits_in_branch(branch).include?(sha)
end
end
end