heidi 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +3 -0
  3. data/README.rdoc +20 -0
  4. data/VERSION +1 -1
  5. data/bin/heidi +8 -82
  6. data/heidi.gemspec +49 -10
  7. data/lib/heidi.rb +4 -0
  8. data/lib/heidi/build.rb +42 -10
  9. data/lib/heidi/builder.rb +7 -2
  10. data/lib/heidi/hook.rb +2 -0
  11. data/lib/heidi/integrator.rb +11 -5
  12. data/lib/heidi/project.rb +37 -24
  13. data/lib/heidi/shell.rb +119 -0
  14. data/lib/heidi/tester.rb +6 -5
  15. data/lib/heidi/web.rb +65 -4
  16. data/lib/heidi/web/assets/css/bootstrap-responsive.css +643 -0
  17. data/lib/heidi/web/assets/css/bootstrap.css +3682 -0
  18. data/lib/heidi/web/{public/css/screen.css → assets/css/colors.css} +1 -75
  19. data/lib/heidi/web/assets/css/docs.css +772 -0
  20. data/lib/heidi/web/{public → assets}/images/HeidiBlue-480.png +0 -0
  21. data/lib/heidi/web/{public → assets}/images/HeidiBlue.gif +0 -0
  22. data/lib/heidi/web/{public → assets}/images/OrganisedMinds.png +0 -0
  23. data/lib/heidi/web/{public → assets}/images/heidi.jpeg +0 -0
  24. data/lib/heidi/web/assets/img/glyphicons-halflings-white.png +0 -0
  25. data/lib/heidi/web/assets/img/glyphicons-halflings.png +0 -0
  26. data/lib/heidi/web/assets/img/glyphicons/glyphicons_009_magic.png +0 -0
  27. data/lib/heidi/web/assets/img/glyphicons/glyphicons_042_group.png +0 -0
  28. data/lib/heidi/web/assets/img/glyphicons/glyphicons_079_podium.png +0 -0
  29. data/lib/heidi/web/assets/img/glyphicons/glyphicons_082_roundabout.png +0 -0
  30. data/lib/heidi/web/assets/img/glyphicons/glyphicons_155_show_thumbnails.png +0 -0
  31. data/lib/heidi/web/assets/img/glyphicons/glyphicons_163_iphone.png +0 -0
  32. data/lib/heidi/web/assets/img/glyphicons/glyphicons_214_resize_small.png +0 -0
  33. data/lib/heidi/web/assets/img/glyphicons/glyphicons_266_book_open.png +0 -0
  34. data/lib/heidi/web/assets/js/README.md +106 -0
  35. data/lib/heidi/web/assets/js/application.js +180 -0
  36. data/lib/heidi/web/assets/js/bootstrap-alert.js +94 -0
  37. data/lib/heidi/web/assets/js/bootstrap-button.js +100 -0
  38. data/lib/heidi/web/assets/js/bootstrap-carousel.js +157 -0
  39. data/lib/heidi/web/assets/js/bootstrap-collapse.js +136 -0
  40. data/lib/heidi/web/assets/js/bootstrap-dropdown.js +92 -0
  41. data/lib/heidi/web/assets/js/bootstrap-modal.js +210 -0
  42. data/lib/heidi/web/assets/js/bootstrap-popover.js +95 -0
  43. data/lib/heidi/web/assets/js/bootstrap-scrollspy.js +125 -0
  44. data/lib/heidi/web/assets/js/bootstrap-tab.js +130 -0
  45. data/lib/heidi/web/assets/js/bootstrap-tooltip.js +270 -0
  46. data/lib/heidi/web/assets/js/bootstrap-transition.js +51 -0
  47. data/lib/heidi/web/assets/js/bootstrap-typeahead.js +271 -0
  48. data/lib/heidi/web/assets/js/google-code-prettify/prettify.css +30 -0
  49. data/lib/heidi/web/assets/js/google-code-prettify/prettify.js +28 -0
  50. data/lib/heidi/web/assets/js/jquery.js +9252 -0
  51. data/lib/heidi/web/views/build.erb +40 -24
  52. data/lib/heidi/web/views/commit.erb +10 -7
  53. data/lib/heidi/web/views/config.erb +34 -18
  54. data/lib/heidi/web/views/home.erb +44 -25
  55. data/lib/heidi/web/views/layout.erb +57 -4
  56. data/lib/heidi/web/views/new_project.erb +56 -0
  57. data/lib/heidi/web/views/project.erb +114 -32
  58. data/lib/heidi/web/views/project_header.erb +20 -0
  59. data/spec/heidi/build_spec.rb +163 -2
  60. data/spec/heidi/builder_spec.rb +61 -1
  61. data/spec/heidi/hook_spec.rb +47 -1
  62. data/spec/heidi/integrator_spec.rb +96 -1
  63. data/spec/heidi/project_spec.rb +177 -2
  64. data/spec/heidi/shell_spec.rb +67 -0
  65. data/spec/heidi/web_spec.rb +78 -2
  66. data/spec/heidi_spec.rb +16 -5
  67. data/spec/spec_helper.rb +1 -1
  68. data/spec/support/01_rworld.rb +9 -0
  69. data/spec/support/mock_project.rb +39 -0
  70. data/spec/support/survivable.rb +10 -0
  71. metadata +131 -79
  72. data/spec/heidi/tester_spec.rb +0 -5
@@ -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,
@@ -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 = Heidi::Tester.new(build)
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
- $stderr.puts e.message
62
- $stderr.puts e.backtrace.join("\n")
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
 
@@ -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
- @name ||= @git[:name]
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..8]
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 integration_branch
99
+ def branch
94
100
  name = @git["build.branch"]
95
101
  name == "" ? nil : name
96
102
  end
97
- def integration_branch=(name)
98
- name = name.split('/').last if 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
- return true if !forced && self.current_build == self.commit
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 = "unknown"
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 = "failed"
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 integration_branch && @git.branch != integration_branch
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
- else
134
- @git.checkout(integration_branch, "origin/#{integration_branch}")
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
- @git.pull
151
+ @git.pull
140
152
 
141
- # when the head has changed, update some stuff
142
- if last_commit != self.commit
143
- record_last_commit
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(120)
171
+ def log(length=60)
172
+ log = @git.graph(length)
160
173
 
161
174
  lines = []
162
175
  log.out.lines.each do |line|
@@ -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
@@ -2,10 +2,11 @@ class Heidi
2
2
  class Tester
3
3
  attr_reader :build, :project, :message
4
4
 
5
- def initialize(build)
6
- @build = build
7
- @project = build.project
8
- @message = ""
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 true
21
+ return @integrator.run_hooks(:tests)
21
22
  end
22
23
 
23
24
  end
@@ -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/public"
28
+ set :public_folder, "#{dir}/web/assets"
27
29
  set :root, dir
28
30
 
29
31
  get '/' do
30
- redirect '/projects/', 302
32
+ @crumbs = [ { 'home' => ''} ]
33
+ erb(:home, { :locals => { :projects => @heidi.projects }})
31
34
  end
32
35
 
33
36
  get '/projects/' do
34
- erb(:home, { :locals => { :projects => @heidi.projects }})
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.integration_branch = params[:branch]
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