11class 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 =~ /\b last_editing_history: (\d +)/ ? $1. to_i : 0
221+ branch_last_event_id = existing_content =~ /\b last_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
177250end
0 commit comments