cijoe 0.0.1

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