imbriaco-integrity 0.1.9.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/.gitignore +12 -0
  2. data/CHANGES +36 -0
  3. data/README.markdown +79 -0
  4. data/Rakefile +79 -0
  5. data/bin/integrity +4 -0
  6. data/config/config.sample.ru +21 -0
  7. data/config/config.sample.yml +41 -0
  8. data/config/heroku/.gems +1 -0
  9. data/config/heroku/Rakefile +6 -0
  10. data/config/heroku/config.ru +7 -0
  11. data/config/heroku/integrity-config.rb +14 -0
  12. data/config/thin.sample.yml +13 -0
  13. data/integrity.gemspec +140 -0
  14. data/lib/integrity/app.rb +138 -0
  15. data/lib/integrity/author.rb +39 -0
  16. data/lib/integrity/build.rb +91 -0
  17. data/lib/integrity/commit.rb +63 -0
  18. data/lib/integrity/core_ext/object.rb +6 -0
  19. data/lib/integrity/helpers/authorization.rb +33 -0
  20. data/lib/integrity/helpers/breadcrumbs.rb +20 -0
  21. data/lib/integrity/helpers/forms.rb +29 -0
  22. data/lib/integrity/helpers/pretty_output.rb +45 -0
  23. data/lib/integrity/helpers/rendering.rb +25 -0
  24. data/lib/integrity/helpers/resources.rb +19 -0
  25. data/lib/integrity/helpers/urls.rb +59 -0
  26. data/lib/integrity/helpers.rb +16 -0
  27. data/lib/integrity/installer.rb +136 -0
  28. data/lib/integrity/migrations.rb +151 -0
  29. data/lib/integrity/notifier/base.rb +74 -0
  30. data/lib/integrity/notifier/test/fixtures.rb +108 -0
  31. data/lib/integrity/notifier/test/hpricot_matcher.rb +38 -0
  32. data/lib/integrity/notifier/test.rb +59 -0
  33. data/lib/integrity/notifier.rb +34 -0
  34. data/lib/integrity/project/deprecated.rb +17 -0
  35. data/lib/integrity/project/notifiers.rb +33 -0
  36. data/lib/integrity/project/push.rb +44 -0
  37. data/lib/integrity/project.rb +102 -0
  38. data/lib/integrity/project_builder.rb +56 -0
  39. data/lib/integrity/scm/git/uri.rb +57 -0
  40. data/lib/integrity/scm/git.rb +84 -0
  41. data/lib/integrity/scm.rb +19 -0
  42. data/lib/integrity.rb +101 -0
  43. data/public/buttons.css +82 -0
  44. data/public/reset.css +7 -0
  45. data/public/spinner.gif +0 -0
  46. data/test/acceptance/api_test.rb +97 -0
  47. data/test/acceptance/browse_project_builds_test.rb +65 -0
  48. data/test/acceptance/browse_project_test.rb +99 -0
  49. data/test/acceptance/build_notifications_test.rb +95 -0
  50. data/test/acceptance/create_project_test.rb +97 -0
  51. data/test/acceptance/delete_project_test.rb +53 -0
  52. data/test/acceptance/edit_project_test.rb +117 -0
  53. data/test/acceptance/error_page_test.rb +18 -0
  54. data/test/acceptance/installer_test.rb +79 -0
  55. data/test/acceptance/manual_build_project_test.rb +82 -0
  56. data/test/acceptance/not_found_page_test.rb +29 -0
  57. data/test/acceptance/project_syndication_test.rb +30 -0
  58. data/test/acceptance/stylesheet_test.rb +26 -0
  59. data/test/acceptance/unauthorized_page_test.rb +20 -0
  60. data/test/helpers/acceptance/email_notifier.rb +55 -0
  61. data/test/helpers/acceptance/git_helper.rb +99 -0
  62. data/test/helpers/acceptance/notifier_helper.rb +47 -0
  63. data/test/helpers/acceptance/textfile_notifier.rb +26 -0
  64. data/test/helpers/acceptance.rb +79 -0
  65. data/test/helpers/expectations/be_a.rb +23 -0
  66. data/test/helpers/expectations/change.rb +90 -0
  67. data/test/helpers/expectations/have.rb +105 -0
  68. data/test/helpers/expectations/predicates.rb +37 -0
  69. data/test/helpers/expectations.rb +4 -0
  70. data/test/helpers/fixtures.rb +87 -0
  71. data/test/helpers/initial_migration_fixture.sql +44 -0
  72. data/test/helpers.rb +87 -0
  73. data/test/unit/build_test.rb +86 -0
  74. data/test/unit/commit_test.rb +62 -0
  75. data/test/unit/helpers_test.rb +103 -0
  76. data/test/unit/integrity_test.rb +52 -0
  77. data/test/unit/migrations_test.rb +57 -0
  78. data/test/unit/notifier/base_test.rb +43 -0
  79. data/test/unit/notifier/test_test.rb +29 -0
  80. data/test/unit/notifier_test.rb +85 -0
  81. data/test/unit/project_builder_test.rb +118 -0
  82. data/test/unit/project_test.rb +371 -0
  83. data/test/unit/scm_test.rb +54 -0
  84. data/views/_commit_info.haml +24 -0
  85. data/views/build.haml +2 -0
  86. data/views/error.haml +37 -0
  87. data/views/home.haml +21 -0
  88. data/views/integrity.sass +400 -0
  89. data/views/layout.haml +29 -0
  90. data/views/new.haml +50 -0
  91. data/views/not_found.haml +31 -0
  92. data/views/notifier.haml +7 -0
  93. data/views/project.builder +21 -0
  94. data/views/project.haml +30 -0
  95. data/views/unauthorized.haml +38 -0
  96. 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