prophet 1.6.2 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3a03cdc7b74ef29b89a87fe997327cb088866998
4
- data.tar.gz: 0c30193560861e6d77f8c72e51033f023fcae629
3
+ metadata.gz: afe7d79d028a1b2365986a403441c0c83ddd8833
4
+ data.tar.gz: 0819c0fd245bc51106ac93fda3a0f1669af8d8b0
5
5
  SHA512:
6
- metadata.gz: 2f83eb11cc1a5d241598b66f05fa68e727cffa2a78ab154e7f09a06a02978c5f0fd894fda55920d54603a3f633d8384aac0c4d6000e56e09f943bc66f37a4b8a
7
- data.tar.gz: 6c15d03d14ec406e7c612763c6408b1c71b9a10a7bcd3f219e4cf5a9a2686d99325e7c56eb02297f88525865000f820a64569df47f58b9608d729afbbfc8c3bf
6
+ metadata.gz: fa4ffda35c9052a9542259acf8fdb8508126ed0e523780faa604a39ee91c459b0da5978d994011a8be3503f10dcddb68b906206180772d9cbc93461b2c12f8a0
7
+ data.tar.gz: 96c4720864de08183fc6532903b3c5da54bec83dc29274c7ba305e70c367ff3018c61b4c49c72425f3b14297a9d9e318f1a4c4d08257711eed8a263fa8e59c34
@@ -1,3 +1,6 @@
1
+ require 'English'
2
+ require 'open3'
3
+
1
4
  class Prophet
2
5
 
3
6
  attr_accessor :username,
@@ -15,7 +18,10 @@ class Prophet
15
18
  :status_success,
16
19
  :comment_failure,
17
20
  :comment_success,
18
- :reuse_comments
21
+ :disable_comments,
22
+ :reuse_comments,
23
+ :status_context,
24
+ :status_target_url
19
25
 
20
26
  # Allow configuration blocks being passed to Prophet.
21
27
  # See the README.md for examples on how to call this method.
@@ -42,7 +48,7 @@ class Prophet
42
48
  begin
43
49
  self.prepare_block.call
44
50
  rescue Exception => e
45
- @log.error "Preparation block raised an exception: #{e}"
51
+ logger.error "Preparation block raised an exception: #{e}"
46
52
  end
47
53
  # Loop through all 'open' pull requests.
48
54
  selected_requests = pull_requests.select do |request|
@@ -53,30 +59,38 @@ class Prophet
53
59
  remove_comment unless self.reuse_comments
54
60
  true
55
61
  end
62
+
56
63
  # Run code on all selected requests.
57
64
  selected_requests.each do |request|
58
65
  @request = request
59
- @log.info "Running for request ##{@request.id}."
66
+ logger.info "Running for request ##{@request.id}."
60
67
  # GitHub always creates a merge commit for its 'Merge Button'.
61
68
  # Prophet reuses that commit to run the code on it.
62
- switch_branch_to_merged_state
63
- # Run specified code (i.e. tests) for the project.
64
- begin
65
- self.exec_block.call
66
- # Unless self.success has already been set (to true/false) manually,
67
- # the success/failure is determined by the last command's return code.
68
- self.success = ($? && $?.exitstatus == 0) if self.success.nil?
69
- rescue Exception => e
70
- @log.error "Execution block raised an exception: #{e}"
71
- self.success = false
69
+ if switch_branch_to_merged_state
70
+ # Run specified code (i.e. tests) for the project.
71
+ begin
72
+ self.exec_block.call
73
+ # Unless self.success has already been set (to true/false) manually,
74
+ # the success/failure is determined by the last command's return code.
75
+ self.success = ($CHILD_STATUS && $CHILD_STATUS.exitstatus == 0) if self.success.nil?
76
+ rescue Exception => e
77
+ logger.error "Execution block raised an exception: #{e}"
78
+ self.success = false
79
+ end
80
+ switch_branch_back
81
+ comment_on_github
82
+ set_status_on_github
72
83
  end
73
- switch_branch_back
74
- comment_on_github
75
- set_status_on_github
76
84
  self.success = nil
77
85
  end
78
86
  end
79
87
 
88
+ def logger
89
+ @logger ||= Logger.new(STDOUT).tap do |log|
90
+ log.level = Logger::INFO
91
+ end
92
+ end
93
+
80
94
 
81
95
  private
82
96
 
@@ -86,13 +100,6 @@ class Prophet
86
100
  end
87
101
 
88
102
  def configure
89
- # Use existing logger or fall back to a new one with standard log level.
90
- if self.logger
91
- @log = self.logger
92
- else
93
- @log = Logger.new(STDOUT)
94
- @log.level = Logger::INFO
95
- end
96
103
  # Set default fall back values for options that aren't set.
97
104
  self.username ||= git_config['github.login']
98
105
  self.password ||= git_config['github.password']
@@ -101,12 +108,14 @@ class Prophet
101
108
  self.rerun_on_source_change = true if self.rerun_on_source_change.nil?
102
109
  self.rerun_on_target_change = true if self.rerun_on_target_change.nil?
103
110
  self.reuse_comments = false if self.reuse_comments.nil?
111
+ self.disable_comments = false if self.disable_comments.nil?
104
112
  # Allow for custom messages.
105
113
  self.status_pending ||= 'Prophet is still running.'
106
114
  self.status_failure ||= 'Prophet reports failure.'
107
115
  self.status_success ||= 'Prophet reports success.'
108
116
  self.comment_failure ||= 'Prophet reports failure.'
109
117
  self.comment_success ||= 'Prophet reports success.'
118
+ self.status_context ||= 'prophet/default'
110
119
  # Find environment (tasks, project, ...).
111
120
  self.prepare_block ||= lambda {}
112
121
  self.exec_block ||= lambda { `rake` }
@@ -125,7 +134,7 @@ class Prophet
125
134
  )
126
135
  # Check user login to GitHub.
127
136
  github.login
128
- @log.info "Successfully logged into GitHub (API v#{github.api_version}) with user '#{user}'."
137
+ logger.info "Successfully logged into GitHub with user '#{user}'."
129
138
  # Ensure the user has access to desired project.
130
139
  # NOTE: All three variants should work:
131
140
  # 'ssh://git@github.com:user/project.git'
@@ -134,20 +143,20 @@ class Prophet
134
143
  @project ||= /github\.com[\/:](.*)\.git$/.match(git_config['remote.origin.url'])[1]
135
144
  begin
136
145
  github.repo @project
137
- @log.info "Successfully accessed GitHub project '#{@project}'"
146
+ logger.info "Successfully accessed GitHub project '#{@project}'"
138
147
  github
139
148
  rescue Octokit::Unauthorized => e
140
- @log.error "Unable to access GitHub project with user '#{user}':\n#{e.message}"
149
+ logger.error "Unable to access GitHub project with user '#{user}':\n#{e.message}"
141
150
  abort
142
151
  end
143
152
  end
144
153
 
145
154
  def pull_requests
146
- request_list = @github.pulls @project, 'open'
155
+ request_list = @github.pulls @project, state: 'open'
147
156
  requests = request_list.collect do |request|
148
157
  PullRequest.new(@github.pull_request @project, request.number)
149
158
  end
150
- @log.info "Found #{requests.size > 0 ? requests.size : 'no'} open pull requests in '#{@project}'."
159
+ logger.info "Found #{requests.size > 0 ? requests.size : 'no'} open pull requests in '#{@project}'."
151
160
  requests
152
161
  end
153
162
 
@@ -156,72 +165,85 @@ class Prophet
156
165
  # - the pull request has been updated since the last run.
157
166
  # - the target (i.e. master) has been updated since the last run.
158
167
  def run_necessary?
159
- @log.info "Checking pull request ##{@request.id}: #{@request.content.title}"
168
+ logger.info "Checking pull request ##{@request.id}: #{@request.content.title}"
160
169
  # Compare current sha ids of target and source branch with those from the last test run.
161
170
  @request.target_head_sha = @github.commits(@project).first.sha
162
171
  comments = @github.issue_comments(@project, @request.id)
163
172
  comments = comments.select { |c| [username, username_fail].include?(c.user.login) }.reverse
164
- # Initialize shas to ensure it will live on after the 'each' block.
165
- shas = nil
166
- @request.comment = nil
167
173
  comments.each do |comment|
168
- shas = /Merged ([\w]+) into ([\w]+)/.match(comment.body)
169
- if shas && shas[1] && shas[2]
170
- # Remember comment to be able to update or delete it later.
171
- @request.comment = comment
172
- break
173
- end
174
+ @request.comment = comment if /Merged ([\w]+) into ([\w]+)/.match(comment.body)
174
175
  end
175
- # If it's not mergeable, we need to delete all comments of former test runs.
176
- unless @request.content.mergeable
176
+
177
+ statuses = @github.status(@project, @request.head_sha).statuses.select { |s| s.context == self.status_context }
178
+ # Only run if it's mergeable.
179
+ if @request.content.mergeable
180
+ if statuses.empty?
181
+ # If there is no status yet, it has to be a new request.
182
+ logger.info 'New pull request detected, run needed.'
183
+ return true
184
+ elsif !self.disable_comments && !@request.comment
185
+ logger.info 'Rerun forced.'
186
+ return true
187
+ end
188
+ else
177
189
  # Sometimes GitHub doesn't have a proper boolean value stored.
178
- if @request.content.mergeable.nil? && switch_branch_to_merged_state(false)
190
+ if @request.content.mergeable.nil? && switch_branch_to_merged_state
179
191
  # Pull request is mergeable after all.
180
192
  switch_branch_back
181
193
  else
182
- @log.info 'Pull request not auto-mergeable. Not running.'
194
+ logger.info 'Pull request not auto-mergeable. Not running.'
183
195
  if @request.comment
184
- @log.info 'Deleting existing comment.'
196
+ logger.info 'Deleting existing comment.'
185
197
  call_github(old_comment_success?).delete_comment(@project, @request.comment.id)
186
198
  end
199
+ create_status(:error, "Pull request not auto-mergeable. Not running.") if statuses.first && statuses.first.state != 'error'
187
200
  return false
188
201
  end
189
202
  end
190
- if @request.comment
191
- @log.info "Current target sha: '#{@request.target_head_sha}', pull sha: '#{@request.head_sha}'."
192
- @log.info "Last test run target sha: '#{shas[2]}', pull sha: '#{shas[1]}'."
203
+
204
+ # Initialize shas to ensure it will live on after the 'each' block.
205
+ shas = nil
206
+ statuses.each do |status|
207
+ shas = /Merged ([\w]+) into ([\w]+)/.match(status.description)
208
+ break if shas && shas[1] && shas[2]
209
+ end
210
+
211
+ if shas
212
+ logger.info "Current target sha: '#{@request.target_head_sha}', pull sha: '#{@request.head_sha}'."
213
+ logger.info "Last test run target sha: '#{shas[2]}', pull sha: '#{shas[1]}'."
193
214
  if self.rerun_on_source_change && (shas[1] != @request.head_sha)
194
- @log.info 'Re-running due to new commit in pull request.'
215
+ logger.info 'Re-running due to new commit in pull request.'
195
216
  return true
196
217
  elsif self.rerun_on_target_change && (shas[2] != @request.target_head_sha)
197
- @log.info 'Re-running due to new commit in target branch.'
198
- return true
218
+ logger.info 'Re-running due to new commit in target branch.'
219
+ return true
199
220
  end
200
221
  else
201
- # If there are no comments yet, it has to be a new request.
202
- @log.info 'New pull request detected, run needed.'
222
+ # If there are no SHAs yet, it has to be a new request.
223
+ logger.info 'New pull request detected, run needed.'
203
224
  return true
204
225
  end
205
- @log.info "Not running for request ##{@request.id}."
226
+
227
+ logger.info "Not running for request ##{@request.id}."
206
228
  false
207
229
  end
208
230
 
209
- def switch_branch_to_merged_state(hard = true)
231
+ def switch_branch_to_merged_state
210
232
  # Fetch the merge-commit for the pull request.
211
233
  # NOTE: This commit is automatically created by 'GitHub Merge Button'.
212
- # FIXME: Use cheetah to pipe to @log.debug instead of that /dev/null hack.
234
+ # FIXME: Use cheetah to pipe to logger.debug instead of that /dev/null hack.
213
235
  `git fetch origin refs/pull/#{@request.id}/merge: &> /dev/null`
214
- `git checkout FETCH_HEAD &> /dev/null`
215
- unless ($? && $?.exitstatus == 0)
216
- @log.error 'Unable to switch to merge branch.'
217
- hard ? abort : false
236
+ _output, status = Open3.capture2 'git checkout FETCH_HEAD &> /dev/null'
237
+ if status != 0
238
+ logger.error 'Unable to switch to merge branch.'
239
+ return false
218
240
  end
219
241
  true
220
242
  end
221
243
 
222
244
  def switch_branch_back
223
- # FIXME: Use cheetah to pipe to @log.debug instead of that /dev/null hack.
224
- @log.info 'Switching back to original branch.'
245
+ # FIXME: Use cheetah to pipe to logger.debug instead of that /dev/null hack.
246
+ logger.info 'Switching back to original branch.'
225
247
  # FIXME: For branches other than master, remember the original branch.
226
248
  `git checkout master &> /dev/null`
227
249
  # Clean up potential remains and run garbage collector.
@@ -243,33 +265,60 @@ class Prophet
243
265
  end
244
266
 
245
267
  def comment_on_github
268
+ return if self.disable_comments
269
+
246
270
  # Determine comment message.
247
271
  message = if self.success
248
- @log.info 'Successful run.'
249
- self.comment_success + "\n( Success: "
272
+ logger.info 'Successful run.'
273
+ self.comment_success
250
274
  else
251
- @log.info 'Failing run.'
252
- self.comment_failure + "\n( Failure: "
275
+ logger.info 'Failing run.'
276
+ self.comment_failure
253
277
  end
254
- message += "Merged #{@request.head_sha} into #{@request.target_head_sha} )"
278
+ message += status_string
255
279
  if self.reuse_comments && old_comment_success? == self.success
256
280
  # Replace existing comment's body with the correct connection.
257
- @log.info "Updating existing #{notion(self.success)} comment."
281
+ logger.info "Updating existing #{notion(self.success)} comment."
258
282
  call_github(self.success).update_comment(@project, @request.comment.id, message)
259
283
  else
260
284
  if @request.comment
261
- @log.info "Deleting existing #{notion(!self.success)} comment."
285
+ logger.info "Deleting existing #{notion(!self.success)} comment."
262
286
  # Delete old comment with correct connection (if @request.comment exists).
263
287
  call_github(!self.success).delete_comment(@project, @request.comment.id)
264
288
  end
265
289
  # Create new comment with correct connection.
266
- @log.info "Adding new #{notion(self.success)} comment."
290
+ logger.info "Adding new #{notion(self.success)} comment."
267
291
  call_github(self.success).add_comment(@project, @request.id, message)
268
292
  end
269
293
  end
270
294
 
295
+ def status_string
296
+ case self.success
297
+ when true
298
+ " (Merged #{@request.head_sha} into #{@request.target_head_sha})"
299
+ when false
300
+ " (Merged #{@request.head_sha} into #{@request.target_head_sha})"
301
+ else
302
+ ""
303
+ end
304
+ end
305
+
306
+ def create_status(state, description)
307
+ logger.info "Setting status '#{state}': '#{description}'"
308
+ @github.create_status(
309
+ @project,
310
+ @request.head_sha,
311
+ state,
312
+ {
313
+ "description" => description,
314
+ "context" => status_context,
315
+ "target_url" => self.status_target_url
316
+ }
317
+ )
318
+ end
319
+
271
320
  def set_status_on_github
272
- @log.info 'Updating status on GitHub.'
321
+ logger.info 'Updating status on GitHub.'
273
322
  case self.success
274
323
  when true
275
324
  state_symbol = :success
@@ -281,12 +330,7 @@ class Prophet
281
330
  state_symbol = :pending
282
331
  state_message = self.status_pending
283
332
  end
284
- @github.post(
285
- "repos/#{@project}/statuses/#{@request.head_sha}", {
286
- :state => state_symbol,
287
- :description => state_message
288
- }
289
- )
333
+ create_status(state_symbol, state_message + status_string)
290
334
  end
291
335
 
292
336
  def notion(success)
@@ -3,6 +3,6 @@ require 'rails'
3
3
 
4
4
  class Railtie < Rails::Railtie
5
5
  rake_tasks do
6
- load 'tasks/prophet.rake.rake'
6
+ load 'tasks/prophet.rake'
7
7
  end
8
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: prophet
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.2
4
+ version: 1.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Bamberger
@@ -10,50 +10,50 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2014-07-29 00:00:00.000000000 Z
13
+ date: 2017-03-06 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: faraday_middleware
17
17
  requirement: !ruby/object:Gem::Requirement
18
18
  requirements:
19
- - - '='
19
+ - - "~>"
20
20
  - !ruby/object:Gem::Version
21
- version: 0.9.0
21
+ version: 0.11.0
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
- - - '='
26
+ - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: 0.9.0
28
+ version: 0.11.0
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: faraday
31
31
  requirement: !ruby/object:Gem::Requirement
32
32
  requirements:
33
- - - '='
33
+ - - "~>"
34
34
  - !ruby/object:Gem::Version
35
- version: 0.8.8
35
+ version: 0.11.0
36
36
  type: :runtime
37
37
  prerelease: false
38
38
  version_requirements: !ruby/object:Gem::Requirement
39
39
  requirements:
40
- - - '='
40
+ - - "~>"
41
41
  - !ruby/object:Gem::Version
42
- version: 0.8.8
42
+ version: 0.11.0
43
43
  - !ruby/object:Gem::Dependency
44
44
  name: octokit
45
45
  requirement: !ruby/object:Gem::Requirement
46
46
  requirements:
47
- - - '='
47
+ - - "~>"
48
48
  - !ruby/object:Gem::Version
49
- version: 1.25.0
49
+ version: '4.0'
50
50
  type: :runtime
51
51
  prerelease: false
52
52
  version_requirements: !ruby/object:Gem::Requirement
53
53
  requirements:
54
- - - '='
54
+ - - "~>"
55
55
  - !ruby/object:Gem::Version
56
- version: 1.25.0
56
+ version: '4.0'
57
57
  description: Prophet runs custom code (i.e. your project's test suite) on open pull
58
58
  requests on GitHub. Afterwards it posts the result as a comment to the respective
59
59
  request. This should give you an outlook on the future state of your repository
@@ -89,7 +89,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
89
89
  version: '0'
90
90
  requirements: []
91
91
  rubyforge_project:
92
- rubygems_version: 2.2.2
92
+ rubygems_version: 2.6.8
93
93
  signing_key:
94
94
  specification_version: 4
95
95
  summary: An easy way to loop through open pull requests and run code onthe merged