imbriaco-integrity 0.1.9.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +12 -0
- data/CHANGES +36 -0
- data/README.markdown +79 -0
- data/Rakefile +79 -0
- data/bin/integrity +4 -0
- data/config/config.sample.ru +21 -0
- data/config/config.sample.yml +41 -0
- data/config/heroku/.gems +1 -0
- data/config/heroku/Rakefile +6 -0
- data/config/heroku/config.ru +7 -0
- data/config/heroku/integrity-config.rb +14 -0
- data/config/thin.sample.yml +13 -0
- data/integrity.gemspec +140 -0
- data/lib/integrity/app.rb +138 -0
- data/lib/integrity/author.rb +39 -0
- data/lib/integrity/build.rb +91 -0
- data/lib/integrity/commit.rb +63 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/helpers/authorization.rb +33 -0
- data/lib/integrity/helpers/breadcrumbs.rb +20 -0
- data/lib/integrity/helpers/forms.rb +29 -0
- data/lib/integrity/helpers/pretty_output.rb +45 -0
- data/lib/integrity/helpers/rendering.rb +25 -0
- data/lib/integrity/helpers/resources.rb +19 -0
- data/lib/integrity/helpers/urls.rb +59 -0
- data/lib/integrity/helpers.rb +16 -0
- data/lib/integrity/installer.rb +136 -0
- data/lib/integrity/migrations.rb +151 -0
- data/lib/integrity/notifier/base.rb +74 -0
- data/lib/integrity/notifier/test/fixtures.rb +108 -0
- data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
- data/lib/integrity/notifier/test.rb +59 -0
- data/lib/integrity/notifier.rb +34 -0
- data/lib/integrity/project/deprecated.rb +17 -0
- data/lib/integrity/project/notifiers.rb +33 -0
- data/lib/integrity/project/push.rb +44 -0
- data/lib/integrity/project.rb +102 -0
- data/lib/integrity/project_builder.rb +56 -0
- data/lib/integrity/scm/git/uri.rb +57 -0
- data/lib/integrity/scm/git.rb +84 -0
- data/lib/integrity/scm.rb +19 -0
- data/lib/integrity.rb +101 -0
- data/public/buttons.css +82 -0
- data/public/reset.css +7 -0
- data/public/spinner.gif +0 -0
- data/test/acceptance/api_test.rb +97 -0
- data/test/acceptance/browse_project_builds_test.rb +65 -0
- data/test/acceptance/browse_project_test.rb +99 -0
- data/test/acceptance/build_notifications_test.rb +95 -0
- data/test/acceptance/create_project_test.rb +97 -0
- data/test/acceptance/delete_project_test.rb +53 -0
- data/test/acceptance/edit_project_test.rb +117 -0
- data/test/acceptance/error_page_test.rb +18 -0
- data/test/acceptance/installer_test.rb +79 -0
- data/test/acceptance/manual_build_project_test.rb +82 -0
- data/test/acceptance/not_found_page_test.rb +29 -0
- data/test/acceptance/project_syndication_test.rb +30 -0
- data/test/acceptance/stylesheet_test.rb +26 -0
- data/test/acceptance/unauthorized_page_test.rb +20 -0
- data/test/helpers/acceptance/email_notifier.rb +55 -0
- data/test/helpers/acceptance/git_helper.rb +99 -0
- data/test/helpers/acceptance/notifier_helper.rb +47 -0
- data/test/helpers/acceptance/textfile_notifier.rb +26 -0
- data/test/helpers/acceptance.rb +79 -0
- data/test/helpers/expectations/be_a.rb +23 -0
- data/test/helpers/expectations/change.rb +90 -0
- data/test/helpers/expectations/have.rb +105 -0
- data/test/helpers/expectations/predicates.rb +37 -0
- data/test/helpers/expectations.rb +4 -0
- data/test/helpers/fixtures.rb +87 -0
- data/test/helpers/initial_migration_fixture.sql +44 -0
- data/test/helpers.rb +87 -0
- data/test/unit/build_test.rb +86 -0
- data/test/unit/commit_test.rb +62 -0
- data/test/unit/helpers_test.rb +103 -0
- data/test/unit/integrity_test.rb +52 -0
- data/test/unit/migrations_test.rb +57 -0
- data/test/unit/notifier/base_test.rb +43 -0
- data/test/unit/notifier/test_test.rb +29 -0
- data/test/unit/notifier_test.rb +85 -0
- data/test/unit/project_builder_test.rb +118 -0
- data/test/unit/project_test.rb +371 -0
- data/test/unit/scm_test.rb +54 -0
- data/views/_commit_info.haml +24 -0
- data/views/build.haml +2 -0
- data/views/error.haml +37 -0
- data/views/home.haml +21 -0
- data/views/integrity.sass +400 -0
- data/views/layout.haml +29 -0
- data/views/new.haml +50 -0
- data/views/not_found.haml +31 -0
- data/views/notifier.haml +7 -0
- data/views/project.builder +21 -0
- data/views/project.haml +30 -0
- data/views/unauthorized.haml +38 -0
- metadata +327 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
require "dm-migrations"
|
2
|
+
require "migration_runner"
|
3
|
+
|
4
|
+
module Integrity
|
5
|
+
def self.migrate_db
|
6
|
+
setup_initial_migration if pre_migrations?
|
7
|
+
Integrity::Migrations.migrate_up!
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.setup_initial_migration
|
11
|
+
database_adapter.execute %q(CREATE TABLE "migration_info" ("migration_name" VARCHAR(255));)
|
12
|
+
database_adapter.execute %q(INSERT INTO "migration_info" ("migration_name") VALUES ("initial"))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.pre_migrations?
|
16
|
+
!table_exists?("migration_info") &&
|
17
|
+
( table_exists?("integrity_projects") &&
|
18
|
+
table_exists?("integrity_builds") &&
|
19
|
+
table_exists?("integrity_notifiers") )
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.table_exists?(table_name)
|
23
|
+
database_adapter.storage_exists?(table_name)
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.database_adapter
|
27
|
+
DataMapper.repository(:default).adapter
|
28
|
+
end
|
29
|
+
|
30
|
+
module Migrations
|
31
|
+
# This is what is actually happening:
|
32
|
+
# include DataMapper::MigrationRunner
|
33
|
+
|
34
|
+
include DataMapper::Types
|
35
|
+
|
36
|
+
migration 1, :initial, :verbose => true do
|
37
|
+
up do
|
38
|
+
create_table :integrity_projects do
|
39
|
+
column :id, Integer, :serial => true
|
40
|
+
column :name, String, :nullable => false
|
41
|
+
column :permalink, String
|
42
|
+
column :uri, URI, :nullable => false
|
43
|
+
column :branch, String, :nullable => false, :default => "master"
|
44
|
+
column :command, String, :nullable => false, :default => "rake"
|
45
|
+
column :public, Boolean, :default => true
|
46
|
+
column :building, Boolean, :default => false
|
47
|
+
column :created_at, DateTime
|
48
|
+
column :updated_at, DateTime
|
49
|
+
|
50
|
+
column :build_id, Integer
|
51
|
+
column :notifier_id, Integer
|
52
|
+
end
|
53
|
+
|
54
|
+
create_table :integrity_builds do
|
55
|
+
column :id, Integer, :serial => true
|
56
|
+
column :output, Text, :nullable => false
|
57
|
+
column :successful, Boolean, :nullable => false, :default => false
|
58
|
+
column :commit_identifier, String, :nullable => false
|
59
|
+
column :commit_metadata, Yaml, :nullable => false
|
60
|
+
column :created_at, DateTime
|
61
|
+
column :updated_at, DateTime
|
62
|
+
|
63
|
+
column :project_id, Integer
|
64
|
+
end
|
65
|
+
|
66
|
+
create_table :integrity_notifiers do
|
67
|
+
column :id, Integer, :serial => true
|
68
|
+
column :name, String, :nullable => false
|
69
|
+
column :config, Yaml, :nullable => false
|
70
|
+
|
71
|
+
column :project_id, Integer
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
migration 2, :add_commits, :verbose => true do
|
77
|
+
up do
|
78
|
+
class ::Integrity::Build
|
79
|
+
property :commit_identifier, String
|
80
|
+
property :commit_metadata, Yaml, :lazy => false
|
81
|
+
property :project_id, Integer
|
82
|
+
end
|
83
|
+
|
84
|
+
create_table :integrity_commits do
|
85
|
+
column :id, Integer, :serial => true
|
86
|
+
column :identifier, String, :nullable => false
|
87
|
+
column :message, String, :nullable => false, :length => 255
|
88
|
+
column :author, String, :nullable => false, :length => 255
|
89
|
+
column :committed_at, DateTime, :nullable => false
|
90
|
+
column :created_at, DateTime
|
91
|
+
column :updated_at, DateTime
|
92
|
+
|
93
|
+
column :project_id, Integer
|
94
|
+
end
|
95
|
+
|
96
|
+
modify_table :integrity_builds do
|
97
|
+
add_column :commit_id, Integer
|
98
|
+
add_column :started_at, DateTime
|
99
|
+
add_column :completed_at, DateTime
|
100
|
+
end
|
101
|
+
|
102
|
+
# Die, orphans, die
|
103
|
+
Build.all(:project_id => nil).destroy!
|
104
|
+
|
105
|
+
# sqlite hodgepockery
|
106
|
+
all_builds = Build.all.each {|b| b.freeze }
|
107
|
+
drop_table :integrity_builds
|
108
|
+
create_table :integrity_builds do
|
109
|
+
column :id, Integer, :serial => true
|
110
|
+
column :started_at, DateTime
|
111
|
+
column :completed_at, DateTime
|
112
|
+
column :successful, Boolean
|
113
|
+
column :output, Text, :nullable => false
|
114
|
+
column :created_at, DateTime
|
115
|
+
column :updated_at, DateTime
|
116
|
+
|
117
|
+
column :commit_id, Integer
|
118
|
+
end
|
119
|
+
|
120
|
+
all_builds.each do |build|
|
121
|
+
commit = Commit.first(:identifier => build.commit_identifier)
|
122
|
+
|
123
|
+
if commit.nil?
|
124
|
+
commit = Commit.create(:identifier => build.commit_identifier,
|
125
|
+
:message => build.commit_metadata[:message],
|
126
|
+
:author => build.commit_metadata[:author],
|
127
|
+
:committed_at => build.commit_metadata[:date],
|
128
|
+
:project_id => build.project_id)
|
129
|
+
end
|
130
|
+
|
131
|
+
Build.create(:commit_id => commit.id,
|
132
|
+
:started_at => build.created_at,
|
133
|
+
:completed_at => build.updated_at,
|
134
|
+
:successful => build.successful,
|
135
|
+
:output => build.output)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
migration 3, :add_enabled_column do
|
141
|
+
up do
|
142
|
+
modify_table(:integrity_notifiers) { add_column :enabled, Boolean }
|
143
|
+
end
|
144
|
+
|
145
|
+
down do
|
146
|
+
# TODO: sqlite doesn't support DROP COLUMN ...
|
147
|
+
# modify_table(:integrity_notifiers) { drop_column :enabled }
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Notifier
|
3
|
+
class Base
|
4
|
+
def self.notify_of_build(build, config)
|
5
|
+
Integrity.log "Notifying of build #{build.commit.short_identifier} using the #{self.class} notifier"
|
6
|
+
Timeout.timeout(8) { new(build.commit, config).deliver! }
|
7
|
+
rescue Timeout::Error
|
8
|
+
Integrity.log "#{notifier.name} notifier timed out"
|
9
|
+
false
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.to_haml
|
13
|
+
raise NotImplementedError, "you need to implement this method in your notifier"
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :commit
|
17
|
+
|
18
|
+
def initialize(commit, config)
|
19
|
+
@commit = commit
|
20
|
+
@config = config
|
21
|
+
end
|
22
|
+
|
23
|
+
def build
|
24
|
+
warn "Notifier::Base#build is deprecated, use Notifier::Base#commit instead (#{caller[0]})"
|
25
|
+
commit
|
26
|
+
end
|
27
|
+
|
28
|
+
def deliver!
|
29
|
+
raise NotImplementedError, "you need to implement this method in your notifier"
|
30
|
+
end
|
31
|
+
|
32
|
+
def short_message
|
33
|
+
commit.human_readable_status
|
34
|
+
end
|
35
|
+
|
36
|
+
def full_message
|
37
|
+
<<-EOM
|
38
|
+
"Build #{commit.identifier} #{commit.successful? ? "was successful" : "failed"}"
|
39
|
+
|
40
|
+
Commit Message: #{commit.message}
|
41
|
+
Commit Date: #{commit.committed_at}
|
42
|
+
Commit Author: #{commit.author.name}
|
43
|
+
|
44
|
+
Link: #{commit_url}
|
45
|
+
|
46
|
+
Build Output:
|
47
|
+
|
48
|
+
#{stripped_commit_output}
|
49
|
+
EOM
|
50
|
+
end
|
51
|
+
|
52
|
+
def commit_url
|
53
|
+
raise if Integrity.config[:base_uri].nil?
|
54
|
+
Integrity.config[:base_uri] / commit.project.permalink / "commits" / commit.identifier
|
55
|
+
end
|
56
|
+
|
57
|
+
def build_url
|
58
|
+
warn "Notifier::Base#build_url is deprecated, use Notifier::Base#commit_url instead (#{caller[0]})"
|
59
|
+
commit_url
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def stripped_commit_output
|
65
|
+
commit.output.gsub("\e[0m", "").gsub(/\e\[3[1-7]m/, "")
|
66
|
+
end
|
67
|
+
|
68
|
+
def stripped_build_output
|
69
|
+
warn "Notifier::Base#stripped_build_output is deprecated, use Notifier::base#stripped_commit_output instead (#{caller[0]})"
|
70
|
+
stripped_commit_output
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
require "dm-sweatshop"
|
2
|
+
|
3
|
+
include DataMapper::Sweatshop::Unique
|
4
|
+
|
5
|
+
class Array
|
6
|
+
def pick
|
7
|
+
self[rand(self.length)]
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def create_notifier!(name)
|
12
|
+
klass = Class.new(Integrity::Notifier::Base) do
|
13
|
+
def self.to_haml; ""; end
|
14
|
+
def deliver!; nil; end
|
15
|
+
end
|
16
|
+
|
17
|
+
unless Integrity::Notifier.const_defined?(name)
|
18
|
+
Integrity::Notifier.const_set(name, klass)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Integrity::Project.fixture do
|
23
|
+
{ :name => (name = unique { /\w+/.gen }),
|
24
|
+
:uri => "git://github.com/#{/\w+/.gen}/#{name}.git",
|
25
|
+
:branch => ["master", "test-refactoring", "lh-34"].pick,
|
26
|
+
:command => ["rake", "make", "ant -buildfile test.xml"].pick,
|
27
|
+
:public => [true, false].pick,
|
28
|
+
:building => [true, false].pick }
|
29
|
+
end
|
30
|
+
|
31
|
+
Integrity::Project.fixture(:integrity) do
|
32
|
+
{ :name => "Integrity",
|
33
|
+
:uri => "git://github.com/foca/integrity.git",
|
34
|
+
:branch => "master",
|
35
|
+
:command => "rake",
|
36
|
+
:public => true,
|
37
|
+
:building => false }
|
38
|
+
end
|
39
|
+
|
40
|
+
Integrity::Project.fixture(:my_test_project) do
|
41
|
+
{ :name => "My Test Project",
|
42
|
+
:uri => File.dirname(__FILE__) + "/../../",
|
43
|
+
:branch => "master",
|
44
|
+
:command => "./test",
|
45
|
+
:public => true,
|
46
|
+
:building => false }
|
47
|
+
end
|
48
|
+
|
49
|
+
Integrity::Commit.fixture do
|
50
|
+
project = Integrity::Project.first || Integrity::Project.gen
|
51
|
+
|
52
|
+
{ :identifier => Digest::SHA1.hexdigest(/[:paragraph:]/.gen),
|
53
|
+
:message => /[:sentence:]/.gen,
|
54
|
+
:author => /\w+ \w+ <\w+@example.org>/.gen,
|
55
|
+
:committed_at => unique {|i| Time.mktime(2008, 12, 15, 18, (59 - i) % 60) },
|
56
|
+
:project_id => project.id }
|
57
|
+
end
|
58
|
+
|
59
|
+
Integrity::Commit.fixture(:successful) do
|
60
|
+
Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:successful))
|
61
|
+
end
|
62
|
+
|
63
|
+
Integrity::Commit.fixture(:failed) do
|
64
|
+
Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:failed))
|
65
|
+
end
|
66
|
+
|
67
|
+
Integrity::Commit.fixture(:pending) do
|
68
|
+
Integrity::Commit.generate_attributes.update(:build => Integrity::Build.gen(:pending))
|
69
|
+
end
|
70
|
+
|
71
|
+
Integrity::Build.fixture do
|
72
|
+
commit = Integrity::Commit.first || Integrity::Commit.gen
|
73
|
+
|
74
|
+
{ :output => /[:paragraph:]/.gen,
|
75
|
+
:successful => true,
|
76
|
+
:started_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) },
|
77
|
+
:created_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) },
|
78
|
+
:completed_at => unique {|i| Time.mktime(2008, 12, 15, 18, i % 60) },
|
79
|
+
:commit_id => commit.id }
|
80
|
+
end
|
81
|
+
|
82
|
+
Integrity::Build.fixture(:successful) do
|
83
|
+
Integrity::Build.generate_attributes.update(:successful => true)
|
84
|
+
end
|
85
|
+
|
86
|
+
Integrity::Build.fixture(:failed) do
|
87
|
+
Integrity::Build.generate_attributes.update(:successful => false)
|
88
|
+
end
|
89
|
+
|
90
|
+
Integrity::Build.fixture(:pending) do
|
91
|
+
Integrity::Build.generate_attributes.update(:successful => nil, :started_at => nil, :completed_at => nil)
|
92
|
+
end
|
93
|
+
|
94
|
+
Integrity::Notifier.fixture(:irc) do
|
95
|
+
create_notifier! "IRC"
|
96
|
+
|
97
|
+
{ :project => Integrity::Project.generate,
|
98
|
+
:name => "IRC",
|
99
|
+
:config => { :uri => "irc://irc.freenode.net/integrity" }}
|
100
|
+
end
|
101
|
+
|
102
|
+
Integrity::Notifier.fixture(:twitter) do
|
103
|
+
create_notifier! "Twitter"
|
104
|
+
|
105
|
+
{ :project => Integrity::Project.generate,
|
106
|
+
:name => "Twitter",
|
107
|
+
:config => { :email => "foo@example.org", :pass => "secret" }}
|
108
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "hpricot"
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class Notifier
|
5
|
+
module Test
|
6
|
+
# Thanks Harry! http://gist.github.com/39960
|
7
|
+
class HpricotMatcher
|
8
|
+
def initialize(html)
|
9
|
+
@doc = Hpricot(html)
|
10
|
+
end
|
11
|
+
|
12
|
+
# elements('h1') returns a Hpricot::Elements object with all h1-tags.
|
13
|
+
def elements(selector)
|
14
|
+
@doc.search(selector)
|
15
|
+
end
|
16
|
+
|
17
|
+
# element('h1') returns Hpricot::Elem with first h1-tag, or nil if
|
18
|
+
# none exist.
|
19
|
+
def element(selector)
|
20
|
+
@doc.at(selector)
|
21
|
+
end
|
22
|
+
|
23
|
+
# tags('h1') returns the inner HTML of all matched elements mathed.
|
24
|
+
def tags(selector)
|
25
|
+
e = elements(selector)
|
26
|
+
e.map {|x| x.inner_html}
|
27
|
+
end
|
28
|
+
|
29
|
+
# tag('h1') returns the inner HTML of the first mached element, or
|
30
|
+
# nil if none matched.
|
31
|
+
def tag(selector)
|
32
|
+
e = element(selector)
|
33
|
+
e && e.inner_html
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../../integrity"
|
2
|
+
|
3
|
+
require "integrity/notifier/test/hpricot_matcher"
|
4
|
+
require "integrity/notifier/test/fixtures"
|
5
|
+
|
6
|
+
module Integrity
|
7
|
+
class Notifier
|
8
|
+
module Test
|
9
|
+
def setup_database
|
10
|
+
DataMapper.setup(:default, "sqlite3::memory:")
|
11
|
+
DataMapper.auto_migrate!
|
12
|
+
end
|
13
|
+
|
14
|
+
def notifier_class
|
15
|
+
Integrity::Notifier.const_get(notifier)
|
16
|
+
end
|
17
|
+
|
18
|
+
def notification(commit)
|
19
|
+
notifier_class.new(commit).full_message
|
20
|
+
end
|
21
|
+
|
22
|
+
def notification_successful
|
23
|
+
notification(Integrity::Commit.gen(:successful))
|
24
|
+
end
|
25
|
+
|
26
|
+
def notification_failed
|
27
|
+
notification(Integrity::Commit.gen(:failed))
|
28
|
+
end
|
29
|
+
|
30
|
+
def assert_form_have_option(option, value=nil)
|
31
|
+
selector = "input##{notifier.downcase}_notifier_#{option}"
|
32
|
+
selector << "[@name='notifiers[#{notifier}][#{option}]']"
|
33
|
+
selector << "[@value='#{value}']" if value
|
34
|
+
|
35
|
+
assert_form_have_tag(selector, option => value)
|
36
|
+
end
|
37
|
+
|
38
|
+
def assert_form_have_options(*options)
|
39
|
+
options.each { |option| assert_form_have_option(option) }
|
40
|
+
end
|
41
|
+
|
42
|
+
def assert_form_have_tag(selector, options={})
|
43
|
+
content = options.delete(:content)
|
44
|
+
assert_have_tag(form(options), selector, content)
|
45
|
+
end
|
46
|
+
|
47
|
+
def assert_have_tag(html, selector, content=nil)
|
48
|
+
matcher = HpricotMatcher.new(html)
|
49
|
+
assert_equal content, matcher.tag(selector) if content
|
50
|
+
assert matcher.tag(selector)
|
51
|
+
end
|
52
|
+
|
53
|
+
def form(config={})
|
54
|
+
Haml::Engine.new(notifier_class.to_haml).
|
55
|
+
render(OpenStruct.new(:config => config))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/notifier/base"
|
2
|
+
|
3
|
+
module Integrity
|
4
|
+
class Notifier
|
5
|
+
include DataMapper::Resource
|
6
|
+
|
7
|
+
property :id, Integer, :serial => true
|
8
|
+
property :name, String, :nullable => false
|
9
|
+
property :enabled, Boolean, :nullable => false, :default => false
|
10
|
+
property :config, Yaml, :nullable => false, :lazy => false
|
11
|
+
|
12
|
+
belongs_to :project, :class_name => "Integrity::Project"
|
13
|
+
|
14
|
+
validates_is_unique :name, :scope => :project_id
|
15
|
+
validates_present :project_id
|
16
|
+
|
17
|
+
def self.register(klass)
|
18
|
+
Integrity.register_notifier(klass)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.available
|
22
|
+
Integrity.notifiers
|
23
|
+
end
|
24
|
+
|
25
|
+
def notify_of_build(build)
|
26
|
+
to_const.notify_of_build(build, config)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def to_const
|
31
|
+
self.class.available[name]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Project
|
3
|
+
module Helpers
|
4
|
+
module Deprecated
|
5
|
+
def last_build
|
6
|
+
warn "Project#last_build is deprecated, use Project#last_commit (#{caller[0]})"
|
7
|
+
last_commit
|
8
|
+
end
|
9
|
+
|
10
|
+
def previous_builds
|
11
|
+
warn "Project#previous_builds is deprecated, use Project#previous_commits (#{caller[0]})"
|
12
|
+
previous_commits
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Project
|
3
|
+
module Helpers
|
4
|
+
module Notifiers
|
5
|
+
def notifies?(notifier)
|
6
|
+
return false unless notifier = notifiers.first(:name => notifier)
|
7
|
+
|
8
|
+
notifier.enabled?
|
9
|
+
end
|
10
|
+
|
11
|
+
def enabled_notifiers
|
12
|
+
notifiers.all(:enabled => true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def config_for(notifier)
|
16
|
+
notifier = notifiers.first(:name => notifier)
|
17
|
+
notifier ? notifier.config : {}
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_notifiers(to_enable, config)
|
21
|
+
config.each_pair { |name, config|
|
22
|
+
notifier = notifiers.first(:name => name)
|
23
|
+
notifier ||= notifiers.new(:name => name)
|
24
|
+
|
25
|
+
notifier.enabled = to_enable.include?(name)
|
26
|
+
notifier.config = config
|
27
|
+
notifier.save
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Project
|
3
|
+
module Helpers
|
4
|
+
module Push
|
5
|
+
def push(payload)
|
6
|
+
payload = parse_payload(payload)
|
7
|
+
raise ArgumentError unless valid_payload?(payload)
|
8
|
+
|
9
|
+
commits =
|
10
|
+
if Integrity.config[:build_all_commits]
|
11
|
+
payload["commits"]
|
12
|
+
else
|
13
|
+
[ payload["commits"].first ]
|
14
|
+
end
|
15
|
+
|
16
|
+
commits.each do |commit_data|
|
17
|
+
create_commit_from(commit_data)
|
18
|
+
build(commit_data["id"])
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def create_commit_from(data)
|
24
|
+
commits.create(:identifier => data["id"],
|
25
|
+
:author => "#{data["author"]["name"]} <#{data["author"]["email"]}>",
|
26
|
+
:message => data["message"],
|
27
|
+
:committed_at => data["timestamp"])
|
28
|
+
end
|
29
|
+
|
30
|
+
def valid_payload?(payload)
|
31
|
+
payload && payload["ref"].to_s.include?(branch) &&
|
32
|
+
!payload["commits"].nil? &&
|
33
|
+
!payload["commits"].to_a.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_payload(payload)
|
37
|
+
JSON.parse(payload.to_s)
|
38
|
+
rescue JSON::ParserError
|
39
|
+
false
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require "integrity/project/notifiers"
|
2
|
+
require "integrity/project/push"
|
3
|
+
require "integrity/project/deprecated"
|
4
|
+
|
5
|
+
module Integrity
|
6
|
+
class Project
|
7
|
+
include DataMapper::Resource
|
8
|
+
|
9
|
+
include Helpers::Deprecated
|
10
|
+
include Helpers::Notifiers
|
11
|
+
include Helpers::Push
|
12
|
+
|
13
|
+
property :id, Integer, :serial => true
|
14
|
+
property :name, String, :nullable => false
|
15
|
+
property :permalink, String
|
16
|
+
property :uri, URI, :nullable => false, :length => 255
|
17
|
+
property :branch, String, :nullable => false, :default => "master"
|
18
|
+
property :command, String, :nullable => false, :length => 255, :default => "rake"
|
19
|
+
property :public, Boolean, :default => true
|
20
|
+
property :building, Boolean, :default => false
|
21
|
+
property :created_at, DateTime
|
22
|
+
property :updated_at, DateTime
|
23
|
+
|
24
|
+
has n, :commits, :class_name => "Integrity::Commit"
|
25
|
+
has n, :notifiers, :class_name => "Integrity::Notifier"
|
26
|
+
|
27
|
+
before :save, :set_permalink
|
28
|
+
before :destroy, :delete_working_directory
|
29
|
+
|
30
|
+
validates_is_unique :name
|
31
|
+
|
32
|
+
def self.only_public_unless(condition)
|
33
|
+
if condition
|
34
|
+
all
|
35
|
+
else
|
36
|
+
all(:public => true)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def build(commit_identifier="HEAD")
|
41
|
+
commit_identifier = head_of_remote_repo if commit_identifier == "HEAD"
|
42
|
+
commit = find_or_create_commit_with_identifier(commit_identifier)
|
43
|
+
Build.queue(commit)
|
44
|
+
end
|
45
|
+
|
46
|
+
def last_commit
|
47
|
+
commits.first(:project_id => id, :order => [:committed_at.desc])
|
48
|
+
end
|
49
|
+
|
50
|
+
def previous_commits
|
51
|
+
commits.all(:project_id => id, :order => [:committed_at.desc]).tap {|commits| commits.shift }
|
52
|
+
end
|
53
|
+
|
54
|
+
def status
|
55
|
+
last_commit && last_commit.status
|
56
|
+
end
|
57
|
+
|
58
|
+
def human_readable_status
|
59
|
+
last_commit && last_commit.human_readable_status
|
60
|
+
end
|
61
|
+
|
62
|
+
def public=(flag)
|
63
|
+
attribute_set(:public, case flag
|
64
|
+
when "1", "0" then flag == "1"
|
65
|
+
else !!flag
|
66
|
+
end)
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def find_or_create_commit_with_identifier(commit_identifier)
|
71
|
+
# We abuse +committed_at+ here setting it to Time.now because we use it
|
72
|
+
# to sort (for last_commit and previous_commits). I don't like this
|
73
|
+
# very much, but for now it's the only solution I can find.
|
74
|
+
#
|
75
|
+
# This also creates a dependency, as now we *always* have to update the
|
76
|
+
# +committed_at+ field after building to ensure the date is correct :(
|
77
|
+
#
|
78
|
+
# This might also make your commit listings a little jumpy, if some
|
79
|
+
# commits change place every time a build finishes =\
|
80
|
+
commits.first_or_create({ :identifier => commit_identifier, :project_id => id }, :committed_at => Time.now)
|
81
|
+
end
|
82
|
+
|
83
|
+
def head_of_remote_repo
|
84
|
+
SCM.new(uri, branch).head
|
85
|
+
end
|
86
|
+
|
87
|
+
def set_permalink
|
88
|
+
self.permalink = (name || "").downcase.
|
89
|
+
gsub(/'s/, "s").
|
90
|
+
gsub(/&/, "and").
|
91
|
+
gsub(/[^a-z0-9]+/, "-").
|
92
|
+
gsub(/-*$/, "")
|
93
|
+
end
|
94
|
+
|
95
|
+
def delete_working_directory
|
96
|
+
commits.all(:project_id => id).destroy!
|
97
|
+
ProjectBuilder.delete_working_directory(self)
|
98
|
+
rescue SCM::SCMUnknownError => error
|
99
|
+
Integrity.log "Problem while trying to deleting code: #{error}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|