alphasights-integrity 0.1.9.3
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/.gitignore +13 -0
- data/CHANGES +48 -0
- data/README.md +82 -0
- data/Rakefile +58 -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 +137 -0
- data/lib/integrity.rb +77 -0
- data/lib/integrity/app.rb +138 -0
- data/lib/integrity/author.rb +39 -0
- data/lib/integrity/build.rb +52 -0
- data/lib/integrity/commit.rb +61 -0
- data/lib/integrity/core_ext/object.rb +6 -0
- data/lib/integrity/helpers.rb +16 -0
- data/lib/integrity/helpers/authorization.rb +33 -0
- data/lib/integrity/helpers/breadcrumbs.rb +20 -0
- data/lib/integrity/helpers/gravatar.rb +16 -0
- data/lib/integrity/helpers/pretty_output.rb +45 -0
- data/lib/integrity/helpers/rendering.rb +49 -0
- data/lib/integrity/helpers/resources.rb +19 -0
- data/lib/integrity/helpers/urls.rb +59 -0
- data/lib/integrity/installer.rb +138 -0
- data/lib/integrity/migrations.rb +153 -0
- data/lib/integrity/notifier.rb +44 -0
- data/lib/integrity/notifier/base.rb +74 -0
- data/lib/integrity/notifier/test.rb +52 -0
- data/lib/integrity/notifier/test/fixtures.rb +108 -0
- data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
- data/lib/integrity/project.rb +94 -0
- data/lib/integrity/project/notifiers.rb +31 -0
- data/lib/integrity/project/push.rb +43 -0
- data/lib/integrity/project_builder.rb +56 -0
- data/lib/integrity/scm.rb +19 -0
- data/lib/integrity/scm/git.rb +84 -0
- data/lib/integrity/scm/git/uri.rb +57 -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 +114 -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 +21 -0
- data/test/acceptance/installer_test.rb +81 -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.rb +75 -0
- data/test/helpers/acceptance.rb +82 -0
- data/test/helpers/acceptance/email_notifier.rb +52 -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/expectations.rb +4 -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/initial_migration_fixture.sql +44 -0
- data/test/unit/build_test.rb +72 -0
- data/test/unit/commit_test.rb +66 -0
- data/test/unit/helpers_test.rb +103 -0
- data/test/unit/integrity_test.rb +35 -0
- data/test/unit/migrations_test.rb +57 -0
- data/test/unit/notifier/base_test.rb +43 -0
- data/test/unit/notifier_test.rb +96 -0
- data/test/unit/project_builder_test.rb +118 -0
- data/test/unit/project_test.rb +344 -0
- data/test/unit/scm_test.rb +54 -0
- data/views/_commit_info.haml +30 -0
- data/views/build.haml +2 -0
- data/views/error.haml +37 -0
- data/views/home.haml +22 -0
- data/views/integrity.sass +424 -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 +31 -0
- data/views/unauthorized.haml +38 -0
- metadata +324 -0
@@ -0,0 +1,44 @@
|
|
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
|
+
:child_key => [:project_id]
|
14
|
+
|
15
|
+
validates_is_unique :name, :scope => :project_id
|
16
|
+
validates_present :project_id
|
17
|
+
|
18
|
+
def self.available
|
19
|
+
@@_notifiers ||= {}
|
20
|
+
@@_notifiers
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.register(klass)
|
24
|
+
raise ArgumentError unless valid?(klass)
|
25
|
+
|
26
|
+
available[klass.to_s.split(":").last] = klass
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.valid?(notifier)
|
30
|
+
notifier.respond_to?(:to_haml) && notifier.respond_to?(:notify_of_build) &&
|
31
|
+
notifier != Notifier::Base
|
32
|
+
end
|
33
|
+
private_class_method :valid?
|
34
|
+
|
35
|
+
def notify_of_build(build)
|
36
|
+
to_const.notify_of_build(build, config)
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def to_const
|
41
|
+
self.class.available[name]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
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,52 @@
|
|
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 build(state=:successful)
|
15
|
+
Integrity::Build.gen(state)
|
16
|
+
end
|
17
|
+
|
18
|
+
def notifier_class
|
19
|
+
Integrity::Notifier.const_get(notifier)
|
20
|
+
end
|
21
|
+
|
22
|
+
def provides_option?(option, value=nil)
|
23
|
+
selector = "input##{notifier.downcase}_notifier_#{option}"
|
24
|
+
selector << "[@name='notifiers[#{notifier}][#{option}]']"
|
25
|
+
selector << "[@value='#{value}']" if value
|
26
|
+
|
27
|
+
form_have_tag?(selector, option => value)
|
28
|
+
end
|
29
|
+
|
30
|
+
def provides_options(*options)
|
31
|
+
options.each { |option| assert_form_have_option(option) }
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def form(config={})
|
36
|
+
Haml::Engine.new(notifier_class.to_haml).
|
37
|
+
render(OpenStruct.new(:config => config))
|
38
|
+
end
|
39
|
+
|
40
|
+
def form_have_tag?(selector, options={})
|
41
|
+
content = options.delete(:content)
|
42
|
+
have_tag?(form(options), selector, content)
|
43
|
+
end
|
44
|
+
|
45
|
+
def have_tag?(html, selector, content=nil)
|
46
|
+
matcher = HpricotMatcher.new(html)
|
47
|
+
assert_equal content, matcher.tag(selector) if content
|
48
|
+
matcher.tag(selector)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
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,94 @@
|
|
1
|
+
require "integrity/project/notifiers"
|
2
|
+
require "integrity/project/push"
|
3
|
+
|
4
|
+
module Integrity
|
5
|
+
class Project
|
6
|
+
include DataMapper::Resource
|
7
|
+
include Notifiers, Push
|
8
|
+
|
9
|
+
property :id, Integer, :serial => true
|
10
|
+
property :name, String, :nullable => false
|
11
|
+
property :permalink, String
|
12
|
+
property :uri, URI, :nullable => false, :length => 255
|
13
|
+
property :branch, String, :nullable => false, :default => "master"
|
14
|
+
property :command, String, :nullable => false, :length => 255, :default => "rake"
|
15
|
+
property :public, Boolean, :default => true
|
16
|
+
property :building, Boolean, :default => false
|
17
|
+
property :created_at, DateTime
|
18
|
+
property :updated_at, DateTime
|
19
|
+
|
20
|
+
has n, :commits, :class_name => "Integrity::Commit",
|
21
|
+
:order => [:committed_at.desc]
|
22
|
+
has n, :notifiers, :class_name => "Integrity::Notifier"
|
23
|
+
|
24
|
+
before :save, :set_permalink
|
25
|
+
before :destroy, :delete_working_directory
|
26
|
+
|
27
|
+
validates_is_unique :name
|
28
|
+
|
29
|
+
def build(commit_identifier="HEAD")
|
30
|
+
commit_identifier = head_of_remote_repo if commit_identifier == "HEAD"
|
31
|
+
commit = find_or_create_commit_with_identifier(commit_identifier)
|
32
|
+
|
33
|
+
Build.queue(commit)
|
34
|
+
end
|
35
|
+
|
36
|
+
def last_commit
|
37
|
+
commits.first(:project_id => id, :order => [:committed_at.desc])
|
38
|
+
end
|
39
|
+
|
40
|
+
def previous_commits
|
41
|
+
commits.all(:project_id => id, :order => [:committed_at.desc]).
|
42
|
+
tap {|commits| commits.shift }
|
43
|
+
end
|
44
|
+
|
45
|
+
def status
|
46
|
+
last_commit && last_commit.status
|
47
|
+
end
|
48
|
+
|
49
|
+
def human_readable_status
|
50
|
+
last_commit && last_commit.human_readable_status
|
51
|
+
end
|
52
|
+
|
53
|
+
def public=(flag)
|
54
|
+
attribute_set(:public, case flag
|
55
|
+
when "1", "0" then flag == "1"
|
56
|
+
else !!flag
|
57
|
+
end)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
def find_or_create_commit_with_identifier(identifier)
|
62
|
+
# We abuse +committed_at+ here setting it to Time.now because we use it
|
63
|
+
# to sort (for last_commit and previous_commits). I don't like this
|
64
|
+
# very much, but for now it's the only solution I can find.
|
65
|
+
#
|
66
|
+
# This also creates a dependency, as now we *always* have to update the
|
67
|
+
# +committed_at+ field after building to ensure the date is correct :(
|
68
|
+
#
|
69
|
+
# This might also make your commit listings a little jumpy, if some
|
70
|
+
# commits change place every time a build finishes =\
|
71
|
+
commits.first_or_create({:identifier => identifier, :project_id => id},
|
72
|
+
:committed_at => Time.now)
|
73
|
+
end
|
74
|
+
|
75
|
+
def head_of_remote_repo
|
76
|
+
SCM.new(uri, branch).head
|
77
|
+
end
|
78
|
+
|
79
|
+
def set_permalink
|
80
|
+
attribute_set(:permalink, (name || "").downcase.
|
81
|
+
gsub(/'s/, "s").
|
82
|
+
gsub(/&/, "and").
|
83
|
+
gsub(/[^a-z0-9]+/, "-").
|
84
|
+
gsub(/-*$/, ""))
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_working_directory
|
88
|
+
commits.destroy!
|
89
|
+
ProjectBuilder.delete_working_directory(self)
|
90
|
+
rescue SCM::SCMUnknownError => error
|
91
|
+
Integrity.log "Problem while trying to deleting code: #{error}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Integrity
|
2
|
+
class Project
|
3
|
+
module Notifiers
|
4
|
+
def notifies?(notifier)
|
5
|
+
return false unless notifier = notifiers.first(:name => notifier)
|
6
|
+
|
7
|
+
notifier.enabled?
|
8
|
+
end
|
9
|
+
|
10
|
+
def enabled_notifiers
|
11
|
+
notifiers.all(:enabled => true)
|
12
|
+
end
|
13
|
+
|
14
|
+
def config_for(notifier)
|
15
|
+
notifier = notifiers.first(:name => notifier)
|
16
|
+
notifier ? notifier.config : {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def update_notifiers(to_enable, config)
|
20
|
+
config.each_pair { |name, config|
|
21
|
+
notifier = notifiers.first(:name => name)
|
22
|
+
notifier ||= notifiers.new(:name => name)
|
23
|
+
|
24
|
+
notifier.enabled = to_enable.include?(name)
|
25
|
+
notifier.config = config
|
26
|
+
notifier.save
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|