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.
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