cipr 0.1.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/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: []
|