heidi 0.0.1

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/lib/heidi/git.rb ADDED
@@ -0,0 +1,113 @@
1
+ require 'fileutils'
2
+ require 'simple_shell'
3
+ require 'strscan'
4
+
5
+ class Heidi
6
+
7
+ # A very simple interface to git base on SimpleShell, no library ties and no
8
+ # fancy stuff
9
+ #
10
+ class Git
11
+ VERBOSE=false
12
+
13
+ def initialize(path=Dir.pwd, verbose=VERBOSE)
14
+ @path = path
15
+ SimpleShell.noisy = verbose
16
+ @shell = SimpleShell.new(@path)
17
+ end
18
+
19
+ # get the latest commit hash
20
+ def commit
21
+ res = @shell.git "log", "-n", "1", "--pretty=%H"
22
+ res.out
23
+ end
24
+ alias_method :HEAD, :commit
25
+ alias_method :head, :commit
26
+
27
+ # find the current branch (the one with the *)
28
+ def branch
29
+ res = @shell.git "branch", "--no-color"
30
+ active = res.out.scan(/\* \w+/).first
31
+ active.scan(/\w+$/).first
32
+ end
33
+
34
+ # git branch
35
+ def branches
36
+ res = @shell.git "branch", "--no-color"
37
+ res.out.split("\n").collect{ |b| b.gsub(/^[\s*]+/, '') }
38
+ end
39
+
40
+ # git checkout $name
41
+ def switch(name)
42
+ return nil unless branches.include?(name)
43
+ @shell.git "checkout", name
44
+ end
45
+
46
+ # git checkout -b $name [$base]
47
+ def checkout(name, base=nil)
48
+ command = [ "git", "checkout", "-b", name ]
49
+ command << base unless base.nil?
50
+ @shell.system(*command)
51
+ end
52
+
53
+ # git merge $base
54
+ def merge(base)
55
+ @shell.git %W(merge #{base})
56
+ end
57
+
58
+ # git fetch $where='origin'
59
+ def fetch(where="origin")
60
+ @shell.git %W(fetch #{where})
61
+ end
62
+
63
+ # git pull $where='origin' [$what]
64
+ def pull(where="origin", what=nil)
65
+ command = [ "git", "pull", where ]
66
+ command << what if !what.nil?
67
+
68
+ @shell.system(*command)
69
+ end
70
+
71
+ # git push $where [$what]
72
+ #
73
+ # $what may be '--tags'
74
+ #
75
+ def push(where="origin", what=nil)
76
+ command = [ "git", "push", where ]
77
+ if !what.nil?
78
+ if what == "--tags"
79
+ command.insert(2, what)
80
+ else
81
+ command << what
82
+ end
83
+ end
84
+
85
+ @shell.system(*command)
86
+ end
87
+
88
+ # git tags
89
+ def tags
90
+ res = @shell.system("git", "tag")
91
+ res.out.split("\n")
92
+ end
93
+
94
+ # git tag -a -m $message $name
95
+ def tag(name, message)
96
+ command = [ "git", "tag", "-a", "-m", message, name ]
97
+ if tags.include?(name)
98
+ command.insert(4, "-f")
99
+ end
100
+ @shell.system(*command)
101
+ end
102
+
103
+ # git config $key $value
104
+ def []=(key, value)
105
+ @shell.system("git", "config", "heidi.#{key}", value)
106
+ end
107
+
108
+ # git config $key
109
+ def [](key)
110
+ @shell.system("git", "config", "heidi.#{key}").out
111
+ end
112
+ end
113
+ end
data/lib/heidi/hook.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'simple_shell'
2
+
3
+ class Heidi
4
+ class Hook
5
+ def initialize(project, script)
6
+ @project = project
7
+ @script = script
8
+ end
9
+
10
+ def perform(where)
11
+ shell = SimpleShell.new(where)
12
+ res = shell.do @script
13
+ return res
14
+ end
15
+
16
+ def name
17
+ File.basename(@script)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,84 @@
1
+ require 'heidi/build'
2
+ require 'heidi/builder'
3
+ require 'heidi/tester'
4
+ require 'time'
5
+
6
+ class Heidi
7
+ class Integrator
8
+ attr_reader :build, :project
9
+
10
+ def initialize(project)
11
+ @project = project
12
+ @build = Heidi::Build.new(project)
13
+ @failed = false
14
+ end
15
+
16
+ def failure
17
+ @failed = true
18
+ build.record(:failure)
19
+ end
20
+
21
+ def integrate
22
+ start = Time.now
23
+ build.lock
24
+ build.load_hooks
25
+ build.clean
26
+
27
+ return failure if !run_hooks(:before)
28
+
29
+ builder = Heidi::Builder.new(build)
30
+ tester = Heidi::Tester.new(build)
31
+
32
+ return failure if !builder.build!
33
+ return failure if !tester.test!
34
+ return failure if !run_hooks(:after)
35
+
36
+ # record the new succesful
37
+ build.record(:success)
38
+
39
+ # create a tarball
40
+ build.create_tar_ball
41
+
42
+ build.log :info, ("Integration took: %.2fs" % (Time.now - start))
43
+ return true
44
+
45
+ rescue Exception => e
46
+ $stderr.puts e.message
47
+ $stderr.puts e.backtrace.join("\n")
48
+
49
+ return $!
50
+
51
+ ensure
52
+ run_hooks(:failed) if @failed == true
53
+
54
+ # always unlock the build root, no matter what
55
+ build.unlock
56
+
57
+ return @failed == true ? false : true
58
+
59
+ end
60
+
61
+ def run_hooks(where)
62
+ return true if build.hooks[where].nil? || build.hooks[where].empty?
63
+
64
+ hooks_failed = false
65
+ build.hooks[where].each do |hook|
66
+ res = hook.perform(build.build_root)
67
+
68
+ if res.S?.to_i != 0
69
+ build.log :error, "--- #{where} hook: #{hook.name} failed ---"
70
+ build.log :error, res.err
71
+
72
+ hooks_failed = true
73
+ break
74
+
75
+ else
76
+ build.log :info, res.out
77
+ end
78
+ end
79
+
80
+ return hooks_failed == true ? false : true
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,113 @@
1
+ require 'heidi/git'
2
+ require 'heidi/integrator'
3
+ require 'heidi/hook'
4
+ require 'time'
5
+
6
+ class Heidi
7
+ class Project
8
+ attr_reader :root, :cached_root, :lock_file, :builds
9
+
10
+ def initialize(root)
11
+ @root = root
12
+ @lock_file = File.join(root, ".lock")
13
+ @cached_root = File.join(root, "cached")
14
+ @git = Heidi::Git.new(@cached_root)
15
+ load_builds
16
+ end
17
+
18
+ def load_builds
19
+ @builds = []
20
+ Dir[File.join(root, "logs", "*")].each do |build|
21
+ next unless File.directory? build
22
+ @builds << Heidi::Build.new(self, File.basename(build))
23
+ end
24
+ end
25
+
26
+ def name=(name)
27
+ @name = name
28
+ @git["name"] = name
29
+ end
30
+
31
+ def name
32
+ @name ||= @git[:name]
33
+ end
34
+
35
+ def commit
36
+ @git.commit[0..8]
37
+ end
38
+
39
+ def last_commit
40
+ @git["commit"]
41
+ end
42
+ def record_last_commit
43
+ @git["commit"] = self.commit
44
+ end
45
+
46
+ def latest_build
47
+ @git["build.latest"]
48
+ end
49
+ def record_latest_build
50
+ @git["build.latest"] = self.commit
51
+ end
52
+
53
+ def build_status
54
+ @git["build.status"]
55
+ end
56
+ def build_status=(status)
57
+ @git["build.status"] = status
58
+ end
59
+
60
+ def integration_branch
61
+ name = @git["build.branch"]
62
+ name == "" ? nil : name
63
+ end
64
+
65
+ def integrate
66
+ return "locked" if locked?
67
+
68
+ status = ""
69
+ self.lock do
70
+ res = Heidi::Integrator.new(self).integrate
71
+ status = res != true ? "failed" : "passed"
72
+ end
73
+
74
+ return status
75
+ end
76
+
77
+ def fetch
78
+ @git.fetch
79
+ if integration_branch
80
+ if @git.branches.include? integration_branch
81
+ @git.switch(integration_branch)
82
+ @git.merge "origin/#{integration_branch}"
83
+
84
+ else
85
+ @git.checkout(integration_branch, "origin/#{integration_branch}")
86
+
87
+ end
88
+ end
89
+
90
+ record_last_commit
91
+ end
92
+
93
+ def lock(&block)
94
+ File.open(lock_file, File::TRUNC|File::CREAT|File::WRONLY) do |f|
95
+ f.puts Time.now.strftime("%c")
96
+ end
97
+
98
+ if block_given?
99
+ yield
100
+ self.unlock
101
+ end
102
+ end
103
+
104
+ def unlock
105
+ File.unlink lock_file
106
+ end
107
+
108
+ def locked?
109
+ File.exists? lock_file
110
+ end
111
+
112
+ end
113
+ end
@@ -0,0 +1,52 @@
1
+ class Heidi
2
+ class Tester
3
+ attr_reader :build, :project, :message
4
+
5
+ def initialize(build)
6
+ @build = build
7
+ @project = build.project
8
+ @message = ""
9
+ end
10
+
11
+ def test!
12
+ build.log(:info, "Starting tests")
13
+
14
+ tests_failed = false
15
+
16
+ if build.hooks[:tests].empty?
17
+ build.log(:error, "There are no test hooks")
18
+ @message = "There are no test hooks"
19
+ return false
20
+ end
21
+
22
+ build.hooks[:tests].each do |hook|
23
+ res = hook.perform(build.build_root)
24
+
25
+ if res.S?.to_i != 0
26
+ log "--- test #{hook.name} failed ---"
27
+ log res.err
28
+
29
+ @message = "tests failed"
30
+ tests_failed = true
31
+ break
32
+
33
+ else
34
+ log res.out
35
+ end
36
+ end
37
+
38
+ return tests_failed ? false : true
39
+ end
40
+
41
+ def log(string)
42
+ File.open(
43
+ File.join(build.root, "test.log"),
44
+ File::CREAT|File::WRONLY|File::APPEND
45
+ ) do |f|
46
+ f.puts string
47
+ end
48
+ end
49
+
50
+
51
+ end
52
+ end
@@ -0,0 +1,9 @@
1
+ class Heidi; module Web; module Routes
2
+
3
+ module Home
4
+ get '/' do
5
+ '<h1>Welcome to Heidi!</h1>Psst. Want to see my <a href="/projects">projects</a>?'
6
+ end
7
+ end
8
+
9
+ end; end; end
@@ -0,0 +1,61 @@
1
+ class Heidi; module Web; module Routes
2
+
3
+ module Projects
4
+ get '/projects' do
5
+ output = ""
6
+ $heidi.projects.each do |project|
7
+ output += "<a href='/projects/#{project.name}'>#{project.name}</a><br />"
8
+ end
9
+
10
+ output
11
+ end
12
+
13
+ get '/projects/:name' do
14
+ project = $heidi[params[:name]]
15
+ if project.nil?
16
+ return "no project by that name: #{params[:name]}"
17
+ end
18
+
19
+ output = "<h1>#{project.name}</h1>"
20
+ output += "Build status: #{project.build_status}"
21
+ output += "<br /><br /><h2>Build history</h2>"
22
+ project.builds.each do |build|
23
+ output += %Q{<a href="/projects/#{project.name}/build/#{build.commit}">#{build.commit}</a> - #{build.status}<br />}
24
+ end
25
+
26
+ output
27
+ end
28
+
29
+ get '/projects/:name/build/:commit' do
30
+ project = $heidi[params[:name]]
31
+ if project.nil?
32
+ return "no project by that name: #{params[:name]}"
33
+ end
34
+
35
+ # load build of project
36
+ build = Heidi::Build.new(project, params[:commit])
37
+ output = "<h1>#{project.name}</h1>"
38
+ output += "<h2>Build: #{build.commit} - #{build.status}<h2>"
39
+
40
+ %w(heidi.info heidi.errors build.log test.log).each do |log_file|
41
+ log = build.logs(log_file)
42
+ if (!log.nil? and !log.empty?)
43
+ output += "<h3>#{log_file}</h3>"
44
+ output += "<pre>#{log}</pre>"
45
+ end
46
+ end
47
+
48
+ output
49
+ end
50
+
51
+ put '/projects/:name/build' do
52
+ project = $heidi[params[:name]]
53
+ if project.nil?
54
+ return "no project by that name: #{params[:name]}"
55
+ end
56
+
57
+ project.integrate
58
+ end
59
+ end
60
+
61
+ end; end; end
@@ -0,0 +1,13 @@
1
+ require 'heidi/web/routes/projects'
2
+ require 'heidi/web/routes/home'
3
+
4
+ class Heidi
5
+ module Web
6
+ module Routes
7
+ def self.included(base)
8
+ base.extend Routes::Home
9
+ base.extend Routes::Projects
10
+ end
11
+ end
12
+ end
13
+ end
data/lib/heidi/web.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'sinatra'
2
+ require 'heidi/web/routes'
3
+
4
+ class Heidi
5
+ module Web
6
+ SimpleShell.noisy = true
7
+ end
8
+ end
data/lib/heidi.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'fileutils'
2
+ require 'simple_shell'
3
+
4
+ require 'heidi/project'
5
+
6
+ class Heidi
7
+ attr_reader :projects
8
+
9
+ def initialize(root=Dir.pwd)
10
+ @root = root
11
+ @projects = []
12
+ Dir[File.join(root,"projects", "*")].each do |project|
13
+ next unless File.directory?(project)
14
+
15
+ @projects << Heidi::Project.new(project)
16
+ end
17
+ end
18
+
19
+ def [](name)
20
+ name = "#{name}"
21
+ @projects.select do |project|
22
+ project.name == name || File.basename(project.root) == name
23
+ end.first
24
+ end
25
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Build do
4
+ pending "more tests please!"
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Builder do
4
+ pending "more tests please!"
5
+ end
@@ -0,0 +1,36 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Git do
4
+ let(:git) { Heidi::Git.new() }
5
+
6
+ it "should can find the HEAD" do
7
+ git.commit.should_not be_empty
8
+ git.commit.should == git.HEAD
9
+ end
10
+
11
+ it "should list all branches" do
12
+ git.branches.should be_kind_of(Array)
13
+ end
14
+
15
+ it "should list the current branch" do
16
+ git.branch.should_not be_empty
17
+ git.branch.should_not =~ /^\*/
18
+ end
19
+
20
+ it "should list the tags" do
21
+ git.tags.should be_kind_of(Array)
22
+ end
23
+
24
+ describe "config" do
25
+ let(:random) { "#{rand(1000)}.#{rand(1000)}" }
26
+
27
+ it "should write configs" do
28
+ git["test.entry"].should_not == random
29
+ git["test.entry"] = random
30
+ end
31
+
32
+ it "should read configs" do
33
+ git["test.entry"].should_not == be_empty
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Hook do
4
+ pending "more tests please!"
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Integrator do
4
+ pending "more tests please!"
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Project do
4
+ pending "more tests please!"
5
+ end
@@ -0,0 +1,5 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi::Tester do
4
+ pending "more tests please!"
5
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require 'heidi/web'
3
+
4
+ describe Heidi::Web do
5
+ pending "more tests please!"
6
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe Heidi do
4
+ before(:all) do
5
+ shell = SimpleShell.new("/")
6
+ shell.mkdir("-p", "/tmp/heidi_spec/projects/one")
7
+ end
8
+
9
+ it "should gather projects on load" do
10
+ h = Heidi.new("/tmp/heidi_spec")
11
+ h.projects.should be_kind_of(Array)
12
+ h.projects.count.should == 1
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'heidi'
5
+
6
+ # Requires supporting files with custom matchers and macros, etc,
7
+ # in ./support/ and its subdirectories.
8
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
9
+
10
+ RSpec.configure do |config|
11
+
12
+ end