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
data/README.markdown CHANGED
@@ -8,6 +8,13 @@ Integrity
8
8
  * Join us on [#integrity][irc-channel] for ideas, help, patches or something
9
9
  * Get the code on [GitHub][repo]
10
10
 
11
+ Try it!
12
+ -------
13
+
14
+ $ git clone git://github.com/foca/integrity.git
15
+ $ rake launch
16
+ # Navigate to <http://0.0.0.0:4567>
17
+
11
18
  Thanks
12
19
  ------
13
20
 
data/Rakefile CHANGED
@@ -1,13 +1,38 @@
1
- require File.dirname(__FILE__) + "/lib/integrity"
2
1
  require "rake/testtask"
2
+ require "rake/clean"
3
3
  require "rcov/rcovtask"
4
4
 
5
- desc "Run all tests and check test coverage"
6
- task :default => "test:coverage:verify"
5
+ begin
6
+ require "metric_fu"
7
+ rescue LoadError
8
+ end
9
+
10
+ desc "Default: run all tests"
11
+ task :default => :test
12
+
13
+ desc "Special task for running tests on <http://builder.integrityapp.com>"
14
+ task :ci do
15
+ sh "git submodule update --init"
16
+
17
+ Rake::Task["test"].invoke
18
+
19
+ metrics = %w(flay flog:all reek roodi saikuro)
20
+ metrics.each { |m| Rake::Task["metrics:#{m}"].invoke }
21
+
22
+ rm_rf "/var/www/integrity-metrics"
23
+ mv "tmp/metric_fu", "/var/www/integrity-metrics"
24
+
25
+ File.open("/var/www/integrity-metrics/index.html", "w") { |f|
26
+ f << "<ul>"
27
+ metrics.map { |m| m.split(":").first }.each { |m|
28
+ f << %Q(<li><a href="/#{m}">#{m}</a></li>)
29
+ }
30
+ f << "</ul>"
31
+ }
32
+ end
7
33
 
8
34
  desc "Run tests"
9
35
  task :test => %w(test:units test:acceptance)
10
-
11
36
  namespace :test do
12
37
  Rake::TestTask.new(:units) do |t|
13
38
  t.test_files = FileList["test/unit/*_test.rb"]
@@ -17,94 +42,77 @@ namespace :test do
17
42
  t.test_files = FileList["test/acceptance/*_test.rb"]
18
43
  end
19
44
 
20
- desc "Measure test coverage"
21
- task :coverage => %w(test:coverage:units test:coverage:acceptance)
45
+ desc "Install all gems on which the tests depend on"
46
+ task :install_dependencies do
47
+ system "gem install rr mocha dm-sweatshop ZenTest"
48
+ system "gem install -s http://gems.github.com jeremymcanally-context \
49
+ jeremymcanally-matchy jeremymcanally-pending foca-storyteller"
50
+ system "git submodule update --init"
51
+ end
52
+ end
22
53
 
23
- namespace :coverage do
24
- desc "Measure test coverage of unit tests"
25
- Rcov::RcovTask.new(:units) do |rcov|
26
- rcov.pattern = "test/unit/*_test.rb"
27
- rcov.rcov_opts = %w(--html --aggregate .aggregated_coverage_report)
28
- rcov.rcov_opts << ENV["RCOV_OPTS"] if ENV["RCOV_OPTS"]
29
- end
54
+ desc "Launch Integrity real quick"
55
+ task :launch do
56
+ ruby "bin/integrity launch"
57
+ end
30
58
 
31
- desc "Measure test coverage of acceptance tests"
32
- Rcov::RcovTask.new(:acceptance) do |rcov|
33
- rcov.pattern = "test/acceptance/*_test.rb"
34
- rcov.rcov_opts = %w(--html --aggregate .aggregated_coverage_report)
35
- rcov.rcov_opts << ENV["RCOV_OPTS"] if ENV["RCOV_OPTS"]
59
+ directory "dist/"
60
+ CLOBBER.include("dist")
61
+
62
+ # Load the gemspec using the same limitations as github
63
+ def spec
64
+ @spec ||=
65
+ begin
66
+ require "rubygems/specification"
67
+ data = File.read("integrity.gemspec")
68
+ spec = nil
69
+ Thread.new { spec = eval("$SAFE = 3\n#{data}") }.join
70
+ spec
36
71
  end
72
+ end
37
73
 
38
- desc "Verify test coverage"
39
- task :verify => "test:coverage" do
40
- File.read("coverage/index.html") =~ /<tt class='coverage_total'>\s*(\d+\.\d+)%\s*<\/tt>/
41
- coverage = $1.to_f
42
-
43
- puts
44
- if coverage == 100
45
- puts "\e[32m100% coverage! Awesome!\e[0m"
46
- else
47
- puts "\e[31mOnly #{coverage}% code coverage. You can do better ;)\e[0m"
48
- end
49
- end
50
- end
74
+ def package(ext="")
75
+ "dist/integrity-#{spec.version}" + ext
76
+ end
51
77
 
52
- desc "Install all gems on which the tests depend on"
53
- task :install_dependencies do
54
- system 'gem install redgreen rr mocha ruby-debug dm-sweatshop webrat'
55
- system 'gem install -s http://gems.github.com jeremymcanally-context jeremymcanally-matchy jeremymcanally-pending foca-storyteller'
56
- end
78
+ desc "Build and install as local gem"
79
+ task :install => package('.gem') do
80
+ sh "gem install #{package('.gem')}"
57
81
  end
58
82
 
59
- namespace :db do
60
- desc "Setup connection."
61
- task :connect do
62
- config = File.expand_path(ENV['CONFIG']) if ENV['CONFIG']
63
- config = Integrity.root / 'config.yml' if File.exists?(Integrity.root / 'config.yml')
64
- Integrity.new(config)
83
+ desc "Publish the current release on Rubyforge"
84
+ task :rubyforge => ["rubyforge:gem", "rubyforge:tarball", "rubyforge:git"]
85
+
86
+ namespace :rubyforge do
87
+ desc "Publish gem and tarball to rubyforge"
88
+ task :gem => package(".gem") do
89
+ sh "rubyforge add_release integrity integrity #{spec.version} #{package('.gem')}"
65
90
  end
66
91
 
67
- desc "Automigrate the database"
68
- task :migrate => :connect do
69
- require "project"
70
- require "build"
71
- require "notifier"
72
- DataMapper.auto_migrate!
92
+ task :tarball => package(".tar.gz") do
93
+ sh "rubyforge add_file integrity integrity #{spec.version} #{package('.tar.gz')}"
73
94
  end
74
- end
75
95
 
76
- begin
77
- require 'jeweler'
78
- Jeweler::Tasks.new do |s|
79
- files = `git ls-files`.split("\n").reject {|f| f =~ %r(^test/acceptance) || f =~ %r(^test/unit) || f =~ /^\.git/ }
80
-
81
- s.name = 'integrity'
82
- s.summary = 'The easy and fun Continuous Integration server'
83
- s.description = 'Your Friendly Continuous Integration server. Easy, fun and painless!'
84
- s.homepage = 'http://integrityapp.com'
85
- s.rubyforge_project = 'integrity'
86
- s.email = 'contacto@nicolassanguinetti.info'
87
- s.authors = ['Nicolás Sanguinetti', 'Simon Rozet']
88
- s.files = files
89
- s.executables = ['integrity']
90
- s.post_install_message = 'Run `integrity help` for information on how to setup Integrity.'
91
-
92
- s.add_dependency 'sinatra', ['>= 0.9.0.3']
93
- s.add_dependency 'haml' # ah, you evil monkey you
94
- s.add_dependency 'dm-core', ['>= 0.9.5']
95
- s.add_dependency 'dm-validations', ['>= 0.9.5']
96
- s.add_dependency 'dm-types', ['>= 0.9.5']
97
- s.add_dependency 'dm-timestamps', ['>= 0.9.5']
98
- s.add_dependency 'dm-aggregates', ['>= 0.9.5']
99
- s.add_dependency 'dm-migrations', ['>= 0.9.5']
100
- s.add_dependency 'data_objects', ['>= 0.9.5']
101
- s.add_dependency 'do_sqlite3', ['>= 0.9.5']
102
- s.add_dependency 'json'
103
- s.add_dependency 'foca-sinatra-diddies', ['>= 0.0.2']
104
- s.add_dependency 'thor'
105
- s.add_dependency 'uuidtools' # required by dm-types
106
- s.add_dependency 'bcrypt-ruby' # required by dm-types
96
+ desc "Push to gitosis@rubyforge.org:integrity.git"
97
+ task :git do
98
+ sh "git push gitosis@rubyforge.org:integrity.git master"
107
99
  end
108
- rescue LoadError
109
100
  end
110
101
 
102
+ desc "Build gem tarball into dist/"
103
+ task :package => %w(.gem .tar.gz).map { |ext| package(ext) }
104
+ namespace :package do
105
+ file package(".tar.gz") => "dist/" do |f|
106
+ sh <<-SH
107
+ git archive \
108
+ --prefix=integrity-#{spec.version}/ \
109
+ --format=tar \
110
+ HEAD | gzip > #{f.name}
111
+ SH
112
+ end
113
+
114
+ file package(".gem") => "dist/" do |f|
115
+ sh "gem build integrity.gemspec"
116
+ mv File.basename(f.name), f.name
117
+ end
118
+ end
@@ -1,31 +1,21 @@
1
1
  #!/usr/bin/env ruby
2
- require 'rubygems'
3
- require 'integrity'
2
+ require "rubygems"
3
+ require "integrity"
4
4
 
5
5
  # If you want to add any notifiers, install the gems and then require them here
6
- # For example, to enable the Email notifier: install the gem (from github:
6
+ # For example, to enable the Email notifier: install the gem (from github:
7
7
  #
8
8
  # sudo gem install -s http://gems.github.com foca-integrity-email
9
- #
9
+ #
10
10
  # And then uncomment the following line:
11
- #
11
+ #
12
12
  # require "notifier/email"
13
13
 
14
- # Load integrity's configuration.
15
- Integrity.config = File.expand_path('./config.yml')
16
-
17
- #######################################################################
18
- ## ##
19
- ## == DON'T EDIT ANYTHING BELOW UNLESS YOU KNOW WHAT YOU'RE DOING == ##
20
- ## ##
21
- #######################################################################
22
- require Integrity.root / 'app'
14
+ # Load configuration and initialize Integrity
15
+ Integrity.new(File.dirname(__FILE__) + "/config.yml")
23
16
 
24
- set :public, Integrity.root / 'public'
25
- set :views, Integrity.root / 'views'
26
- set :port, 8910
27
- set :env, :production
28
- disable :run, :reload
17
+ # You probably don't want to edit anything below
18
+ Integrity::App.set :environment, ENV["RACK_ENV"] || :production
19
+ Integrity::App.set :port, 8910
29
20
 
30
- use Rack::CommonLogger, Integrity.logger if Integrity.config[:log_debug_info]
31
- run Sinatra::Application
21
+ run Integrity::App
@@ -4,24 +4,27 @@
4
4
  # http://builder.integrityapp.com
5
5
  :base_uri: http://integrity.domain.tld
6
6
 
7
- # This should be a complete connection string to your database. For example
8
- # `mysql://user@localhost/integrity` (you need an `integrity` db created in
9
- # localhost, of course).
7
+ # This should be a complete connection string to your database.
8
+ #
9
+ # Examples:
10
+ # * `mysql://user:password@localhost/integrity`
11
+ # * `postgres://user:password@localhost/integrity`
12
+ # * `sqlite3:///home/integrity/db/integrity.sqlite`
13
+ #
14
+ # Note:
15
+ # * The appropriate data_objects adapter must be installed (`do_mysql`, etc)
16
+ # * You must create the `integrity` database on localhost, of course.
10
17
  :database_uri: sqlite3:///var/integrity.db
11
18
 
12
- # This is where your project's code will be checked out to. Make sure it's
19
+ # This is where your project's code will be checked out to. Make sure it's
13
20
  # writable by the user that runs Integrity.
14
21
  :export_directory: /path/to/scm/exports
15
22
 
16
23
  # Path to the integrity log file
17
24
  :log: /var/log/integrity.log
18
25
 
19
- # Enable/Disable debug logging. Turning this on will log queries made to the
20
- # database and web requests (if using the provided rackup file)
21
- :log_debug_info: false
22
-
23
- # Enable or disable HTTP authentication for the app. BE AWARE that if you
24
- # disable this anyone can delete and alter projects, so do it only if your
26
+ # Enable or disable HTTP authentication for the app. BE AWARE that if you
27
+ # disable this anyone can delete and alter projects, so do it only if your
25
28
  # app is running in a controlled environment (ie, behind your company's
26
29
  # firewall.)
27
30
  :use_basic_auth: false
@@ -29,10 +32,10 @@
29
32
  # When `use_basic_auth` is true, the admin's username for HTTP authentication.
30
33
  :admin_username: username
31
34
 
32
- # When `use_basic_auth` is true, the admin's password. Usually saved as a
35
+ # When `use_basic_auth` is true, the admin's password. Usually saved as a
33
36
  # SHA1 hash. See the next option.
34
37
  :admin_password: a94a8fe5ccb19ba61c4c0873d391e987982fbbd3
35
38
 
36
- # If this is true, then whenever we authenticate the admin user, will hash
39
+ # If this is true, then whenever we authenticate the admin user, will hash
37
40
  # it using SHA1. If not, we'll assume the provided password is in plain text.
38
41
  :hash_admin_password: true
data/lib/integrity.rb CHANGED
@@ -1,40 +1,41 @@
1
- __DIR__ = File.dirname(__FILE__)
2
- $:.unshift "#{__DIR__}/integrity", *Dir["#{__DIR__}/../vendor/**/lib"].to_a
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
3
2
 
4
- require "rubygems"
5
3
  require "json"
6
4
  require "dm-core"
7
5
  require "dm-validations"
8
6
  require "dm-types"
9
7
  require "dm-timestamps"
10
8
  require "dm-aggregates"
9
+ require "sinatra/base"
11
10
 
12
11
  require "yaml"
13
12
  require "logger"
14
13
  require "digest/sha1"
15
14
  require "timeout"
16
15
  require "ostruct"
17
- require "fileutils"
16
+ require "pathname"
18
17
 
19
- require "core_ext/object"
20
- require "core_ext/string"
18
+ require "integrity/core_ext/object"
21
19
 
22
- require "project"
23
- require "build"
24
- require "project_builder"
25
- require "scm"
26
- require "scm/git"
27
- require "notifier"
20
+ require "integrity/project"
21
+ require "integrity/author"
22
+ require "integrity/commit"
23
+ require "integrity/build"
24
+ require "integrity/project_builder"
25
+ require "integrity/scm"
26
+ require "integrity/scm/git"
27
+ require "integrity/notifier"
28
+ require "integrity/helpers"
29
+ require "integrity/app"
28
30
 
29
31
  module Integrity
30
32
  def self.new(config_file = nil)
31
- self.config = config_file unless config_file.nil?
32
- DataMapper.logger = self.logger if config[:log_debug_info]
33
+ self.config = YAML.load_file(config_file) unless config_file.nil?
33
34
  DataMapper.setup(:default, config[:database_uri])
34
35
  end
35
36
 
36
37
  def self.root
37
- File.expand_path(File.join(File.dirname(__FILE__), ".."))
38
+ Pathname.new(File.dirname(__FILE__)).join("..").expand_path
38
39
  end
39
40
 
40
41
  def self.default_configuration
@@ -44,15 +45,15 @@ module Integrity
44
45
  :base_uri => "http://localhost:8910",
45
46
  :use_basic_auth => false,
46
47
  :build_all_commits => true,
47
- :log_debug_info => false }
48
+ :log_debug_info => false }.dup
48
49
  end
49
50
 
50
51
  def self.config
51
52
  @config ||= default_configuration
52
53
  end
53
54
 
54
- def self.config=(file)
55
- @config = default_configuration.merge(YAML.load_file(file))
55
+ def self.config=(options)
56
+ @config = default_configuration.merge(options)
56
57
  end
57
58
 
58
59
  def self.log(message, &block)
@@ -66,14 +67,11 @@ module Integrity
66
67
  end
67
68
 
68
69
  def self.version
69
- @version ||= begin
70
- file = YAML.load_file(Integrity.root / "VERSION.yml")
71
- "#{file['major']}.#{file['minor']}.#{file['patch']}"
72
- end
70
+ YAML.load_file(File.dirname(__FILE__) + "/../VERSION.yml").
71
+ values.join(".")
73
72
  end
74
73
 
75
74
  private
76
-
77
75
  class LogFormatter < Logger::Formatter
78
76
  def call(severity, time, progname, msg)
79
77
  time.strftime("[%H:%M:%S] ") + msg2str(msg) + "\n"
@@ -0,0 +1,138 @@
1
+ module Integrity
2
+ class App < Sinatra::Default
3
+ set :root, File.dirname(__FILE__) + "/../.."
4
+ set :app_file, __FILE__
5
+ enable :sessions
6
+
7
+ include Integrity
8
+ include Integrity::Helpers
9
+
10
+ not_found do
11
+ status 404
12
+ show :not_found, :title => "lost, are we?"
13
+ end
14
+
15
+ error do
16
+ @error = request.env["sinatra.error"]
17
+ status 500
18
+ show :error, :title => "something has gone terribly wrong"
19
+ end
20
+
21
+ before do
22
+ # The browser only sends http auth data for requests that are explicitly
23
+ # required to do so. This way we get the real values of +#logged_in?+ and
24
+ # +#current_user+
25
+ login_required if session[:user]
26
+ end
27
+
28
+ get "/integrity.css" do
29
+ response["Content-Type"] = "text/css; charset=utf-8"
30
+ etag stylesheet_hash
31
+ sass :integrity
32
+ end
33
+
34
+ get "/" do
35
+ @projects = Project.only_public_unless(authorized?)
36
+ show :home, :title => "projects"
37
+ end
38
+
39
+ get "/login" do
40
+ login_required
41
+
42
+ session[:user] = current_user
43
+ redirect root_url
44
+ end
45
+
46
+ get "/new" do
47
+ login_required
48
+
49
+ @project = Project.new
50
+ show :new, :title => ["projects", "new project"]
51
+ end
52
+
53
+ post "/" do
54
+ login_required
55
+
56
+ @project = Project.new(params[:project_data])
57
+
58
+ if @project.save
59
+ update_notifiers_of(@project)
60
+ redirect project_url(@project)
61
+ else
62
+ show :new, :title => ["projects", "new project"]
63
+ end
64
+ end
65
+
66
+ get "/:project.atom" do
67
+ login_required unless current_project.public?
68
+ response["Content-Type"] = "application/rss+xml; charset=utf-8"
69
+ builder :project
70
+ end
71
+
72
+ get "/:project" do
73
+ login_required unless current_project.public?
74
+ show :project, :title => ["projects", current_project.name]
75
+ end
76
+
77
+ put "/:project" do
78
+ login_required
79
+
80
+ if current_project.update_attributes(params[:project_data])
81
+ update_notifiers_of(current_project)
82
+ redirect project_url(current_project)
83
+ else
84
+ show :new, :title => ["projects", current_project.permalink, "edit"]
85
+ end
86
+ end
87
+
88
+ delete "/:project" do
89
+ login_required
90
+
91
+ current_project.destroy
92
+ redirect root_url
93
+ end
94
+
95
+ get "/:project/edit" do
96
+ login_required
97
+
98
+ show :new, :title => ["projects", current_project.permalink, "edit"]
99
+ end
100
+
101
+ post "/:project/push" do
102
+ login_required
103
+
104
+ content_type "text/plain"
105
+
106
+ begin
107
+ current_project.push(params[:payload])
108
+ 201
109
+ rescue ArgumentError
110
+ [422, "Invalid Request"]
111
+ end
112
+ end
113
+
114
+ post "/:project/builds" do
115
+ login_required
116
+
117
+ current_project.build
118
+ redirect project_url(current_project)
119
+ end
120
+
121
+ get "/:project/commits/:commit" do
122
+ login_required unless current_project.public?
123
+
124
+ show :build, :title => ["projects", current_project.permalink, current_commit.short_commit_identifier]
125
+ end
126
+
127
+ get "/:project/builds/:commit" do
128
+ redirect "/#{params[:project]}/commits/#{params[:commit]}", 301
129
+ end
130
+
131
+ post "/:project/commits/:commit/builds" do
132
+ login_required
133
+
134
+ current_project.build(params[:commit])
135
+ redirect commit_url(current_commit)
136
+ end
137
+ end
138
+ end