prophet 1.6.2 → 1.9.0

Sign up to get free protection for your applications and to get access to all the features.
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