heidi 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -0
- data/Gemfile.lock +3 -0
- data/README.rdoc +20 -0
- data/VERSION +1 -1
- data/bin/heidi +8 -82
- data/heidi.gemspec +49 -10
- data/lib/heidi.rb +4 -0
- data/lib/heidi/build.rb +42 -10
- data/lib/heidi/builder.rb +7 -2
- data/lib/heidi/hook.rb +2 -0
- data/lib/heidi/integrator.rb +11 -5
- data/lib/heidi/project.rb +37 -24
- data/lib/heidi/shell.rb +119 -0
- data/lib/heidi/tester.rb +6 -5
- data/lib/heidi/web.rb +65 -4
- data/lib/heidi/web/assets/css/bootstrap-responsive.css +643 -0
- data/lib/heidi/web/assets/css/bootstrap.css +3682 -0
- data/lib/heidi/web/{public/css/screen.css → assets/css/colors.css} +1 -75
- data/lib/heidi/web/assets/css/docs.css +772 -0
- data/lib/heidi/web/{public → assets}/images/HeidiBlue-480.png +0 -0
- data/lib/heidi/web/{public → assets}/images/HeidiBlue.gif +0 -0
- data/lib/heidi/web/{public → assets}/images/OrganisedMinds.png +0 -0
- data/lib/heidi/web/{public → assets}/images/heidi.jpeg +0 -0
- data/lib/heidi/web/assets/img/glyphicons-halflings-white.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons-halflings.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_009_magic.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_042_group.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_079_podium.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_082_roundabout.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_155_show_thumbnails.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_163_iphone.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_214_resize_small.png +0 -0
- data/lib/heidi/web/assets/img/glyphicons/glyphicons_266_book_open.png +0 -0
- data/lib/heidi/web/assets/js/README.md +106 -0
- data/lib/heidi/web/assets/js/application.js +180 -0
- data/lib/heidi/web/assets/js/bootstrap-alert.js +94 -0
- data/lib/heidi/web/assets/js/bootstrap-button.js +100 -0
- data/lib/heidi/web/assets/js/bootstrap-carousel.js +157 -0
- data/lib/heidi/web/assets/js/bootstrap-collapse.js +136 -0
- data/lib/heidi/web/assets/js/bootstrap-dropdown.js +92 -0
- data/lib/heidi/web/assets/js/bootstrap-modal.js +210 -0
- data/lib/heidi/web/assets/js/bootstrap-popover.js +95 -0
- data/lib/heidi/web/assets/js/bootstrap-scrollspy.js +125 -0
- data/lib/heidi/web/assets/js/bootstrap-tab.js +130 -0
- data/lib/heidi/web/assets/js/bootstrap-tooltip.js +270 -0
- data/lib/heidi/web/assets/js/bootstrap-transition.js +51 -0
- data/lib/heidi/web/assets/js/bootstrap-typeahead.js +271 -0
- data/lib/heidi/web/assets/js/google-code-prettify/prettify.css +30 -0
- data/lib/heidi/web/assets/js/google-code-prettify/prettify.js +28 -0
- data/lib/heidi/web/assets/js/jquery.js +9252 -0
- data/lib/heidi/web/views/build.erb +40 -24
- data/lib/heidi/web/views/commit.erb +10 -7
- data/lib/heidi/web/views/config.erb +34 -18
- data/lib/heidi/web/views/home.erb +44 -25
- data/lib/heidi/web/views/layout.erb +57 -4
- data/lib/heidi/web/views/new_project.erb +56 -0
- data/lib/heidi/web/views/project.erb +114 -32
- data/lib/heidi/web/views/project_header.erb +20 -0
- data/spec/heidi/build_spec.rb +163 -2
- data/spec/heidi/builder_spec.rb +61 -1
- data/spec/heidi/hook_spec.rb +47 -1
- data/spec/heidi/integrator_spec.rb +96 -1
- data/spec/heidi/project_spec.rb +177 -2
- data/spec/heidi/shell_spec.rb +67 -0
- data/spec/heidi/web_spec.rb +78 -2
- data/spec/heidi_spec.rb +16 -5
- data/spec/spec_helper.rb +1 -1
- data/spec/support/01_rworld.rb +9 -0
- data/spec/support/mock_project.rb +39 -0
- data/spec/support/survivable.rb +10 -0
- metadata +131 -79
- data/spec/heidi/tester_spec.rb +0 -5
data/lib/heidi/hook.rb
CHANGED
@@ -16,9 +16,11 @@ class Heidi
|
|
16
16
|
|
17
17
|
start = Time.now
|
18
18
|
env = {
|
19
|
+
'HEIDI_DIR' => build.project.root,
|
19
20
|
'HEIDI_LOG_DIR' => build.log_root,
|
20
21
|
'HEIDI_BUILD_DIR' => where,
|
21
22
|
'HEIDI_BUILD_COMMIT' => build.commit,
|
23
|
+
'HEIDI_BRANCH' => build.project.branch,
|
22
24
|
|
23
25
|
'RUBYOPT' => nil,
|
24
26
|
'BUNDLE_BIN_PATH' => nil,
|
data/lib/heidi/integrator.rb
CHANGED
@@ -42,14 +42,16 @@ class Heidi
|
|
42
42
|
|
43
43
|
return failure if !run_hooks(:before)
|
44
44
|
|
45
|
+
# have a builder build the build dir
|
45
46
|
builder = Heidi::Builder.new(build)
|
46
47
|
return failure if !builder.build!
|
47
48
|
return failure if !run_hooks(:build)
|
48
49
|
|
49
|
-
tester
|
50
|
+
# have a tester test the build
|
51
|
+
tester = Heidi::Tester.new(self)
|
50
52
|
return failure if !tester.test!
|
51
|
-
return failure if !run_hooks(:tests)
|
52
53
|
|
54
|
+
# and run the success hooks
|
53
55
|
return failure if !run_hooks(:success)
|
54
56
|
|
55
57
|
# create a tarball
|
@@ -58,20 +60,24 @@ class Heidi
|
|
58
60
|
return success
|
59
61
|
|
60
62
|
rescue Exception => e
|
61
|
-
|
62
|
-
|
63
|
+
build.log(:error, e.message)
|
64
|
+
build.log(:error, e.backtrace.join("\n"))
|
63
65
|
|
64
|
-
return
|
66
|
+
return e.message
|
65
67
|
|
66
68
|
ensure
|
67
69
|
run_hooks(:failure) if @failed == true
|
68
70
|
|
71
|
+
run_hooks(:after)
|
72
|
+
|
69
73
|
# always unlock the build root, no matter what
|
70
74
|
build.unlock
|
71
75
|
|
72
76
|
end
|
73
77
|
|
74
78
|
def run_hooks(where)
|
79
|
+
build.load_hooks if build.hooks.nil? || build.hooks.empty?
|
80
|
+
|
75
81
|
return true if @hooks_ran.include?(where)
|
76
82
|
return true if build.hooks[where].nil? || build.hooks[where].empty?
|
77
83
|
|
data/lib/heidi/project.rb
CHANGED
@@ -34,16 +34,17 @@ class Heidi
|
|
34
34
|
end.first
|
35
35
|
end
|
36
36
|
|
37
|
-
return @builds
|
37
|
+
return @builds.sort_by(&:time)
|
38
38
|
end
|
39
39
|
|
40
40
|
def name=(name)
|
41
|
-
@name = name
|
42
41
|
@git["name"] = name
|
43
42
|
end
|
44
43
|
|
45
44
|
def name
|
46
|
-
|
45
|
+
name ||= @git[:name] || basename
|
46
|
+
name = basename if name.empty?
|
47
|
+
name
|
47
48
|
end
|
48
49
|
|
49
50
|
def basename
|
@@ -51,8 +52,9 @@ class Heidi
|
|
51
52
|
end
|
52
53
|
|
53
54
|
def commit
|
54
|
-
@git.commit[0..
|
55
|
+
@git.commit[0..7]
|
55
56
|
end
|
57
|
+
alias_method :HEAD, :commit
|
56
58
|
|
57
59
|
def author(commit=self.commit)
|
58
60
|
@git.log(1, "%cN <%cE>", commit)
|
@@ -62,6 +64,10 @@ class Heidi
|
|
62
64
|
@git.log(1, "%ci", commit)
|
63
65
|
end
|
64
66
|
|
67
|
+
def message(commit=self.commit)
|
68
|
+
@git.log(1, "%B", commit)
|
69
|
+
end
|
70
|
+
|
65
71
|
def last_commit
|
66
72
|
@git["commit"]
|
67
73
|
end
|
@@ -84,18 +90,18 @@ class Heidi
|
|
84
90
|
end
|
85
91
|
|
86
92
|
def build_status
|
87
|
-
@git["build.status"]
|
93
|
+
@git["build.status"] || "unknown"
|
88
94
|
end
|
89
95
|
def build_status=(status)
|
90
96
|
@git["build.status"] = status
|
91
97
|
end
|
92
98
|
|
93
|
-
def
|
99
|
+
def branch
|
94
100
|
name = @git["build.branch"]
|
95
101
|
name == "" ? nil : name
|
96
102
|
end
|
97
|
-
def
|
98
|
-
name
|
103
|
+
def branch=(name)
|
104
|
+
name.gsub!("origin/", "")
|
99
105
|
@git["build.branch"] = name
|
100
106
|
end
|
101
107
|
|
@@ -104,10 +110,13 @@ class Heidi
|
|
104
110
|
end
|
105
111
|
|
106
112
|
def integrate(forced=false)
|
107
|
-
|
113
|
+
must_build = !(self.current_build == self.commit)
|
114
|
+
must_build = build_status != "passed"
|
115
|
+
|
116
|
+
return true if !forced && !must_build
|
108
117
|
return "locked" if locked?
|
109
118
|
|
110
|
-
status =
|
119
|
+
status = Heidi::DNF
|
111
120
|
|
112
121
|
self.lock do
|
113
122
|
record_current_build
|
@@ -117,7 +126,7 @@ class Heidi
|
|
117
126
|
elsif res.is_a? String
|
118
127
|
status = res
|
119
128
|
else
|
120
|
-
status =
|
129
|
+
status = Heidi::FAILED
|
121
130
|
end
|
122
131
|
end
|
123
132
|
|
@@ -125,22 +134,26 @@ class Heidi
|
|
125
134
|
end
|
126
135
|
|
127
136
|
def fetch
|
128
|
-
if
|
129
|
-
if @git.branches.include? integration_branch
|
130
|
-
@git.switch(integration_branch)
|
131
|
-
@git.merge "origin/#{integration_branch}"
|
137
|
+
return if locked?
|
132
138
|
|
133
|
-
|
134
|
-
|
139
|
+
self.lock do
|
140
|
+
if branch && @git.branch != branch
|
141
|
+
if @git.branches.include? branch
|
142
|
+
@git.switch(branch)
|
143
|
+
@git.merge "origin/#{branch}"
|
144
|
+
|
145
|
+
else
|
146
|
+
@git.checkout(branch, "origin/#{branch}")
|
135
147
|
|
148
|
+
end
|
136
149
|
end
|
137
|
-
end
|
138
150
|
|
139
|
-
|
151
|
+
@git.pull
|
140
152
|
|
141
|
-
|
142
|
-
|
143
|
-
|
153
|
+
# when the head has changed, update some stuff
|
154
|
+
if last_commit != self.commit
|
155
|
+
record_last_commit
|
156
|
+
end
|
144
157
|
end
|
145
158
|
end
|
146
159
|
|
@@ -155,8 +168,8 @@ class Heidi
|
|
155
168
|
end
|
156
169
|
end
|
157
170
|
|
158
|
-
def log
|
159
|
-
log = @git.graph(
|
171
|
+
def log(length=60)
|
172
|
+
log = @git.graph(length)
|
160
173
|
|
161
174
|
lines = []
|
162
175
|
log.out.lines.each do |line|
|
data/lib/heidi/shell.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
require 'heidi'
|
2
|
+
require 'simple_shell'
|
3
|
+
|
4
|
+
class Heidi
|
5
|
+
module Shell
|
6
|
+
def shell
|
7
|
+
@shell ||= SimpleShell.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def check_heidi_root()
|
11
|
+
if !File.exists?("./projects") && File.directory?("./projects")
|
12
|
+
$stderr.puts "You're not inside Heidi"
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def noisy
|
18
|
+
@noisy=true
|
19
|
+
end
|
20
|
+
|
21
|
+
def silent
|
22
|
+
@noisy=false
|
23
|
+
end
|
24
|
+
|
25
|
+
def shout(msg)
|
26
|
+
@noisy ||= false
|
27
|
+
return if @noisy == false
|
28
|
+
puts msg
|
29
|
+
end
|
30
|
+
|
31
|
+
def new_heidi_root(name)
|
32
|
+
shout "creating #{name}/"
|
33
|
+
shout "creating #{name}/projects"
|
34
|
+
shell.mkdir %W(-p #{name}/projects)
|
35
|
+
shell.mkdir %W(-p #{name}/bin)
|
36
|
+
shout "creating #{name}/Gemfile"
|
37
|
+
File.open("#{name}/Gemfile", File::CREAT|File::WRONLY) do |f|
|
38
|
+
f.puts 'source "http://rubygems.org"'
|
39
|
+
f.puts 'gem "heidi"'
|
40
|
+
end
|
41
|
+
|
42
|
+
shout "\nIf you like you can run: bundle install --binstubs"
|
43
|
+
shout "this will tie heidi and heidi_web to this location"
|
44
|
+
shout "\nFor even more tying down, run: bundle install --deployment"
|
45
|
+
shout "after running bundle install --binstubs"
|
46
|
+
end
|
47
|
+
|
48
|
+
def new_project(name, repo, branch="master")
|
49
|
+
if File.exists? "projects/#{name}"
|
50
|
+
$stderr.puts "projects/#{name} is in the way. Please remove it"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
# create a logs dir
|
55
|
+
shout "creating projects/#{name}"
|
56
|
+
shout "creating projects/#{name}/logs"
|
57
|
+
shell.mkdir %W(-p projects/#{name}/logs)
|
58
|
+
|
59
|
+
%w(build tests failure success before).each do |hook|
|
60
|
+
shout "creating projects/#{name}/hooks/#{hook}"
|
61
|
+
shell.mkdir %W(-p projects/#{name}/hooks/#{hook})
|
62
|
+
end
|
63
|
+
|
64
|
+
# make a clone
|
65
|
+
shell.in("projects/#{name}") do |sh|
|
66
|
+
shout "filling #{name} cache"
|
67
|
+
|
68
|
+
shout "git clone #{repo}"
|
69
|
+
sh.git %W(clone #{repo} cached)
|
70
|
+
|
71
|
+
sh.in("cached") do |cached|
|
72
|
+
shout "setting the name of the project to: #{name}"
|
73
|
+
cached.git %W(config heidi.name #{name})
|
74
|
+
shout "setting the branch to: #{branch}"
|
75
|
+
cached.git %W(config heidi.build.branch #{branch})
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
shout "Creating default test hook: projects/#{name}/hooks/tests/01_rspec"
|
80
|
+
File.open("projects/#{name}/hooks/tests/01_rspec", File::CREAT|File::WRONLY) do |f|
|
81
|
+
f.puts %q(#!/bin/sh
|
82
|
+
|
83
|
+
# edit this file to your needs
|
84
|
+
bundle exec rake spec
|
85
|
+
)
|
86
|
+
end
|
87
|
+
shell.chmod %W(+x projects/#{name}/hooks/tests/01_rspec)
|
88
|
+
shout "\n"
|
89
|
+
shout "Now edit or add some hooks and run: heidi integrate #{name}"
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
def remove_project(name)
|
94
|
+
# remove build and cache dir, expose logs directly
|
95
|
+
shout "removing build dir"
|
96
|
+
shell.rm %W(-r projects/#{name}/build)
|
97
|
+
shout "removing cache (preserving project config)"
|
98
|
+
shell.cp %W(-pr projects/#{name}/cached/.git/config projects/#{name})
|
99
|
+
shell.rm %W(-r projects/#{name}/cached)
|
100
|
+
shout "exposing builds"
|
101
|
+
shell.mv %W(projects/#{name}/logs/* projects/#{name}/)
|
102
|
+
shell.rm %W(-r projects/#{name}/logs)
|
103
|
+
end
|
104
|
+
|
105
|
+
def integrate(name)
|
106
|
+
heidi = Heidi.new
|
107
|
+
heidi.projects.each do |project|
|
108
|
+
next if !name.nil? && project.name != name
|
109
|
+
|
110
|
+
project.fetch
|
111
|
+
msg = project.integrate(!name.nil?)
|
112
|
+
unless msg.nil? || msg == true
|
113
|
+
$stderr.puts "#{project.name}: #{msg}"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
end
|
119
|
+
end
|
data/lib/heidi/tester.rb
CHANGED
@@ -2,10 +2,11 @@ class Heidi
|
|
2
2
|
class Tester
|
3
3
|
attr_reader :build, :project, :message
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@
|
5
|
+
def initialize(integrator)
|
6
|
+
@integrator = integrator
|
7
|
+
@build = integrator.build
|
8
|
+
@project = @build.project
|
9
|
+
@message = ""
|
9
10
|
end
|
10
11
|
|
11
12
|
def test!
|
@@ -17,7 +18,7 @@ class Heidi
|
|
17
18
|
return false
|
18
19
|
end
|
19
20
|
|
20
|
-
return
|
21
|
+
return @integrator.run_hooks(:tests)
|
21
22
|
end
|
22
23
|
|
23
24
|
end
|
data/lib/heidi/web.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'sinatra/base'
|
2
2
|
require 'heidi'
|
3
|
+
require 'heidi/shell'
|
3
4
|
require 'simple_shell'
|
4
5
|
|
5
6
|
class Heidi
|
@@ -16,6 +17,7 @@ class Heidi
|
|
16
17
|
|
17
18
|
before {
|
18
19
|
@heidi = Heidi.new(self.class.project_path)
|
20
|
+
@crumbs = []
|
19
21
|
}
|
20
22
|
|
21
23
|
dir = File.dirname(File.expand_path(__FILE__))
|
@@ -23,15 +25,33 @@ class Heidi
|
|
23
25
|
set :sessions, true
|
24
26
|
|
25
27
|
set :views, "#{dir}/web/views"
|
26
|
-
set :public_folder, "#{dir}/web/
|
28
|
+
set :public_folder, "#{dir}/web/assets"
|
27
29
|
set :root, dir
|
28
30
|
|
29
31
|
get '/' do
|
30
|
-
|
32
|
+
@crumbs = [ { 'home' => ''} ]
|
33
|
+
erb(:home, { :locals => { :projects => @heidi.projects }})
|
31
34
|
end
|
32
35
|
|
33
36
|
get '/projects/' do
|
34
|
-
|
37
|
+
@crumbs = [ { 'home' => '/'}, { 'new project' => '' } ]
|
38
|
+
erb(:new_project)
|
39
|
+
end
|
40
|
+
|
41
|
+
post '/projects/' do
|
42
|
+
basename = params[:name].downcase.gsub(/\W/,'_')
|
43
|
+
|
44
|
+
Thread.new(basename) do |name|
|
45
|
+
worker = Class.new
|
46
|
+
worker.extend(Heidi::Shell)
|
47
|
+
worker.silent
|
48
|
+
|
49
|
+
worker.new_project(name, params[:origin], params[:branch])
|
50
|
+
end
|
51
|
+
|
52
|
+
sleep 1
|
53
|
+
|
54
|
+
redirect "/projects/#{basename}", 302
|
35
55
|
end
|
36
56
|
|
37
57
|
get '/projects/:name' do
|
@@ -43,6 +63,19 @@ class Heidi
|
|
43
63
|
erb(:project, { :locals => { :project => project }})
|
44
64
|
end
|
45
65
|
|
66
|
+
get '/projects/:name/fetch' do
|
67
|
+
project = @heidi[params[:name]]
|
68
|
+
if project.nil?
|
69
|
+
return "no project by that name: #{params[:name]}"
|
70
|
+
end
|
71
|
+
|
72
|
+
Thread.new { project.fetch && project.integrate() }
|
73
|
+
|
74
|
+
sleep 1
|
75
|
+
|
76
|
+
redirect "/projects/#{project.basename}", 302
|
77
|
+
end
|
78
|
+
|
46
79
|
get '/projects/:name/build/:commit' do
|
47
80
|
project = @heidi[params[:name]]
|
48
81
|
if project.nil?
|
@@ -117,7 +150,7 @@ class Heidi
|
|
117
150
|
end
|
118
151
|
|
119
152
|
project.name = params[:name]
|
120
|
-
project.
|
153
|
+
project.branch = params[:branch]
|
121
154
|
|
122
155
|
|
123
156
|
redirect "/projects/#{project.basename}", 302
|
@@ -137,6 +170,34 @@ class Heidi
|
|
137
170
|
"<span class=\"#{classes.join(" ")}\">"
|
138
171
|
end
|
139
172
|
end
|
173
|
+
|
174
|
+
def breadcrumbs
|
175
|
+
""
|
176
|
+
end
|
177
|
+
|
178
|
+
def status2alert(status)
|
179
|
+
case status
|
180
|
+
when "passed"
|
181
|
+
"alert-success"
|
182
|
+
when "failed"
|
183
|
+
"alert-error"
|
184
|
+
else
|
185
|
+
""
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def status2label(status)
|
190
|
+
case status
|
191
|
+
when "passed"
|
192
|
+
"label-success"
|
193
|
+
when "failed"
|
194
|
+
"label-important"
|
195
|
+
else
|
196
|
+
"label-info"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
|
140
201
|
end
|
141
202
|
end
|
142
203
|
end
|