pullermann 1.0.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.
- data/LICENSE +20 -0
- data/lib/pullermann/pullermann.rb +203 -0
- data/lib/pullermann/railtie.rb +8 -0
- data/lib/pullermann.rb +4 -0
- data/lib/tasks/pullermann.rake +4 -0
- metadata +86 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (C) 2011 Dominik Bamberger bamboo@suse.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
20
|
+
|
@@ -0,0 +1,203 @@
|
|
1
|
+
class Pullermann
|
2
|
+
|
3
|
+
attr_accessor :username,
|
4
|
+
:password,
|
5
|
+
:username_fail,
|
6
|
+
:password_fail,
|
7
|
+
:rerun_on_source_change,
|
8
|
+
:rerun_on_target_change,
|
9
|
+
:prepare_block,
|
10
|
+
:test_block,
|
11
|
+
:log_level
|
12
|
+
|
13
|
+
# Allow configuration blocks being passed to Pullermann.
|
14
|
+
def self.setup
|
15
|
+
yield main_instance
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_preparation(&block)
|
19
|
+
self.prepare_block = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_execution(&block)
|
23
|
+
self.test_block = block
|
24
|
+
end
|
25
|
+
|
26
|
+
# The main Pullermann task. Call this to start testing.
|
27
|
+
def self.run
|
28
|
+
main_instance.run
|
29
|
+
end
|
30
|
+
|
31
|
+
def run
|
32
|
+
# Populate variables and setup environment.
|
33
|
+
configure
|
34
|
+
# Loop through all 'open' pull requests.
|
35
|
+
pull_requests.each do |request|
|
36
|
+
@request_id = request['number']
|
37
|
+
# Jump to next iteration if source and/or target haven't change since last run.
|
38
|
+
next unless test_run_necessary?
|
39
|
+
# GitHub always creates a merge commit for its 'Merge Button'.
|
40
|
+
switch_branch_to_merged_state
|
41
|
+
# Prepare project and CI (e.g. Jenkins) for the test run.
|
42
|
+
self.prepare_block.call
|
43
|
+
# Run specified tests for the project.
|
44
|
+
# NOTE: Either ensure the last call in that block runs your tests
|
45
|
+
# or manually set @result to a boolean inside this block.
|
46
|
+
self.test_block.call
|
47
|
+
# Unless already set, the success/failure is determined by the last
|
48
|
+
# command's return code.
|
49
|
+
@result ||= $? == 0
|
50
|
+
# We need to switch back to the original branch in case we need to test
|
51
|
+
# more pull requests.
|
52
|
+
switch_branch_back
|
53
|
+
comment_on_github
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Remember the one instance we setup in our application and want to run.
|
61
|
+
def self.main_instance
|
62
|
+
@main_instance ||= Pullermann.new
|
63
|
+
end
|
64
|
+
|
65
|
+
def configure
|
66
|
+
@log = Logger.new(STDOUT)
|
67
|
+
@log.level = self.log_level || Logger::INFO
|
68
|
+
# Set default fall back values for options that aren't set.
|
69
|
+
self.username ||= git_config['github.login']
|
70
|
+
self.password ||= git_config['github.password']
|
71
|
+
self.username_fail ||= self.username
|
72
|
+
self.password_fail ||= self.password
|
73
|
+
self.rerun_on_source_change = true unless self.rerun_on_source_change == false
|
74
|
+
self.rerun_on_target_change = true unless self.rerun_on_target_change == false
|
75
|
+
# Find environment (tasks, project, ...).
|
76
|
+
@prepare_block ||= lambda {}
|
77
|
+
@test_block ||= lambda { `rake test` }
|
78
|
+
connect_to_github
|
79
|
+
end
|
80
|
+
|
81
|
+
def connect_to_github(user = self.username, pass = self.password)
|
82
|
+
@github = Octokit::Client.new(
|
83
|
+
:login => user,
|
84
|
+
:password => pass
|
85
|
+
)
|
86
|
+
# Check user login to GitHub.
|
87
|
+
@github.login
|
88
|
+
@log.info "Successfully logged into GitHub (API v#{@github.api_version}) with user '#{user}'."
|
89
|
+
# Ensure the user has access to desired project.
|
90
|
+
@project = /:(.*)\.git/.match(git_config['remote.origin.url'])[1]
|
91
|
+
begin
|
92
|
+
@github.repo @project
|
93
|
+
@log.info "Successfully accessed GitHub project '#{@project}'"
|
94
|
+
rescue Octokit::Unauthorized => e
|
95
|
+
@log.error "Unable to access GitHub project with user '#{user}':\n#{e.message}"
|
96
|
+
abort
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def pull_requests
|
101
|
+
pulls = @github.pulls @project, 'open'
|
102
|
+
@log.info "Found #{pulls.size > 0 ? pulls.size : 'no'} open pull requests in '#{@project}'."
|
103
|
+
pulls
|
104
|
+
end
|
105
|
+
|
106
|
+
# Test runs are necessary if:
|
107
|
+
# - the pull request hasn't been tested before.
|
108
|
+
# - the pull request has been updated since the last run.
|
109
|
+
# - the target (i.e. master) has been updated since the last run.
|
110
|
+
def test_run_necessary?
|
111
|
+
pull_request = @github.pull_request @project, @request_id
|
112
|
+
@log.info "Checking pull request ##{@request_id}: #{pull_request.title}"
|
113
|
+
# If it's not mergeable, there is no point in going on.
|
114
|
+
unless pull_request.mergeable
|
115
|
+
@log.info 'Pull request not auto-mergeable, skipping... '
|
116
|
+
return false
|
117
|
+
end
|
118
|
+
comments = @github.issue_comments(@project, @request_id)
|
119
|
+
comments = comments.select{ |c| [username, username_fail].include?(c.user.login) }.reverse
|
120
|
+
if comments.empty?
|
121
|
+
# If there are no comments yet, it has to be a new request.
|
122
|
+
@log.info 'New pull request detected, test run needed.'
|
123
|
+
return true
|
124
|
+
else
|
125
|
+
# Compare current sha ids of target and source branch with those from the last test run.
|
126
|
+
@target_head_sha ||= @github.commits(@project).first.sha
|
127
|
+
@pull_head_sha = pull_request.head.sha
|
128
|
+
# Initialize shas to ensure it will live on after the 'each' block.
|
129
|
+
shas = nil
|
130
|
+
comments.each do |comment|
|
131
|
+
shas = /master sha# ([\w]+) ; pull sha# ([\w]+)/.match(comment.body)
|
132
|
+
break if shas && shas[1] && shas[2]
|
133
|
+
end
|
134
|
+
# We finally found the latest comment that includes the necessary information.
|
135
|
+
if shas && shas[1] && shas[2]
|
136
|
+
@log.info "Current target sha: '#{@target_head_sha}', pull sha: '#{@pull_head_sha}'."
|
137
|
+
@log.info "Last test run target sha: '#{shas[1]}', pull sha: '#{shas[2]}'."
|
138
|
+
if self.rerun_on_source_change && (shas[2] != @pull_head_sha)
|
139
|
+
@log.info 'Re-running test due to new commit in pull request.'
|
140
|
+
return true
|
141
|
+
elsif self.rerun_on_target_change && (shas[1] != @target_head_sha)
|
142
|
+
@log.info 'Re-running test due to new commit in target branch.'
|
143
|
+
return true
|
144
|
+
end
|
145
|
+
else
|
146
|
+
@log.info 'New pull request detected, test run needed.'
|
147
|
+
return true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
@log.info "Not running tests for request ##{@request_id}."
|
151
|
+
false
|
152
|
+
end
|
153
|
+
|
154
|
+
def switch_branch_to_merged_state
|
155
|
+
# Fetch the merge-commit for the pull request.
|
156
|
+
# NOTE: This commit is automatically created by 'GitHub Merge Button'.
|
157
|
+
# FIXME: Use cheetah to pipe to @log.debug instead of that /dev/null hack.
|
158
|
+
`git fetch origin refs/pull/#{@request_id}/merge: &> /dev/null`
|
159
|
+
`git checkout FETCH_HEAD &> /dev/null`
|
160
|
+
unless $? == 0
|
161
|
+
@log.error 'Unable to switch to merge branch.'
|
162
|
+
abort
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
def switch_branch_back
|
168
|
+
# FIXME: Use cheetah to pipe to @log.debug instead of that /dev/null hack.
|
169
|
+
@log.info 'Switching back to original branch.'
|
170
|
+
# FIXME: For branches other than master, remember the original branch.
|
171
|
+
`git co master &> /dev/null`
|
172
|
+
end
|
173
|
+
|
174
|
+
# Output the result to a comment on the pull request on GitHub.
|
175
|
+
def comment_on_github
|
176
|
+
sha_string = "\n( master sha# #{@target_head_sha} ; pull sha# #{@pull_head_sha} )"
|
177
|
+
if @result
|
178
|
+
message = 'Well done! All tests are still passing after merging this pull request. '
|
179
|
+
else
|
180
|
+
unless self.username == self.username_fail
|
181
|
+
# Re-connect with username_fail and password_fail.
|
182
|
+
connect_to_github(self.username, self.password)
|
183
|
+
end
|
184
|
+
message = 'Unfortunately your tests are failing after merging this pull request. '
|
185
|
+
end
|
186
|
+
@github.add_comment(@project, @request_id, message + sha_string)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Collect git config information in a Hash for easy access.
|
190
|
+
# Checks '~/.gitconfig' for credentials.
|
191
|
+
def git_config
|
192
|
+
unless @git_config
|
193
|
+
# Read @git_config from local git config.
|
194
|
+
@git_config = {}
|
195
|
+
`git config --list`.split("\n").each do |line|
|
196
|
+
key, value = line.split('=')
|
197
|
+
@git_config[key] = value
|
198
|
+
end
|
199
|
+
end
|
200
|
+
@git_config
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
data/lib/pullermann.rb
ADDED
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pullermann
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Dominik Bamberger
|
9
|
+
- Thomas Schmidt
|
10
|
+
- Jordi Massaguer Pla
|
11
|
+
autorequire:
|
12
|
+
bindir: bin
|
13
|
+
cert_chain: []
|
14
|
+
date: 2012-06-14 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: octokit
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ! '>='
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '0'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '0'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: rspec
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ! '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
description: Pullermann runs your project's test suite on open pull requests on GitHub.
|
49
|
+
Afterwards it posts the result as a comment to the respective request. That way
|
50
|
+
you know whether your tests are still going to pass if you accept the request and
|
51
|
+
merge the code.
|
52
|
+
email: bamboo@suse.com
|
53
|
+
executables: []
|
54
|
+
extensions: []
|
55
|
+
extra_rdoc_files: []
|
56
|
+
files:
|
57
|
+
- LICENSE
|
58
|
+
- lib/pullermann.rb
|
59
|
+
- lib/tasks/pullermann.rake
|
60
|
+
- lib/pullermann/pullermann.rb
|
61
|
+
- lib/pullermann/railtie.rb
|
62
|
+
homepage: http://github.com/b4mboo/pullermann
|
63
|
+
licenses: []
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ! '>='
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 1.8.24
|
83
|
+
signing_key:
|
84
|
+
specification_version: 3
|
85
|
+
summary: An easy way to test pull requests.
|
86
|
+
test_files: []
|