foca-integrity 0.1.8 → 0.1.9.0

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 (75) hide show
  1. data/README.markdown +7 -0
  2. data/Rakefile +89 -81
  3. data/config/config.sample.ru +11 -21
  4. data/config/config.sample.yml +15 -12
  5. data/lib/integrity.rb +21 -23
  6. data/lib/integrity/app.rb +138 -0
  7. data/lib/integrity/author.rb +39 -0
  8. data/lib/integrity/build.rb +54 -31
  9. data/lib/integrity/commit.rb +71 -0
  10. data/lib/integrity/helpers.rb +3 -3
  11. data/lib/integrity/helpers/authorization.rb +2 -2
  12. data/lib/integrity/helpers/forms.rb +3 -3
  13. data/lib/integrity/helpers/pretty_output.rb +1 -1
  14. data/lib/integrity/helpers/rendering.rb +6 -1
  15. data/lib/integrity/helpers/resources.rb +9 -3
  16. data/lib/integrity/helpers/urls.rb +15 -13
  17. data/lib/integrity/installer.rb +43 -60
  18. data/lib/integrity/migrations.rb +31 -48
  19. data/lib/integrity/notifier.rb +14 -16
  20. data/lib/integrity/notifier/base.rb +29 -19
  21. data/lib/integrity/notifier/test_helpers.rb +100 -0
  22. data/lib/integrity/project.rb +69 -33
  23. data/lib/integrity/project_builder.rb +23 -14
  24. data/lib/integrity/scm/git.rb +15 -14
  25. data/lib/integrity/scm/git/uri.rb +9 -9
  26. data/test/acceptance/api_test.rb +97 -0
  27. data/test/acceptance/browse_project_builds_test.rb +65 -0
  28. data/test/acceptance/browse_project_test.rb +95 -0
  29. data/test/acceptance/build_notifications_test.rb +42 -0
  30. data/test/acceptance/create_project_test.rb +97 -0
  31. data/test/acceptance/delete_project_test.rb +53 -0
  32. data/test/acceptance/edit_project_test.rb +117 -0
  33. data/test/acceptance/error_page_test.rb +18 -0
  34. data/test/acceptance/helpers.rb +2 -0
  35. data/test/acceptance/installer_test.rb +62 -0
  36. data/test/acceptance/manual_build_project_test.rb +82 -0
  37. data/test/acceptance/notifier_test.rb +109 -0
  38. data/test/acceptance/project_syndication_test.rb +30 -0
  39. data/test/acceptance/stylesheet_test.rb +18 -0
  40. data/test/helpers.rb +59 -27
  41. data/test/helpers/acceptance.rb +19 -64
  42. data/test/helpers/acceptance/email_notifier.rb +55 -0
  43. data/test/helpers/acceptance/git_helper.rb +15 -15
  44. data/test/helpers/acceptance/textfile_notifier.rb +3 -3
  45. data/test/helpers/expectations.rb +0 -1
  46. data/test/helpers/expectations/be_a.rb +4 -4
  47. data/test/helpers/expectations/change.rb +5 -5
  48. data/test/helpers/expectations/have.rb +4 -4
  49. data/test/helpers/expectations/predicates.rb +4 -4
  50. data/test/helpers/fixtures.rb +44 -18
  51. data/test/helpers/initial_migration_fixture.sql +44 -0
  52. data/test/unit/build_test.rb +51 -0
  53. data/test/unit/commit_test.rb +83 -0
  54. data/test/unit/helpers_test.rb +56 -0
  55. data/test/unit/integrity_test.rb +18 -0
  56. data/test/unit/migrations_test.rb +56 -0
  57. data/test/unit/notifier_test.rb +123 -0
  58. data/test/unit/project_builder_test.rb +108 -0
  59. data/test/unit/project_test.rb +282 -0
  60. data/test/unit/scm_test.rb +54 -0
  61. data/views/_commit_info.haml +24 -0
  62. data/views/build.haml +2 -2
  63. data/views/error.haml +4 -3
  64. data/views/home.haml +3 -5
  65. data/views/integrity.sass +19 -6
  66. data/views/new.haml +6 -6
  67. data/views/project.builder +9 -9
  68. data/views/project.haml +14 -12
  69. metadata +98 -116
  70. data/VERSION.yml +0 -4
  71. data/app.rb +0 -137
  72. data/integrity.gemspec +0 -76
  73. data/lib/integrity/core_ext/string.rb +0 -5
  74. data/test/helpers/expectations/have_tag.rb +0 -128
  75. data/views/_build_info.haml +0 -18
@@ -0,0 +1,39 @@
1
+ module Integrity
2
+ class Author < DataMapper::Type
3
+ primitive String
4
+ size 65535
5
+ lazy true
6
+
7
+ class AuthorStruct < Struct.new(:name, :email)
8
+ def self.parse(string)
9
+ raise ArgumentError.new("invalid author string") unless string =~ /^(.*) <(.*)>$/
10
+
11
+ new($1.strip, $2.strip)
12
+ end
13
+
14
+ def to_s
15
+ @full ||= "#{name} <#{email}>"
16
+ end
17
+
18
+ alias_method :full, :to_s
19
+ end
20
+
21
+ def self.load(value, property)
22
+ AuthorStruct.parse(value) unless value.nil?
23
+ end
24
+
25
+ def self.dump(value, property)
26
+ return nil if value.nil?
27
+
28
+ value.to_s
29
+ end
30
+
31
+ def self.typecast(value, property)
32
+ case value
33
+ when AuthorStruct then value
34
+ when NilClass then load(nil, property)
35
+ else load(value.to_s, property)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -2,60 +2,83 @@ module Integrity
2
2
  class Build
3
3
  include DataMapper::Resource
4
4
 
5
- property :id, Serial
6
- property :output, Text, :nullable => false, :default => ""
7
- property :successful, Boolean, :nullable => false, :default => false
8
- property :commit_identifier, String, :nullable => false
9
- property :commit_metadata, Yaml, :nullable => false, :lazy => false
10
- property :created_at, DateTime
11
- property :updated_at, DateTime
5
+ property :id, Integer, :serial => true
6
+ property :output, Text, :default => "", :lazy => false
7
+ property :successful, Boolean, :default => false
8
+ property :commit_id, Integer, :nullable => false
9
+ property :created_at, DateTime
10
+ property :updated_at, DateTime
11
+ property :started_at, DateTime
12
+ property :completed_at, DateTime
12
13
 
13
- belongs_to :project, :class_name => "Integrity::Project"
14
+ belongs_to :commit, :class_name => "Integrity::Commit"
15
+
16
+ def self.pending
17
+ all(:started_at => nil)
18
+ end
19
+
20
+ def pending?
21
+ started_at.nil?
22
+ end
14
23
 
15
24
  def failed?
16
25
  !successful?
17
26
  end
18
27
 
19
28
  def status
20
- successful? ? :success : :failed
29
+ case
30
+ when pending? then :pending
31
+ when successful? then :success
32
+ when failed? then :failed
33
+ end
21
34
  end
22
35
 
23
- def human_readable_status
24
- successful? ? "Build Successful" : "Build Failed"
36
+ def start!(time=Time.now)
37
+ self.started_at = time
25
38
  end
26
39
 
40
+ def complete!(time=Time.now)
41
+ self.completed_at = time
42
+ end
43
+
44
+ #
45
+ # Deprecated methods
46
+ #
27
47
  def short_commit_identifier
28
- sha1?(commit_identifier) ? commit_identifier[0..6] : commit_identifier
48
+ warn "Build#short_commit_identifier is deprecated, use Commit#short_identifier"
49
+ commit.short_identifier
29
50
  end
30
-
31
- def commit_metadata
32
- case data = attribute_get(:commit_metadata)
33
- when String; YAML.load(data)
34
- else data
35
- end
51
+
52
+ def commit_identifier
53
+ warn "Build#commit_identifier is deprecated, use Commit#identifier"
54
+ commit.identifier
36
55
  end
37
56
 
38
57
  def commit_author
39
- @author ||= begin
40
- commit_metadata[:author] =~ /^(.*) <(.*)>$/
41
- OpenStruct.new(:name => $1.strip, :email => $2.strip, :full => commit_metadata[:author])
42
- end
58
+ warn "Build#commit_author is deprecated, use Commit#author"
59
+ commit.author
43
60
  end
44
61
 
45
62
  def commit_message
46
- commit_metadata[:message]
63
+ warn "Build#commit_message is deprecated, use Commit#message"
64
+ commit.message
47
65
  end
48
66
 
49
67
  def commited_at
50
- case commit_metadata[:date]
51
- when String then Time.parse(commit_metadata[:date])
52
- else commit_metadata[:date]
53
- end
68
+ warn "Build#commited_at is deprecated, use Commit#committed_at"
69
+ commit.committed_at
54
70
  end
55
71
 
56
- private
57
- def sha1?(string)
58
- string =~ /^[a-f0-9]{40}$/
59
- end
72
+ def project_id
73
+ warn "Build#project_id is deprecated, use Commit#project_id"
74
+ commit.project_id
75
+ end
76
+
77
+ def commit_metadata
78
+ warn "Build#commit_metadata is deprecated, use the different methods in Commit instead"
79
+ { :message => commit.message,
80
+ :author => commit.author,
81
+ :date => commit.committed_at }
82
+ end
60
83
  end
61
84
  end
@@ -0,0 +1,71 @@
1
+ module Integrity
2
+ class Commit
3
+ include DataMapper::Resource
4
+
5
+ property :id, Integer, :serial => true
6
+ property :identifier, String, :nullable => false
7
+ property :message, String, :length => 255
8
+ property :author, Author, :length => 255
9
+ property :committed_at, DateTime
10
+ property :created_at, DateTime
11
+ property :updated_at, DateTime
12
+
13
+ has 1, :build, :class_name => "Integrity::Build", :order => [:created_at.desc]
14
+ belongs_to :project, :class_name => "Integrity::Project"
15
+
16
+ def message
17
+ attribute_get(:message) || "<Commit message not loaded>"
18
+ end
19
+
20
+ def author
21
+ attribute_get(:author) || Author.load('<Commit author not loaded> <<Commit author not loaded>>', :author)
22
+ end
23
+
24
+ def short_identifier
25
+ identifier.to_s[0..6]
26
+ end
27
+
28
+ def status
29
+ build.nil? ? :pending : build.status
30
+ end
31
+
32
+ def successful?
33
+ status == :success
34
+ end
35
+
36
+ def failed?
37
+ status == :failed
38
+ end
39
+
40
+ def pending?
41
+ status == :pending
42
+ end
43
+
44
+ def human_readable_status
45
+ case status
46
+ when :success; "Built #{short_identifier} successfully"
47
+ when :failed; "Built #{short_identifier} and failed"
48
+ when :pending; "#{short_identifier} hasn't been built yet"
49
+ end
50
+ end
51
+
52
+ def output
53
+ build && build.output
54
+ end
55
+
56
+ def queue_build
57
+ self.build = Build.create(:commit_id => id)
58
+ self.save
59
+
60
+ # Build on foreground (this will move away, I promise)
61
+ ProjectBuilder.new(project).build(self)
62
+ end
63
+
64
+ # Deprecation layer
65
+ alias :short_commit_identifier :short_identifier
66
+ alias :commit_identifier :identifier
67
+ alias :commit_author :author
68
+ alias :commit_message :message
69
+ alias :commited_at :committed_at
70
+ end
71
+ end
@@ -1,7 +1,7 @@
1
1
  Dir["#{File.dirname(__FILE__)}/helpers/*.rb"].each &method(:require)
2
2
 
3
3
  module Integrity
4
- module Helpers
4
+ module Helpers
5
5
  include Authorization
6
6
  include Breadcrumbs
7
7
  include Forms
@@ -9,8 +9,8 @@ module Integrity
9
9
  include Rendering
10
10
  include Resources
11
11
  include Urls
12
-
12
+
13
13
  include Rack::Utils
14
14
  alias :h :escape_html
15
15
  end
16
- end
16
+ end
@@ -1,4 +1,4 @@
1
- require "diddies"
1
+ require "sinatra/ditties"
2
2
 
3
3
  module Integrity
4
4
  module Helpers
@@ -30,4 +30,4 @@ module Integrity
30
30
  end
31
31
  end
32
32
  end
33
- end
33
+ end
@@ -12,8 +12,8 @@ module Integrity
12
12
 
13
13
  def checkbox(name, condition, extras={})
14
14
  attrs = { :name => name, :type => "checkbox", :value => "1" }
15
- attrs.merge!(:checked => condition ? true : nil)
16
- attrs.merge(extras)
15
+ attrs[:checked] = !!condition
16
+ attrs.update(extras)
17
17
  end
18
18
 
19
19
  def notifier_form(notifier)
@@ -25,4 +25,4 @@ module Integrity
25
25
  end
26
26
  end
27
27
  end
28
- end
28
+ end
@@ -42,4 +42,4 @@ module Integrity
42
42
  end
43
43
  end
44
44
  end
45
- end
45
+ end
@@ -1,6 +1,11 @@
1
1
  module Integrity
2
2
  module Helpers
3
3
  module Rendering
4
+ def stylesheet_hash
5
+ @_hash ||= Digest::MD5.file(
6
+ options.views + "/integrity.sass").tap { |file| file.hexdigest }
7
+ end
8
+
4
9
  def show(view, options={})
5
10
  @title = breadcrumbs(*options[:title])
6
11
  haml view
@@ -11,4 +16,4 @@ module Integrity
11
16
  end
12
17
  end
13
18
  end
14
- end
19
+ end
@@ -5,9 +5,15 @@ module Integrity
5
5
  @project ||= Project.first(:permalink => params[:project]) or raise Sinatra::NotFound
6
6
  end
7
7
 
8
- def current_build
9
- @build ||= current_project.builds.first(:commit_identifier => params[:build]) or raise Sinatra::NotFound
8
+ def current_commit
9
+ @commit ||= current_project.commits.first(:identifier => params[:commit]) or raise Sinatra::NotFound
10
+ end
11
+
12
+ def update_notifiers_of(project)
13
+ if params["notifiers"]
14
+ project.enable_notifiers(params["notifiers"].keys, params["notifiers"])
15
+ end
10
16
  end
11
17
  end
12
18
  end
13
- end
19
+ end
@@ -2,15 +2,7 @@ module Integrity
2
2
  module Helpers
3
3
  module Urls
4
4
  def url(path)
5
- url = "#{request.scheme}://#{request.host}"
6
-
7
- if request.scheme == "https" && request.port != 443 ||
8
- request.scheme == "http" && request.port != 80
9
- url << ":#{request.port}"
10
- end
11
-
12
- url << "/" unless path.index("/").zero?
13
- url << path
5
+ Addressable::URI.parse(request.url).join(path).to_s
14
6
  end
15
7
 
16
8
  def root_url
@@ -35,13 +27,23 @@ module Integrity
35
27
  end.to_s
36
28
  end
37
29
 
38
- def build_path(build)
39
- "/#{build.project.permalink}/builds/#{build.commit_identifier}"
30
+ def commit_path(commit, *path)
31
+ project_path(commit.project, "commits", commit.identifier, *path)
32
+ end
33
+
34
+ def build_path(build, *path)
35
+ warn "#build_path is deprecated, use #commit_path instead"
36
+ commit_path build.commit, *path
37
+ end
38
+
39
+ def commit_url(commit)
40
+ url commit_path(commit)
40
41
  end
41
42
 
42
43
  def build_url(build)
43
- url build_path(build)
44
+ warn "#build_url is deprecated, use #commit_url instead"
45
+ commit_url build.commit
44
46
  end
45
47
  end
46
48
  end
47
- end
49
+ end
@@ -6,63 +6,73 @@ module Integrity
6
6
  include FileUtils
7
7
 
8
8
  desc "install [PATH]",
9
- "Copy template files to PATH. Next, go there and edit them."
9
+ "Copy template files to PATH for desired deployement strategy
10
+ (either Thin or Passenger). Next, go there and edit them."
11
+ method_options :passenger => false, :thin => false
10
12
  def install(path)
11
- @root = File.expand_path(path)
13
+ @root = Pathname(path).expand_path
12
14
 
13
15
  create_dir_structure
14
16
  copy_template_files
15
17
  edit_template_files
16
- create_db(root / "config.yml")
18
+ migrate_db(root.join("config.yml"))
17
19
  after_setup_message
18
20
  end
19
21
 
20
- desc "create_db [CONFIG]",
21
- "Checks the `database_uri` in CONFIG and creates and bootstraps a database for integrity"
22
- def create_db(config, direction="up")
22
+ desc "migrate_db [CONFIG]",
23
+ "Checks the `database_uri` in CONFIG and migrates the
24
+ database up to the lastest version."
25
+ def migrate_db(config)
23
26
  Integrity.new(config)
24
- migrate_db(direction, 1)
25
- end
26
27
 
27
- desc "version",
28
- "Print the current integrity version"
29
- def version
30
- puts Integrity.version
28
+ require "integrity/migrations"
29
+ Integrity.migrate_db
31
30
  end
32
31
 
33
- private
34
- attr_reader :root
32
+ desc "launch [CONFIG]",
33
+ "Launch Integrity real quick."
34
+ method_options :config => :optional, :port => 4567
35
+ def launch
36
+ require "thin"
37
+ require "do_sqlite3"
38
+
39
+ if File.file?(options[:config].to_s)
40
+ Integrity.new(options[:config])
41
+ else
42
+ DataMapper.setup(:default, "sqlite3::memory:")
43
+ end
35
44
 
36
- def migrate_db(direction, level=nil)
37
- require 'migrations'
45
+ DataMapper.auto_migrate!
38
46
 
39
- set_up_migrations unless migrations_already_set_up?
40
- add_initial_migration if tables_from_before_migrations_exist?
47
+ Thin::Server.start("0.0.0.0", options[:port], Integrity::App)
48
+ rescue LoadError => boom
49
+ missing_dependency = boom.message.split("--").last.lstrip
50
+ puts "Please install #{missing_dependency} to launch Integrity"
51
+ end
41
52
 
42
- case direction
43
- when "up" then Integrity::Migrations.migrate_up!(level)
44
- when "down" then Integrity::Migrations.migrate_down!(level)
45
- else raise ArgumentError, "DIRECTION must be either up or down"
46
- end
47
- end
53
+ private
54
+ attr_reader :root
48
55
 
49
56
  def create_dir_structure
50
57
  mkdir_p root
51
58
  mkdir_p root / "builds"
52
59
  mkdir_p root / "log"
53
- mkdir_p root / "public" # this one is to play nice with Passenger
54
- mkdir_p root / "tmp" # this one is to play nice with Passenger
60
+
61
+ if options[:passenger]
62
+ mkdir_p root / "public"
63
+ mkdir_p root / "tmp"
64
+ end
55
65
  end
56
66
 
57
67
  def copy_template_files
58
- cp Integrity.root / "config" / "config.sample.ru", root / "config.ru"
59
- cp Integrity.root / "config" / "config.sample.yml", root / "config.yml"
60
- cp Integrity.root / "config" / "thin.sample.yml", root / "thin.yml"
68
+ copy "config/config.sample.ru"
69
+ copy "config/config.sample.yml"
70
+ copy "config/thin.sample.yml" if options[:thin]
61
71
  end
62
72
 
63
73
  def edit_template_files
64
74
  edit_integrity_configuration
65
- edit_thin_configuration
75
+ edit_thin_configuration if options[:thin]
66
76
  end
67
77
 
68
78
  def edit_integrity_configuration
@@ -97,36 +107,9 @@ module Integrity
97
107
  puts %Q(Don't forget to tweak #{root / "config.yml"} to your needs.)
98
108
  end
99
109
 
100
- def set_up_migrations
101
- database_adapter.execute %q(CREATE TABLE "migration_info" ("migration_name" VARCHAR(255));)
102
- end
103
-
104
- def add_initial_migration
105
- database_adapter.execute %q(INSERT INTO "migration_info" ("migration_name") VALUES ("initial"))
106
- end
107
-
108
- def tables_from_before_migrations_exist?
109
- table_exists?("integrity_projects") &&
110
- table_exists?("integrity_builds") &&
111
- table_exists?("integrity_notifiers")
112
- end
113
-
114
- def migrations_already_set_up?
115
- table_exists?("migration_info")
116
- end
117
-
118
- def without_pluralizing_table_names
119
- database_adapter.resource_naming_convention = DataMapper::NamingConventions::Resource::Underscored
120
- yield
121
- database_adapter.resource_naming_convention = DataMapper::NamingConventions::Resource::UnderscoredAndPluralized
122
- end
123
-
124
- def table_exists?(table_name)
125
- database_adapter.storage_exists?(table_name)
126
- end
127
-
128
- def database_adapter
129
- DataMapper.repository(:default).adapter
110
+ def copy(path)
111
+ cp(File.dirname(__FILE__) + "/../../#{path}",
112
+ root.join(File.basename(path).gsub(/\.sample/, "")))
130
113
  end
131
114
  end
132
115
  end