cipr 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +30 -0
- data/bin/cipr +58 -0
- data/lib/cipr/github.rb +120 -0
- data/lib/cipr/pull_request.rb +51 -0
- data/lib/cipr/version.rb +3 -0
- data/lib/cipr.rb +21 -0
- data/spec/github.rb +4 -0
- metadata +103 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Mark Simoneau
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Cipr #
|
2
|
+
|
3
|
+
Cipr is continuous integration for your pull requests. It detects new pull reqests or changed pull requests and runs specs on them
|
4
|
+
|
5
|
+
## Basic Usage ##
|
6
|
+
|
7
|
+
gem install cipr
|
8
|
+
cipr [-u <github_user> [-p <github_password> | -t <github_token>]] http://github.com/<github_user>/<github_repo>
|
9
|
+
|
10
|
+
## Configuration ##
|
11
|
+
|
12
|
+
By default, cipr does the following
|
13
|
+
|
14
|
+
Watches your repository for open pull requests
|
15
|
+
Upon finding one, clones the repository in a new directory, applies the pull request patch, and runs 'rake spec' on it
|
16
|
+
The results are added as a comment to the pull request
|
17
|
+
|
18
|
+
If you have private repositories, you'll need to set up your ~/.netrc file like so:
|
19
|
+
|
20
|
+
machine github.com
|
21
|
+
login <github_username>
|
22
|
+
password <github_password>
|
23
|
+
|
24
|
+
## TODO ##
|
25
|
+
|
26
|
+
Provide a mechanism for defining what to run on a repo
|
27
|
+
|
28
|
+
## About ##
|
29
|
+
|
30
|
+
Cipr (pronounced like sipper) is continuous integration for pull requests
|
data/bin/cipr
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
|
3
|
+
|
4
|
+
require 'choice'
|
5
|
+
require 'cipr'
|
6
|
+
|
7
|
+
Choice.options do
|
8
|
+
banner "Usage: #{File.basename(__FILE__)} [-u <github_user>] [-p <github_password>] [-d] user/repo"
|
9
|
+
header ''
|
10
|
+
header 'Github Options:'
|
11
|
+
|
12
|
+
option :user do
|
13
|
+
short '-u'
|
14
|
+
long '--user=USERNAME'
|
15
|
+
desc "Github username for authentication"
|
16
|
+
end
|
17
|
+
|
18
|
+
option :password do
|
19
|
+
short '-p'
|
20
|
+
long '--password=PASSWORD'
|
21
|
+
desc "Github password for authentication"
|
22
|
+
end
|
23
|
+
|
24
|
+
option :directory do
|
25
|
+
short '-d'
|
26
|
+
long '--directory=GIT_REPO_DIRECTORY'
|
27
|
+
desc "Github repo location"
|
28
|
+
end
|
29
|
+
|
30
|
+
option :command do
|
31
|
+
short '-c'
|
32
|
+
long '--command=COMMAND_TO_RUN'
|
33
|
+
desc "Command to run to test, defaults to 'rake spec'"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
separator ''
|
38
|
+
separator 'Common options: '
|
39
|
+
|
40
|
+
option :help do
|
41
|
+
long '--help'
|
42
|
+
desc 'Show this message'
|
43
|
+
end
|
44
|
+
|
45
|
+
option :version do
|
46
|
+
short '-v'
|
47
|
+
long '--version'
|
48
|
+
desc 'Show version'
|
49
|
+
action do
|
50
|
+
puts "#{File.basename(__FILE__)} v#{Cipr::Version}"
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
options = Choice.choices
|
57
|
+
|
58
|
+
Cipr::Server.go(Choice.rest[0].to_s, options)
|
data/lib/cipr/github.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'httparty'
|
2
|
+
require 'git'
|
3
|
+
|
4
|
+
module Cipr
|
5
|
+
class Repo
|
6
|
+
include HTTParty
|
7
|
+
base_uri 'https://api.github.com'
|
8
|
+
|
9
|
+
def initialize(repo, options={})
|
10
|
+
@repo_user, @repo = repo.split("/")
|
11
|
+
@directory = options[:directory]
|
12
|
+
@prep = options[:prep] || 'bundle && rake db:migrate'
|
13
|
+
@command = options[:command] || 'bundle exec rake spec'
|
14
|
+
@auth = {:username => options[:user] || @repo_user, :password => options[:password]}.reject {|k,v| v.nil?}
|
15
|
+
end
|
16
|
+
|
17
|
+
def pull_requests(state=:open)
|
18
|
+
get("/repos/#@repo_user/#@repo/pulls", :query => {:state => state}).map {|p| PullRequest.new(self, p)}
|
19
|
+
end
|
20
|
+
|
21
|
+
def pull_request(id)
|
22
|
+
PullRequest.new(self, get("/repos/#@repo_user/#@repo/pulls/#{id}"))
|
23
|
+
end
|
24
|
+
|
25
|
+
def comment(issue_id, body)
|
26
|
+
post("/repos/#@repo_user/#@repo/issues/#{issue_id}/comments", :body => {'body' => body}.to_json)
|
27
|
+
end
|
28
|
+
|
29
|
+
def test
|
30
|
+
tested = 0
|
31
|
+
pull_requests.each do |pr|
|
32
|
+
pr = pull_request(pr.number)
|
33
|
+
next if pr.merged?
|
34
|
+
|
35
|
+
ready = false
|
36
|
+
if pr.mergeable?
|
37
|
+
puts "Applying Pull Request ##{pr.number}"
|
38
|
+
ready = pr.apply =~ /Updating/
|
39
|
+
else
|
40
|
+
puts "Checking out Pull Request ##{pr.number}"
|
41
|
+
pr.checkout
|
42
|
+
ready = true
|
43
|
+
end
|
44
|
+
|
45
|
+
if ready
|
46
|
+
puts "Testing Pull Request ##{pr.number}"
|
47
|
+
output = execute
|
48
|
+
unless output.nil? || output.strip.empty?
|
49
|
+
pr.comment(output)
|
50
|
+
tested += 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
puts "Tested #{tested} pull requests this round..."
|
55
|
+
tested
|
56
|
+
end
|
57
|
+
|
58
|
+
def git
|
59
|
+
@g ||= @directory ? Git.open(@directory) : Git.clone("https://github.com/#@repo_user/#@repo.git", @repo)
|
60
|
+
end
|
61
|
+
|
62
|
+
def apply_pull_request(pr)
|
63
|
+
pr = pull_request(pr.respond_to?(:number) ? pr.number : pr)
|
64
|
+
git.branch(pr.merge_to).checkout
|
65
|
+
git.branch("#{pr.username}-#{pr.merge_from}").checkout
|
66
|
+
git.add_remote(pr.username, pr.pull_repo) unless git.remotes.map(&:to_s).include?(pr.username)
|
67
|
+
git.pull(pr.username, "#{pr.username}/#{pr.merge_from}", "Merge pull request ##{pr.number} - #{pr.title}")
|
68
|
+
end
|
69
|
+
|
70
|
+
def checkout_pull_request(pr)
|
71
|
+
pr = pull_request(pr.respond_to?(:number) ? pr.number : pr)
|
72
|
+
git.branch(pr.merge_to).checkout
|
73
|
+
git.add_remote(pr.username, pr.pull_repo) unless git.remotes.map(&:to_s).include?(pr.username)
|
74
|
+
git.branch("#{pr.username}/#{pr.merge_from}").checkout
|
75
|
+
end
|
76
|
+
|
77
|
+
def auth_string
|
78
|
+
[@auth[:username], @auth[:password]].compact.join(":")
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def execute
|
84
|
+
puts @prep
|
85
|
+
`/bin/bash -l -c 'cd #{git.dir} && #{@prep}'`
|
86
|
+
puts @command
|
87
|
+
`/bin/bash -l -c 'cd #{git.dir} && #{@command} > /tmp/test_output.txt'`
|
88
|
+
result = File.open("/tmp/test_output.txt").gets
|
89
|
+
split_comment(result, 80)
|
90
|
+
end
|
91
|
+
|
92
|
+
def split_comment(comment, line_length=80)
|
93
|
+
comment.split("\n").map do |l|
|
94
|
+
p = []
|
95
|
+
while l.length > line_length
|
96
|
+
p << l[0..(line_length-1)]
|
97
|
+
l = l[line_length..-1]
|
98
|
+
end
|
99
|
+
p << l
|
100
|
+
p.join("\n")
|
101
|
+
end.join("\n")
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def get(path, options={})
|
106
|
+
options.merge!(:basic_auth => @auth)
|
107
|
+
self.class.get(path, options)
|
108
|
+
end
|
109
|
+
|
110
|
+
def post(path, options={})
|
111
|
+
options.merge!(:basic_auth => @auth)
|
112
|
+
self.class.post(path, @auth.merge(options))
|
113
|
+
end
|
114
|
+
|
115
|
+
def put(path, options={})
|
116
|
+
options.merge!(:basic_auth => @auth)
|
117
|
+
self.class.put(path, @auth.merge(options))
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
module Cipr
|
3
|
+
class PullRequest < OpenStruct
|
4
|
+
def initialize(repo, *args)
|
5
|
+
@repo = repo
|
6
|
+
super(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def comment(body)
|
10
|
+
@repo.comment(self.number, body)
|
11
|
+
end
|
12
|
+
|
13
|
+
def mergeable?
|
14
|
+
mergeable
|
15
|
+
end
|
16
|
+
|
17
|
+
def merged?
|
18
|
+
merged
|
19
|
+
end
|
20
|
+
|
21
|
+
def username
|
22
|
+
head['user']['login']
|
23
|
+
end
|
24
|
+
|
25
|
+
def merge_to
|
26
|
+
base['ref']
|
27
|
+
end
|
28
|
+
|
29
|
+
def merge_from
|
30
|
+
head['ref']
|
31
|
+
end
|
32
|
+
|
33
|
+
def pull_repo
|
34
|
+
auth = @repo.auth_string
|
35
|
+
if auth && !auth.empty?
|
36
|
+
head['repo']['clone_url'].sub("https://", "https://#{auth}@")
|
37
|
+
else
|
38
|
+
head['repo']['clone_url']
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def apply
|
43
|
+
@repo.apply_pull_request(self)
|
44
|
+
end
|
45
|
+
|
46
|
+
def checkout
|
47
|
+
@repo.checkout_pull_request(self)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/cipr/version.rb
ADDED
data/lib/cipr.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'cipr/version'
|
2
|
+
require_relative 'cipr/github'
|
3
|
+
require_relative 'cipr/pull_request'
|
4
|
+
|
5
|
+
module Cipr
|
6
|
+
class Server
|
7
|
+
def self.go(repo, options={})
|
8
|
+
wait_time = 1
|
9
|
+
c = Cipr::Repo.new(repo, options)
|
10
|
+
while true
|
11
|
+
puts "Checking for Pull Requests..."
|
12
|
+
if c.test > 0
|
13
|
+
wait_time = 2
|
14
|
+
else
|
15
|
+
wait_time += 2 if wait_time < 30
|
16
|
+
end
|
17
|
+
sleep 60*wait_time
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/spec/github.rb
ADDED
metadata
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cipr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Mark Simoneau
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-10 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: choice
|
16
|
+
requirement: &70312260471920 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70312260471920
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: github_api
|
27
|
+
requirement: &70312260470680 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70312260470680
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: git
|
38
|
+
requirement: &70312260469340 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70312260469340
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rspec
|
49
|
+
requirement: &70312260467920 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70312260467920
|
58
|
+
description: cipr tests your pull requests and comments the results
|
59
|
+
email: mark@quarternotecoda.com
|
60
|
+
executables:
|
61
|
+
- cipr
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- README.md
|
66
|
+
- LICENSE
|
67
|
+
- lib/cipr/github.rb
|
68
|
+
- lib/cipr/pull_request.rb
|
69
|
+
- lib/cipr/version.rb
|
70
|
+
- lib/cipr.rb
|
71
|
+
- bin/cipr
|
72
|
+
- spec/github.rb
|
73
|
+
homepage: http://github.com/marksim/cipr
|
74
|
+
licenses: []
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ! '>='
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: '0'
|
85
|
+
segments:
|
86
|
+
- 0
|
87
|
+
hash: 2263140989473189672
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
hash: 2263140989473189672
|
97
|
+
requirements: []
|
98
|
+
rubyforge_project:
|
99
|
+
rubygems_version: 1.8.10
|
100
|
+
signing_key:
|
101
|
+
specification_version: 3
|
102
|
+
summary: cipr tests your pull requests.
|
103
|
+
test_files: []
|