heidi 0.3.1 → 0.4.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/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
|