Skip to content

Commit ad18d61

Browse files
authored
Merge pull request #132 from ruby-no-kai/generate_sponsors_yaml_file_job_reuse
GenerateSponsorsYamlFileJob: stable branch, reuse PR & extract GitHubPusher
2 parents b56ef5a + 4029ec0 commit ad18d61

File tree

1 file changed

+113
-40
lines changed

1 file changed

+113
-40
lines changed

app/jobs/generate_sponsors_yaml_file_job.rb

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
class GenerateSponsorsYamlFileJob < ApplicationJob
2-
delegate :octokit, :base_branch, to: :github_installation
3-
42
def perform(conference, push: true)
53
@conference = conference
64

@@ -109,9 +107,10 @@ def yaml_data
109107
end
110108

111109
combined_data = data ? data.merge("_events" => events) : { "_events" => events }
112-
comment_parts = []
113-
comment_parts << "last_editing_history: #{@last_id}" if @last_id
114-
comment_parts << "last_event_editing_history: #{@last_event_editing_history_id}" if @last_event_editing_history_id
110+
comment_parts = [
111+
"last_editing_history: #{@last_id || 0}",
112+
"last_event_editing_history: #{@last_event_editing_history_id || 0}",
113+
]
115114
@yaml_data = [
116115
"# #{comment_parts.join(', ')}",
117116
combined_data.to_yaml,
@@ -128,50 +127,124 @@ def json_data
128127

129128
def push_to_github
130129
return unless repo
131-
return if yaml_data.nil? # to generate
130+
return if yaml_data.nil?
131+
132132
push_id = @last_id || "event-#{@last_event_editing_history_id}"
133-
@branch_name = "sponsor-app/#{push_id}"
134-
@filepath = repo.path
133+
GitHubPusher.new(
134+
conference: @conference,
135+
filepath: repo.path,
136+
content: yaml_data,
137+
last_editing_history_id: @last_id,
138+
last_event_editing_history_id: @last_event_editing_history_id,
139+
push_id:,
140+
).push
141+
end
142+
143+
# For debugging
144+
def self.get_octokit(repo)
145+
GithubInstallation.new(repo).octokit
146+
end
147+
148+
class GitHubPusher
149+
MAX_RETRIES = 3
135150

136-
begin
137-
octokit.delete_branch(repo.name, @branch_name)
138-
rescue Octokit::UnprocessableEntity
151+
delegate :octokit, :base_branch, to: :github_installation
152+
153+
def initialize(conference:, filepath:, content:, last_editing_history_id:, last_event_editing_history_id:, push_id:)
154+
@conference = conference
155+
@repo = conference.github_repo
156+
@filepath = filepath
157+
@content = content
158+
@last_id = last_editing_history_id || 0
159+
@last_event_id = last_event_editing_history_id || 0
160+
@branch_name = "sponsor-app/#{conference.slug}"
161+
@pr_title = "Update sponsors.yml for #{conference.slug} (#{push_id})"
139162
end
140163

141-
head = octokit.branch(repo.name, base_branch)
142-
octokit.create_ref(repo.name, "refs/heads/#{@branch_name}", head[:commit][:sha])
164+
def push
165+
return unless @repo
143166

144-
begin
145-
blob_sha = octokit.contents(repo.name, path: @filepath)[:sha]
146-
rescue Octokit::NotFound
147-
blob_sha = nil
167+
if branch_has_newer_data?
168+
Rails.logger.info "GenerateSponsorsYamlFileJob: branch has newer data, skipping"
169+
return
170+
end
171+
172+
reset_branch
173+
commit_content
174+
create_or_update_pull_request
148175
end
149176

150-
octokit.update_contents(
151-
repo.name,
152-
@filepath,
153-
"Update sponsors.yml for #{@conference.slug} (#{push_id})",
154-
blob_sha,
155-
yaml_data,
156-
branch: @branch_name,
157-
)
158-
octokit.create_pull_request(
159-
repo.name,
160-
base_branch,
161-
@branch_name,
162-
"Update sponsors.yml for #{@conference.slug} (#{push_id})",
163-
nil,
164-
)
165-
end
177+
private
166178

167-
# For debugging
168-
def self.get_octokit(repo)
169-
GithubInstallation.new(repo).octokit
170-
end
179+
def reset_branch
180+
begin
181+
octokit.delete_branch(@repo.name, @branch_name)
182+
rescue Octokit::UnprocessableEntity
183+
end
184+
185+
head = octokit.branch(@repo.name, base_branch)
186+
octokit.create_ref(@repo.name, "refs/heads/#{@branch_name}", head[:commit][:sha])
187+
end
188+
189+
def commit_content
190+
begin
191+
blob_sha = octokit.contents(@repo.name, path: @filepath, ref: base_branch)[:sha]
192+
rescue Octokit::NotFound
193+
blob_sha = nil
194+
end
195+
196+
retries = 0
197+
begin
198+
octokit.update_contents(@repo.name, @filepath, @pr_title, blob_sha, @content, branch: @branch_name)
199+
rescue Octokit::Conflict, Octokit::UnprocessableEntity => e
200+
if retries < MAX_RETRIES && !branch_has_newer_data?
201+
retries += 1
202+
begin
203+
blob_sha = octokit.contents(@repo.name, path: @filepath, ref: @branch_name)[:sha]
204+
rescue Octokit::NotFound
205+
blob_sha = nil
206+
end
207+
Rails.logger.info "GenerateSponsorsYamlFileJob: commit conflict, retrying (#{retries}/#{MAX_RETRIES})"
208+
retry
209+
else
210+
Rails.logger.info "GenerateSponsorsYamlFileJob: commit conflict and branch has newer data (or max retries), skipping"
211+
return
212+
end
213+
end
214+
end
171215

172-
private
216+
def branch_has_newer_data?
217+
existing = octokit.contents(@repo.name, path: @filepath, ref: @branch_name)
218+
existing_content = Base64.decode64(existing[:content])
173219

174-
def github_installation
175-
@github_installation ||= GithubInstallation.new(repo.name, branch: repo.branch)
220+
branch_last_id = existing_content =~ /\blast_editing_history: (\d+)/ ? $1.to_i : 0
221+
branch_last_event_id = existing_content =~ /\blast_event_editing_history: (\d+)/ ? $1.to_i : 0
222+
223+
# Branch is newer when both IDs are >= ours and at least one is strictly greater
224+
branch_last_id >= @last_id && branch_last_event_id >= @last_event_id &&
225+
(branch_last_id > @last_id || branch_last_event_id > @last_event_id)
226+
rescue Octokit::NotFound
227+
false # Branch or file doesn't exist
228+
end
229+
230+
def create_or_update_pull_request
231+
owner = @repo.name.split('/')[0]
232+
existing_prs = octokit.pull_requests(@repo.name, state: 'open', head: "#{owner}:#{@branch_name}")
233+
if existing_prs.any?
234+
octokit.update_pull_request(@repo.name, existing_prs[0][:number], title: @pr_title)
235+
else
236+
begin
237+
octokit.create_pull_request(@repo.name, base_branch, @branch_name, @pr_title, nil)
238+
rescue Octokit::UnprocessableEntity
239+
# Concurrent job already created PR
240+
existing_prs = octokit.pull_requests(@repo.name, state: 'open', head: "#{owner}:#{@branch_name}")
241+
octokit.update_pull_request(@repo.name, existing_prs[0][:number], title: @pr_title) if existing_prs.any?
242+
end
243+
end
244+
end
245+
246+
def github_installation
247+
@github_installation ||= GithubInstallation.new(@repo.name, branch: @repo.branch)
248+
end
176249
end
177250
end

0 commit comments

Comments
 (0)