cijoe 0.0.1

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 ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Chris Wanstrath
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.
@@ -0,0 +1,157 @@
1
+ CI Joe
2
+ ======
3
+
4
+ Joe is a [Continuous
5
+ Integration](http://en.wikipedia.org/wiki/Continuous_integration)
6
+ server that'll run your tests on demand and report their pass/fail status.
7
+
8
+ Because knowing is half the battle.
9
+
10
+ ![The Battle](http://img.skitch.com/20090805-g4a2qhttwij8n2jr9t552efn3k.png)
11
+
12
+ Quickstart
13
+ ----------
14
+
15
+ $ rip install git://github.com/defunkt/cijoe.git 0.1.1
16
+ $ git clone git://github.com/you/yourrepo.git
17
+ $ cijoe yourrepo
18
+
19
+ Boom. Navigate to http://localhost:4567 to see Joe in action.
20
+ Check `cijoe -h` for other options.
21
+
22
+ Basically you need to run `cijoe` and hand it the path to a git
23
+ repo. Make sure this isn't a shared repo: Joe needs to own it.
24
+
25
+ Joe looks for various git config settings in the repo you hand it. For
26
+ instance, you can tell Joe what command to run by setting
27
+ `cijoe.runner`:
28
+
29
+ $ git config --add cijoe.runner "rake -s test:units"
30
+
31
+ Joe doesn't care about Ruby, Python, or whatever. As long as the
32
+ runner returns a non-zero exit status on fail and a zero on success,
33
+ everyone is happy.
34
+
35
+ Need to do some massaging of your repo before the tests run, like
36
+ maybe swapping in a new database.yml? No problem - Joe will try to
37
+ run `.git/hooks/after-reset` if it exists before each build phase.
38
+ Do it in there. Just make sure it's executable.
39
+
40
+ Want to notify IRC or email on test pass or failure? Joe will run
41
+ `.git/hooks/build-failed` or `.git/hooks/build-worked` if they exist
42
+ and are executable on build pass / fail. They're just shell scripts -
43
+ put whatever you want in there.
44
+
45
+ Tip: your repo's `HEAD` will point to the commit used to run the
46
+ build. Pull any metadata you want out of that scro.
47
+
48
+
49
+ Other Branches
50
+ --------------
51
+
52
+ Want joe to run against a branch other than `master`? No problem:
53
+
54
+ $ git config --add cijoe.branch deploy
55
+
56
+
57
+ Campfire
58
+ --------
59
+
60
+ Campfire notification is included, because it's what we use. Want Joe
61
+ notify your Campfire? Put this in your repo's `.git/config`:
62
+
63
+ [campfire]
64
+ user = your@campfire.email
65
+ pass = passw0rd
66
+ subdomain = whatever
67
+ room = Awesomeness
68
+ ssl = false
69
+
70
+ Or do it the old fashion way:
71
+
72
+ $ cd yourrepo
73
+ $ git config --add campfire.user chris@ozmm.org
74
+ $ git config --add campfire.subdomain github
75
+ etc.
76
+
77
+
78
+ Multiple Projects
79
+ -----------------
80
+
81
+ Want CI for multiple projects? Just start multiple instances of Joe!
82
+ He can run on any port - try `cijoe -h` for more options.
83
+
84
+ Thanks to Dean Strelau you can also run multiple instances of CI Joe using
85
+ a single `config.ru` file.
86
+
87
+ In particular, it is possible to mount Server at a subpath:
88
+
89
+ map '/foo' do
90
+ run CIJoe::Server.set(:project_path => 'projects/foo')
91
+ end
92
+
93
+ and you can even run multiple instances of Joe if you subclass:
94
+
95
+ map '/foo' do
96
+ run Class.new(CIJoe::Server).set(:project_path => 'projects/foo')
97
+ end
98
+ map '/bar' do
99
+ run Class.new(CIJoe::Server).set(:project_path => 'projects/bar')
100
+ end
101
+
102
+
103
+ HTTP Auth
104
+ ---------
105
+
106
+ Worried about people triggering your builds? Setup HTTP auth:
107
+
108
+ $ git config --add cijoe.user chris
109
+ $ git config --add cijoe.pass secret
110
+
111
+
112
+ GitHub Integration
113
+ ------------------
114
+
115
+ Any POST to Joe will trigger a build. If you are hiding Joe behind
116
+ HTTP auth, that's okay - GitHub knows how to authenticate properly.
117
+
118
+ ![Post-Receive URL](http://img.skitch.com/20090806-d2bxrk733gqu8m11tf4kyir5d8.png)
119
+
120
+ You can find the Post-Receive option under the 'Service Hooks' subtab
121
+ of your project's "Admin" tab.
122
+
123
+
124
+ Daemonize
125
+ ---------
126
+
127
+ Want to run Joe as a daemon? Use `nohup`:
128
+
129
+ $ nohup cijoe -p 4444 repo &
130
+
131
+
132
+ Other CI Servers
133
+ ----------------
134
+
135
+ Need more features? More notifiers? Check out one of these bad boys:
136
+
137
+ * [Cerberus](http://cerberus.rubyforge.org/)
138
+ * [Integrity](http://integrityapp.com/)
139
+ * [CruiseControl.rb](http://cruisecontrolrb.thoughtworks.com/)
140
+ * [BuildBot](http://buildbot.net/trac)
141
+
142
+
143
+ Screenshots
144
+ -----------
145
+
146
+ ![Building](http://img.skitch.com/20090806-ryw34ksi5ixnrdwxcptqy28iy7.png)
147
+
148
+ ![Built](http://img.skitch.com/20090806-f7j3r65yecaq13hdcxqwtc5krd.)
149
+
150
+
151
+ Questions? Concerns?
152
+ --------------------
153
+
154
+ [Issues](http://github.com/defunkt/cijoe/issues) or [the mailing list](http://groups.google.com/group/cijoe).
155
+
156
+
157
+ ( Chris Wanstrath :: chris@ozmm.org )
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'jeweler'
3
+
4
+ # We're not putting VERSION or VERSION.yml in the root,
5
+ # so we have to help Jeweler find our version.
6
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/lib'
7
+ require 'cijoe/version'
8
+
9
+ CIJoe::Version.instance_eval do
10
+ def refresh
11
+ end
12
+ end
13
+
14
+ class Jeweler
15
+ def version_helper
16
+ CIJoe::Version
17
+ end
18
+
19
+ def version_exists?
20
+ true
21
+ end
22
+ end
23
+
24
+ Jeweler::Tasks.new do |gemspec|
25
+ gemspec.name = "cijoe"
26
+ gemspec.summary = "CI Joe is a simple Continuous Integration server."
27
+ gemspec.description = "CI Joe is a simple Continuous Integration server."
28
+ gemspec.email = "chris@ozmm.org"
29
+ gemspec.homepage = "http://github.com/defunkt/cijoe"
30
+ gemspec.authors = ["Chris Wanstrath"]
31
+ gemspec.add_dependency 'choice'
32
+ gemspec.add_dependency 'sinatra'
33
+ gemspec.add_dependency 'open4'
34
+ end
35
+ rescue LoadError
36
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
37
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
3
+
4
+ require 'cijoe'
5
+ require 'choice'
6
+
7
+ Choice.options do
8
+ banner "Usage: #{File.basename(__FILE__)} [-hpv] path_to_git_repo"
9
+ header ''
10
+ header 'Server options:'
11
+
12
+ option :host do
13
+ d = "0.0.0.0"
14
+ short '-h'
15
+ long '--host=HOST'
16
+ desc "The hostname or ip of the host to bind to (default #{d})"
17
+ default d
18
+ end
19
+
20
+ option :port do
21
+ d = 4567
22
+ short '-p'
23
+ long '--port=PORT'
24
+ desc "The port to listen on (default #{d})"
25
+ cast Integer
26
+ default d
27
+ end
28
+
29
+ separator ''
30
+ separator 'Common options: '
31
+
32
+ option :help do
33
+ long '--help'
34
+ desc 'Show this message'
35
+ end
36
+
37
+ option :version do
38
+ short '-v'
39
+ long '--version'
40
+ desc 'Show version'
41
+ action do
42
+ puts "#{File.basename(__FILE__)} v#{CIJoe::Version}"
43
+ exit
44
+ end
45
+ end
46
+ end
47
+
48
+ options = Choice.choices
49
+ CIJoe::Server.start(options[:host], options[:port], Choice.rest[0])
@@ -0,0 +1,5 @@
1
+ git://github.com/collectiveidea/tinder.git 1.2.0
2
+ git://github.com/sinatra/sinatra.git 0.9.4
3
+ git://github.com/rack/rack.git 1.0
4
+ git://github.com/defunkt/choice.git 8b125564
5
+ git://github.com/ahoward/open4.git 25c3ed8
@@ -0,0 +1,135 @@
1
+ ##
2
+ # CI Joe.
3
+ # Because knowing is half the battle.
4
+ #
5
+ # This is a stupid simple CI server. It can build one (1)
6
+ # git-based project only.
7
+ #
8
+ # It only remembers the last build.
9
+ #
10
+ # It only notifies to Campfire.
11
+ #
12
+ # It's a RAH (Real American Hero).
13
+ #
14
+ # Seriously, I'm gonna be nuts about keeping this simple.
15
+
16
+ begin
17
+ require 'open4'
18
+ rescue LoadError
19
+ abort "** Please install open4"
20
+ end
21
+
22
+ require 'cijoe/version'
23
+ require 'cijoe/config'
24
+ require 'cijoe/commit'
25
+ require 'cijoe/build'
26
+ require 'cijoe/campfire'
27
+ require 'cijoe/server'
28
+
29
+ class CIJoe
30
+ attr_reader :user, :project, :url, :current_build, :last_build
31
+
32
+ def initialize(project_path)
33
+ project_path = File.expand_path(project_path)
34
+ Dir.chdir(project_path)
35
+
36
+ @user, @project = git_user_and_project
37
+ @url = "http://github.com/#{@user}/#{@project}"
38
+
39
+ @last_build = nil
40
+ @current_build = nil
41
+
42
+ trap("INT") { stop }
43
+ end
44
+
45
+ # is a build running?
46
+ def building?
47
+ !!@current_build
48
+ end
49
+
50
+ # the pid of the running child process
51
+ def pid
52
+ building? and @pid
53
+ end
54
+
55
+ # kill the child and exit
56
+ def stop
57
+ Process.kill(9, pid) if pid
58
+ exit!
59
+ end
60
+
61
+ # build callbacks
62
+ def build_failed(output, error)
63
+ finish_build :failed, "#{error}\n\n#{output}"
64
+ run_hook "build-failed"
65
+ end
66
+
67
+ def build_worked(output)
68
+ finish_build :worked, output
69
+ run_hook "build-worked"
70
+ end
71
+
72
+ def finish_build(status, output)
73
+ @current_build.finished_at = Time.now
74
+ @current_build.status = status
75
+ @current_build.output = output
76
+ @last_build = @current_build
77
+ @current_build = nil
78
+ @last_build.notify if @last_build.respond_to? :notify
79
+ end
80
+
81
+ # run the build but make sure only
82
+ # one is running at a time
83
+ def build
84
+ return if building?
85
+ @current_build = Build.new(@user, @project)
86
+ Thread.new { build! }
87
+ end
88
+
89
+ # update git then run the build
90
+ def build!
91
+ out, err, status = '', '', nil
92
+ git_update
93
+ @current_build.sha = git_sha
94
+
95
+ status = Open4.popen4(runner_command) do |pid, stdin, stdout, stderr|
96
+ @pid = pid
97
+ err, out = stderr.read.strip, stdout.read.strip
98
+ end
99
+
100
+ status.exitstatus.to_i == 0 ? build_worked(out) : build_failed(out, err)
101
+ rescue Object => e
102
+ build_failed('', e.to_s)
103
+ end
104
+
105
+ # shellin' out
106
+ def runner_command
107
+ runner = Config.cijoe.runner.to_s
108
+ runner == '' ? "rake -s test:units" : runner
109
+ end
110
+
111
+ def git_sha
112
+ `git rev-parse origin/#{git_branch}`.chomp
113
+ end
114
+
115
+ def git_update
116
+ `git fetch origin && git reset --hard origin/#{git_branch}`
117
+ run_hook "after-reset"
118
+ end
119
+
120
+ def git_user_and_project
121
+ Config.remote.origin.url.to_s.chomp('.git').split(':')[-1].split('/')[-2, 2]
122
+ end
123
+
124
+ def git_branch
125
+ branch = Config.cijoe.branch.to_s
126
+ branch == '' ? "master" : branch
127
+ end
128
+
129
+ # massage our repo
130
+ def run_hook(hook)
131
+ if File.exists?(file=".git/hooks/#{hook}") && File.executable?(file)
132
+ `sh #{file}`
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,33 @@
1
+ class CIJoe
2
+ class Build < Struct.new(:user, :project, :started_at, :finished_at, :sha, :status, :output)
3
+ def initialize(*args)
4
+ super
5
+ self.started_at = Time.now
6
+ end
7
+
8
+ def status
9
+ return super if started_at && finished_at
10
+ :building
11
+ end
12
+
13
+ def failed?
14
+ status == :failed
15
+ end
16
+
17
+ def worked?
18
+ status == :worked
19
+ end
20
+
21
+ def short_sha
22
+ sha[0,7] if sha
23
+ end
24
+
25
+ def clean_output
26
+ output.gsub(/\e\[.+?m/, '').strip
27
+ end
28
+
29
+ def commit
30
+ @commit ||= Commit.new(sha, user, project)
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ class CIJoe
2
+ module Campfire
3
+ def self.activate
4
+ if valid_config?
5
+ require 'tinder'
6
+
7
+ CIJoe::Build.class_eval do
8
+ include CIJoe::Campfire
9
+ end
10
+
11
+ puts "Loaded Campfire notifier"
12
+ else
13
+ puts "Can't load Campfire notifier."
14
+ puts "Please add the following to your project's .git/config:"
15
+ puts "[campfire]"
16
+ puts "\tuser = your@campfire.email"
17
+ puts "\tpass = passw0rd"
18
+ puts "\tsubdomain = whatever"
19
+ puts "\troom = Awesomeness"
20
+ puts "\tssl = false"
21
+ end
22
+ end
23
+
24
+ def self.config
25
+ @config ||= {
26
+ :subdomain => Config.campfire.subdomain.to_s,
27
+ :user => Config.campfire.user.to_s,
28
+ :pass => Config.campfire.pass.to_s,
29
+ :room => Config.campfire.room.to_s,
30
+ :ssl => Config.campfire.ssl.to_s.strip == 'true'
31
+ }
32
+ end
33
+
34
+ def self.valid_config?
35
+ %w( subdomain user pass room ).all? do |key|
36
+ !config[key.intern].empty?
37
+ end
38
+ end
39
+
40
+ def notify
41
+ room.speak "#{short_message}. #{commit.url}"
42
+ room.paste full_message if failed?
43
+ room.leave
44
+ end
45
+
46
+ private
47
+ def room
48
+ @room ||= begin
49
+ config = Campfire.config
50
+ options = {}
51
+ options[:ssl] = config[:ssl] ? true : false
52
+ campfire = Tinder::Campfire.new(config[:subdomain], options)
53
+ campfire.login(config[:user], config[:pass])
54
+ campfire.find_room_by_name(config[:room])
55
+ end
56
+ end
57
+
58
+ def short_message
59
+ "Build #{short_sha} of #{project} #{worked? ? "was successful" : "failed"}"
60
+ end
61
+
62
+ def full_message
63
+ <<-EOM
64
+ Commit Message: #{commit.message}
65
+ Commit Date: #{commit.committed_at}
66
+ Commit Author: #{commit.author}
67
+
68
+ #{clean_output}
69
+ EOM
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,27 @@
1
+ class CIJoe
2
+ class Commit < Struct.new(:sha, :user, :project)
3
+ def url
4
+ "http://github.com/#{user}/#{project}/commit/#{sha}"
5
+ end
6
+
7
+ def author
8
+ raw_commit_lines[1].split(':')[-1]
9
+ end
10
+
11
+ def committed_at
12
+ raw_commit_lines[2].split(':', 2)[-1]
13
+ end
14
+
15
+ def message
16
+ raw_commit_lines[4].split(':')[-1].strip
17
+ end
18
+
19
+ def raw_commit
20
+ @raw_commit ||= `git show #{sha}`.chomp
21
+ end
22
+
23
+ def raw_commit_lines
24
+ @raw_commit_lines ||= raw_commit.split("\n")
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ class CIJoe
2
+ class Config
3
+ def self.method_missing(command, *args)
4
+ new(command)
5
+ end
6
+
7
+ def initialize(command, parent = nil)
8
+ @command = command
9
+ @parent = parent
10
+ end
11
+
12
+ def method_missing(command, *args)
13
+ Config.new(command, self)
14
+ end
15
+
16
+ def to_s
17
+ `git config #{config_string}`.chomp
18
+ end
19
+
20
+ def config_string
21
+ @parent ? "#{@parent.config_string}.#{@command}" : @command
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,79 @@
1
+ require 'sinatra'
2
+ require 'erb'
3
+
4
+ class CIJoe
5
+ class Server < Sinatra::Base
6
+ dir = File.dirname(File.expand_path(__FILE__))
7
+
8
+ set :views, "#{dir}/views"
9
+ set :public, "#{dir}/public"
10
+ set :static, true
11
+
12
+ get '/?' do
13
+ erb(:template, {}, :joe => @joe)
14
+ end
15
+
16
+ post '/?' do
17
+ payload = params[:payload].to_s
18
+ if payload.empty? || payload.include?(@joe.git_branch)
19
+ @joe.build
20
+ end
21
+ redirect request.path
22
+ end
23
+
24
+
25
+ helpers do
26
+ include Rack::Utils
27
+ alias_method :h, :escape_html
28
+
29
+ # thanks integrity!
30
+ def bash_color_codes(string)
31
+ string.gsub("\e[0m", '</span>').
32
+ gsub("\e[31m", '<span class="color31">').
33
+ gsub("\e[32m", '<span class="color32">').
34
+ gsub("\e[33m", '<span class="color33">').
35
+ gsub("\e[34m", '<span class="color34">').
36
+ gsub("\e[35m", '<span class="color35">').
37
+ gsub("\e[36m", '<span class="color36">').
38
+ gsub("\e[37m", '<span class="color37">')
39
+ end
40
+
41
+ def pretty_time(time)
42
+ time.strftime("%Y-%m-%d %H:%M")
43
+ end
44
+ end
45
+
46
+ def initialize(*args)
47
+ super
48
+ check_project
49
+ @joe = CIJoe.new(options.project_path)
50
+ setup_auth
51
+
52
+ CIJoe::Campfire.activate
53
+ end
54
+
55
+ def self.start(host, port, project_path)
56
+ set :project_path, project_path
57
+ CIJoe::Server.run! :host => host, :port => port
58
+ end
59
+
60
+ def check_project
61
+ if options.project_path.nil? || !File.exists?(File.expand_path(options.project_path))
62
+ puts "Whoops! I need the path to a Git repo."
63
+ puts " $ git clone git@github.com:username/project.git project"
64
+ abort " $ cijoe project"
65
+ end
66
+ end
67
+
68
+ def setup_auth
69
+ user, pass = Config.cijoe.user.to_s, Config.cijoe.pass.to_s
70
+
71
+ if user != '' && pass != ''
72
+ use Rack::Auth::Basic do |username, password|
73
+ [ username, password ] == [ user, pass ]
74
+ end
75
+ puts "Using HTTP basic auth"
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,3 @@
1
+ class CIJoe
2
+ Version = "0.0.1"
3
+ end
@@ -0,0 +1,275 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <style>
5
+ /*****************************************************************************/
6
+ /*
7
+ /* Common
8
+ /*
9
+ /*****************************************************************************/
10
+
11
+ /* Global Reset */
12
+
13
+ * {
14
+ margin: 0;
15
+ padding: 0;
16
+ }
17
+
18
+ html, body {
19
+ height: 100%;
20
+ }
21
+
22
+ body {
23
+ background-color: white;
24
+ font: 13.34px helvetica, arial, clean, sans-serif;
25
+ *font-size: small;
26
+ text-align: center;
27
+ }
28
+
29
+ h1, h2, h3, h4, h5, h6 {
30
+ font-size: 100%;
31
+ }
32
+
33
+ h1 {
34
+ margin-bottom: 1em;
35
+ }
36
+
37
+ h1 a {
38
+ text-decoration: none;
39
+ color: #000;
40
+ }
41
+
42
+ .failed, .color31 {
43
+ color: red !important;
44
+ }
45
+
46
+ .worked, .color32 {
47
+ color: green !important;
48
+ }
49
+
50
+ .errored, .color33 {
51
+ color: yellow !important;
52
+ }
53
+
54
+ p {
55
+ margin: 1em 0;
56
+ }
57
+
58
+ a {
59
+ color: #00a;
60
+ }
61
+
62
+ a:hover {
63
+ color: black;
64
+ }
65
+
66
+ a:visited {
67
+ color: #a0a;
68
+ }
69
+
70
+ table {
71
+ font-size: inherit;
72
+ font: 100%;
73
+ }
74
+
75
+ /*****************************************************************************/
76
+ /*
77
+ /* Home
78
+ /*
79
+ /*****************************************************************************/
80
+
81
+ ul.posts {
82
+ list-style-type: none;
83
+ margin-bottom: 2em;
84
+ }
85
+
86
+ ul.posts li {
87
+ line-height: 1.75em;
88
+ }
89
+
90
+ ul.posts .date {
91
+ color: #aaa;
92
+ font-family: Monaco, "Courier New", monospace;
93
+ font-size: 80%;
94
+ }
95
+
96
+ /*****************************************************************************/
97
+ /*
98
+ /* Site
99
+ /*
100
+ /*****************************************************************************/
101
+
102
+ .site {
103
+ font-size: 110%;
104
+ text-align: justify;
105
+ width: 40em;
106
+ margin: 3em auto 2em auto;
107
+ line-height: 1.5em;
108
+ }
109
+
110
+ .title {
111
+ color: #a00;
112
+ font-weight: bold;
113
+ margin-bottom: 2em;
114
+ }
115
+
116
+ .site .title a {
117
+ color: #a00;
118
+ text-decoration: none;
119
+ }
120
+
121
+ .site .title a:hover {
122
+ color: black;
123
+ }
124
+
125
+ .site .title .extra {
126
+ color: #aaa;
127
+ text-decoration: none;
128
+ margin-left: 1em;
129
+ font-size: 0.9em;
130
+ }
131
+
132
+ .site .title a.extra:hover {
133
+ color: black;
134
+ }
135
+
136
+ .site .meta {
137
+ color: #aaa;
138
+ }
139
+
140
+ .site .footer {
141
+ font-size: 80%;
142
+ color: #666;
143
+ border-top: 4px solid #eee;
144
+ margin-top: 2em;
145
+ overflow: hidden;
146
+ }
147
+
148
+ .site .footer .contact {
149
+ float: left;
150
+ margin-right: 3em;
151
+ }
152
+
153
+ .site .footer .contact a {
154
+ color: #8085C1;
155
+ }
156
+
157
+ .site .footer .rss {
158
+ margin-top: 1.1em;
159
+ margin-right: -.2em;
160
+ float: right;
161
+ }
162
+
163
+ .site .footer .rss img {
164
+ border: 0;
165
+ }
166
+
167
+ /*****************************************************************************/
168
+ /*
169
+ /* Posts
170
+ /*
171
+ /*****************************************************************************/
172
+
173
+ #post {
174
+
175
+ }
176
+
177
+ /* standard */
178
+
179
+ #post pre {
180
+ border: 1px solid #ddd;
181
+ background-color: #eef;
182
+ padding: 0 .4em;
183
+ }
184
+
185
+ #post ul,
186
+ #post ol {
187
+ margin-left: 1.25em;
188
+ }
189
+
190
+ #post code {
191
+ border: 1px solid #ddd;
192
+ background-color: #eef;
193
+ font-size: 95%;
194
+ padding: 0 .2em;
195
+ }
196
+
197
+ #post pre code {
198
+ border: none;
199
+ }
200
+
201
+ /* terminal */
202
+
203
+ pre.terminal {
204
+ border: 1px solid black;
205
+ background-color: #333;
206
+ color: white;
207
+ padding: 5px;
208
+ overflow: auto;
209
+ word-wrap: break-word;
210
+ }
211
+
212
+ pre.terminal code {
213
+ font-family: 'Bitstream Vera Sans Mono', 'Courier', monospace;
214
+ background-color: #333;
215
+ }
216
+ </style>
217
+ </head>
218
+ <body>
219
+ <div class="site">
220
+ <div class="title">
221
+ <a href="/">CI Joe</a>
222
+ <span class="extra">because knowing is half the battle</span>
223
+ </div>
224
+
225
+ <div id="home">
226
+ <h1><a href="<%= joe.url %>"><%= joe.project %></a></h1>
227
+ <ul class="posts">
228
+ <% if joe.current_build %>
229
+ <li>
230
+ <span class="date"><%= pretty_time(joe.current_build.started_at) if joe.current_build %></span> &raquo;
231
+ <% if joe.current_build.sha %>
232
+ Building <a href="<%= joe.url %>/commits/<%= joe.current_build.sha %>"><%= joe.current_build.short_sha %></a> <small>(pid: <%= joe.pid %>)</small>
233
+ <% else %>
234
+ Build starting...
235
+ <% end %>
236
+ </li>
237
+ <% else %>
238
+ <li><form method="POST"><input type="submit" value="Build"/></form></li>
239
+ <% end %>
240
+
241
+ <% if joe.last_build %>
242
+ <li><span class="date"><%= pretty_time(joe.last_build.finished_at) %></span> &raquo; Built <a href="<%= joe.url %>/commits/<%= joe.last_build.sha %>"><%= joe.last_build.short_sha %></a> <span class="<%= joe.last_build.status %>">(<%= joe.last_build.status %>)</span></li>
243
+ <% if joe.last_build.failed? %>
244
+ <li><pre class="terminal"><code><%=bash_color_codes h(joe.last_build.output) %></code></pre></li>
245
+ <% end %>
246
+ <% end %>
247
+ </ul>
248
+ </div>
249
+
250
+ <div class="footer">
251
+ <div class="contact">
252
+ <p>
253
+ <a href="http://github.com/defunkt/cijoe/tree/master#readme">Documentation</a><br/>
254
+ <a href="http://github.com/defunkt/cijoe">Source</a><br/>
255
+ <a href="http://github.com/defunkt/cijoe/issues">Issues</a><br/>
256
+ <a href="http://twitter.com/defunkt">Twitter</a>
257
+ </p>
258
+ </div>
259
+ <div class="contact">
260
+ <p>
261
+ Designed by <a href="http://tom.preston-werner.com/">Tom Preston-Werner</a><br/>
262
+ Influenced by <a href="http://integrityapp.com/">Integrity</a><br/>
263
+ Built with <a href="http://sinatrarb.com/">Sinatra</a><br/>
264
+ Keep it simple, Sam.
265
+ </p>
266
+ </div>
267
+ <div class="rss">
268
+ <a href="http://github.com/defunkt/cijoe">
269
+ <img src="<%= request.path %>/octocat.png" alt="Octocat!" />
270
+ </a>
271
+ </div>
272
+ </div>
273
+ </div>
274
+ </body>
275
+ </html>
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cijoe
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Chris Wanstrath
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-03 00:00:00 -07:00
13
+ default_executable: cijoe
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: choice
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: sinatra
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: open4
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ description: CI Joe is a simple Continuous Integration server.
46
+ email: chris@ozmm.org
47
+ executables:
48
+ - cijoe
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - LICENSE
53
+ - README.markdown
54
+ files:
55
+ - LICENSE
56
+ - README.markdown
57
+ - Rakefile
58
+ - bin/cijoe
59
+ - deps.rip
60
+ - lib/cijoe.rb
61
+ - lib/cijoe/build.rb
62
+ - lib/cijoe/campfire.rb
63
+ - lib/cijoe/commit.rb
64
+ - lib/cijoe/config.rb
65
+ - lib/cijoe/public/octocat.png
66
+ - lib/cijoe/server.rb
67
+ - lib/cijoe/version.rb
68
+ - lib/cijoe/views/template.erb
69
+ has_rdoc: true
70
+ homepage: http://github.com/defunkt/cijoe
71
+ licenses: []
72
+
73
+ post_install_message:
74
+ rdoc_options:
75
+ - --charset=UTF-8
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: "0"
83
+ version:
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ requirements: []
91
+
92
+ rubyforge_project:
93
+ rubygems_version: 1.3.5
94
+ signing_key:
95
+ specification_version: 3
96
+ summary: CI Joe is a simple Continuous Integration server.
97
+ test_files: []
98
+