imbriaco-integrity 0.1.9.2

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