diff options
author | Dmitriy Zaporozhets <dzaporozhets@gitlab.com> | 2014-11-05 09:51:39 +0000 |
---|---|---|
committer | Dmitriy Zaporozhets <dzaporozhets@gitlab.com> | 2014-11-05 09:51:39 +0000 |
commit | 3559e3906a005a71183032bc06cf7538fc9d7d05 (patch) | |
tree | 5cd5f8f194ef03e10cac10cd7860c86aff5939bb | |
parent | f18299e45279ce2ffdaee22482e30e6f37c1f5b6 (diff) | |
parent | e1ea394072b9358d77a02a021cc79a37ddee1409 (diff) | |
download | gitlab-ci-3559e3906a005a71183032bc06cf7538fc9d7d05.tar.gz |
Merge branch 'parallel-builds' into 'master'
Parallel builds
This merge contains next changes:
* Replace project scripts textarea with jobs
* Create multiple builds on push if there are multiple jobs
* Change UI hierarchy to next chain: project -> commit -> builds
* Requires GitLab 7.5.0pre or higher
See merge request !83
56 files changed, 697 insertions, 185 deletions
@@ -12,6 +12,7 @@ gem 'rails', '4.0.10' gem 'protected_attributes' gem 'activerecord-deprecated_finders' gem 'activerecord-session_store' +gem "nested_form" # DB gem 'mysql2', group: :mysql @@ -98,4 +99,5 @@ end group :test do gem 'webmock' + gem 'email_spec' end diff --git a/Gemfile.lock b/Gemfile.lock index f0b701b..fb05eb7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -74,6 +74,9 @@ GEM diff-lcs (1.2.5) docile (1.1.1) dotenv (0.9.0) + email_spec (1.5.0) + launchy (~> 2.1) + mail (~> 2.2) equalizer (0.0.9) erubis (2.7.0) execjs (2.0.2) @@ -158,6 +161,7 @@ GEM multi_json (1.10.1) multi_xml (0.5.5) mysql2 (0.3.14) + nested_form (0.3.2) nokogiri (1.6.0) mini_portile (~> 0.5.0) nprogress-rails (0.1.2.3) @@ -312,6 +316,7 @@ DEPENDENCIES capybara coffee-rails (~> 4.0.0) coveralls + email_spec factory_girl_rails ffaker font-awesome-rails (~> 3.2) @@ -329,6 +334,7 @@ DEPENDENCIES letter_opener minitest (= 4.3.2) mysql2 + nested_form nprogress-rails pg poltergeist diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index d27aacc..3908860 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -17,6 +17,7 @@ #= require jquery.turbolinks #= require nprogress #= require nprogress-turbolinks +#= require jquery_nested_form #= require_tree . # # diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index 4ea7dbd..bad6fd5 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -23,3 +23,25 @@ vertical-align: middle !important; } } + +.commit-info { + font-size: 14px; + + .attr-name { + font-weight: 300; + color: #666; + margin-right: 5px; + } + + pre.commit-message { + font-size: 14px; + background: none; + padding: 0; + margin: 0; + border: none; + margin: 20px 0; + border-bottom: 1px solid #EEE; + padding-bottom: 20px; + border-radius: 0; + } +} diff --git a/app/controllers/builds_controller.rb b/app/controllers/builds_controller.rb index a0eed73..243aaae 100644 --- a/app/controllers/builds_controller.rb +++ b/app/controllers/builds_controller.rb @@ -2,18 +2,18 @@ class BuildsController < ApplicationController before_filter :authenticate_user!, except: [:status] before_filter :project before_filter :authorize_access_project!, except: [:status] - before_filter :build, except: [:status, :show] + before_filter :build, except: [:show] def show if params[:id] =~ /\A\d+\Z/ @build = build else - # try to find build by sha - build = build_by_sha + # try to find commit by sha + commit = commit_by_sha - if build - # Redirect from sha to build with id - redirect_to project_build_path(build.project, build) + if commit + # Redirect to commit page + redirect_to project_commit_path(commit.project, commit) return end end @@ -38,8 +38,6 @@ class BuildsController < ApplicationController end def status - @build = build_by_sha - render json: @build.to_json(only: [:status, :id, :sha, :coverage]) end @@ -59,7 +57,7 @@ class BuildsController < ApplicationController @build ||= project.builds.unscoped.find_by(id: params[:id]) end - def build_by_sha - @project.commits.find_by(sha: params[:id]).try(:last_build) + def commit_by_sha + @project.commits.find_by(sha: params[:id]) end end diff --git a/app/controllers/commits_controller.rb b/app/controllers/commits_controller.rb new file mode 100644 index 0000000..929841e --- /dev/null +++ b/app/controllers/commits_controller.rb @@ -0,0 +1,24 @@ +class CommitsController < ApplicationController + before_filter :authenticate_user!, except: [:status] + before_filter :project + before_filter :commit + before_filter :authorize_access_project!, except: [:status] + + def show + @builds = @commit.builds + end + + def status + render json: @commit.to_json(only: [:id, :sha], methods: [:status, :coverage]) + end + + private + + def project + @project ||= Project.find(params[:project_id]) + end + + def commit + @commit ||= project.commits.find_by(sha: params[:id]) + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7a6a8e0..f21578f 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -36,9 +36,9 @@ class ProjectsController < ApplicationController @ref = params[:ref] - @builds = @project.builds - @builds = @builds.where(ref: @ref) if @ref - @builds = @builds.order('id DESC').page(params[:page]).per(20) + @commits = @project.commits + @commits = @commits.where(ref: @ref) if @ref + @commits = @commits.order('id DESC').page(params[:page]).per(20) end def integration @@ -73,9 +73,9 @@ class ProjectsController < ApplicationController end def build - @build = CreateBuildService.new.execute(@project, params.dup) + @builds = CreateBuildsService.new.execute(@project, params.dup) - if @build && @build.persisted? + if @builds.any? && @builds.any?(&:persisted?) head 201 else head 400 diff --git a/app/helpers/builds_helper.rb b/app/helpers/builds_helper.rb index 0c1e264..4182675 100644 --- a/app/helpers/builds_helper.rb +++ b/app/helpers/builds_helper.rb @@ -28,4 +28,14 @@ module BuildsHelper def build_url(build) project_build_url(build.project, build) end + + def build_status_alert_class(build) + if build.success? + 'alert-success' + elsif build.failed? || build.canceled? + 'alert-danger' + else + 'alert-warning' + end + end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb new file mode 100644 index 0000000..a79f729 --- /dev/null +++ b/app/helpers/commits_helper.rb @@ -0,0 +1,12 @@ +module CommitsHelper + def commit_status_alert_class(commit) + case commit.status + when 'success' + 'alert-success' + when 'failed', 'canceled' + 'alert-danger' + else + 'alert-warning' + end + end +end diff --git a/app/helpers/gitlab_helper.rb b/app/helpers/gitlab_helper.rb index a8cac14..a5d3192 100644 --- a/app/helpers/gitlab_helper.rb +++ b/app/helpers/gitlab_helper.rb @@ -15,6 +15,6 @@ module GitlabHelper def gitlab_commit_link project, sha gitlab_url = project.gitlab_url.dup gitlab_url << "/commit/#{sha}" - link_to sha, gitlab_url + link_to sha[0...10], gitlab_url end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index d8d9e39..ebc06c3 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -9,16 +9,6 @@ module ProjectsHelper end end - def build_status_alert_class build - if build.success? - 'alert-success' - elsif build.failed? || build.canceled? - 'alert-danger' - else - 'alert-warning' - end - end - def ref_tab_class ref = nil 'active' if ref == @ref end diff --git a/app/models/build.rb b/app/models/build.rb index 968ac03..7486492 100644 --- a/app/models/build.rb +++ b/app/models/build.rb @@ -3,15 +3,23 @@ # Table name: builds # # id :integer not null, primary key +# project_id :integer +# ref :string(255) # status :string(255) # finished_at :datetime # trace :text # created_at :datetime # updated_at :datetime +# sha :string(255) # started_at :datetime # tmp_file :string(255) +# before_sha :string(255) +# push_data :text # runner_id :integer +# coverage :float # commit_id :integer +# commands :text +# job_id :integer # class Build < ActiveRecord::Base @@ -19,8 +27,10 @@ class Build < ActiveRecord::Base belongs_to :commit belongs_to :runner + belongs_to :job - attr_accessible :status, :finished_at, :trace, :started_at, :runner_id, :commit_id, :coverage + attr_accessible :status, :finished_at, :trace, :started_at, :runner_id, + :commit_id, :coverage, :commands, :job_id validates :commit, presence: true validates :status, presence: true @@ -56,7 +66,12 @@ class Build < ActiveRecord::Base end def self.retry(build) - Build.create(commit_id: build.commit_id) + Build.create( + commit_id: build.commit_id, + job_id: build.job_id, + status: :pending, + commands: build.commands + ) end state_machine :status, initial: :pending do @@ -123,10 +138,6 @@ class Build < ActiveRecord::Base canceled? || success? || failed? end - def commands - project.scripts - end - def timeout project.timeout end diff --git a/app/models/commit.rb b/app/models/commit.rb index 665f136..4abe0da 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -2,25 +2,30 @@ # # Table name: commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime # class Commit < ActiveRecord::Base belongs_to :project has_many :builds + has_many :jobs, through: :builds serialize :push_data validates_presence_of :ref, :sha, :before_sha, :push_data validate :valid_commit_sha + def to_param + sha + end + def last_build builds.last end @@ -94,4 +99,72 @@ class Commit < ActiveRecord::Base recipients << git_author_email if project.email_add_committer? recipients.uniq end + + def create_builds + project.jobs.active.map do |job| + build = builds.new(commands: job.commands) + build.job = job + build.save + build + end + end + + def builds_without_retry + @builds_without_retry ||= + begin + grouped_builds = builds.group_by(&:job) + grouped_builds.map do |job, builds| + builds.sort_by(&:id).last + end + end + end + + def status + if success? + 'success' + elsif pending? + 'pending' + elsif running? + 'running' + else + 'failed' + end + end + + def pending? + builds_without_retry.all? do |build| + build.pending? + end + end + + def running? + builds_without_retry.any? do |build| + build.running? || build.pending? + end + end + + def success? + builds_without_retry.all? do |build| + build.success? + end + end + + def failed? + status == 'failed' + end + + def canceled? + end + + def duration + end + + def finished_at + end + + def coverage + if builds.size == 1 + builds.first.coverage + end + end end diff --git a/app/models/job.rb b/app/models/job.rb new file mode 100644 index 0000000..dcda159 --- /dev/null +++ b/app/models/job.rb @@ -0,0 +1,20 @@ +# == Schema Information +# +# Table name: jobs +# +# id :integer not null, primary key +# project_id :integer not null +# commands :text +# active :boolean default(TRUE), not null +# created_at :datetime +# updated_at :datetime +# name :string(255) +# + +class Job < ActiveRecord::Base + belongs_to :project + has_many :builds + + scope :active, ->() { where(active: true) } + scope :archived, ->() { where(active: false) } +end diff --git a/app/models/project.rb b/app/models/project.rb index a9dc3ac..314f7e8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -20,24 +20,31 @@ # email_recipients :string(255) default(""), not null # email_add_committer :boolean default(TRUE), not null # email_only_broken_builds :boolean default(TRUE), not null +# skip_refs :string(255) +# coverage_regex :string(255) # class Project < ActiveRecord::Base attr_accessible :name, :path, :scripts, :timeout, :token, :timeout_in_minutes, :default_ref, :gitlab_url, :always_build, :polling_interval, :public, :ssh_url_to_repo, :gitlab_id, :allow_git_fetch, :skip_refs, - :email_recipients, :email_add_committer, :email_only_broken_builds, :coverage_regex + :email_recipients, :email_add_committer, :email_only_broken_builds, :coverage_regex, + :jobs_attributes has_many :commits, dependent: :destroy has_many :builds, through: :commits, dependent: :destroy has_many :runner_projects, dependent: :destroy has_many :runners, through: :runner_projects has_many :web_hooks, dependent: :destroy + has_many :jobs, dependent: :destroy + + accepts_nested_attributes_for :jobs, allow_destroy: true # # Validations # - validates_presence_of :name, :scripts, :timeout, :token, :default_ref, :gitlab_url, :ssh_url_to_repo, :gitlab_id + validates_presence_of :name, :scripts, :timeout, :token, :default_ref, + :gitlab_url, :ssh_url_to_repo, :gitlab_id validates_uniqueness_of :name @@ -45,6 +52,7 @@ class Project < ActiveRecord::Base presence: true, if: ->(project) { project.always_build.present? } + validate :validate_jobs scope :public_only, ->() { where(public: true) } @@ -125,7 +133,7 @@ ls -la end def last_build - @last_build ||= commits.last.last_build + @last_build ||= commits.last.last_build if commits.any? end def last_build_date @@ -194,4 +202,16 @@ ls -la def coverage_enabled? coverage_regex.present? end + + def build_default_job + jobs.build(commands: Project.base_build_script) + end + + def validate_jobs + remaining_jobs = jobs.reject(&:marked_for_destruction?) + + if remaining_jobs.empty? + errors.add(:jobs, "At least one foo") + end + end end diff --git a/app/services/create_build_service.rb b/app/services/create_builds_service.rb index b3a4edf..8608227 100644 --- a/app/services/create_build_service.rb +++ b/app/services/create_builds_service.rb @@ -1,12 +1,7 @@ -class CreateBuildService +class CreateBuildsService def execute(project, params) commit = Commit.where(project: project).where(sha: params[:after]).first commit ||= CreateCommitService.new.execute(project, params) - - if commit.persisted? - commit.builds.create - else - commit.builds.new - end + commit.create_builds end end diff --git a/app/services/create_project_service.rb b/app/services/create_project_service.rb index bbc33a1..c012b8a 100644 --- a/app/services/create_project_service.rb +++ b/app/services/create_project_service.rb @@ -5,6 +5,7 @@ class CreateProjectService @project = Project.parse(params) Project.transaction do + @project.build_default_job @project.save! opts = { diff --git a/app/views/builds/_build.html.haml b/app/views/builds/_build.html.haml index e851a7a..853556c 100644 --- a/app/views/builds/_build.html.haml +++ b/app/views/builds/_build.html.haml @@ -3,11 +3,12 @@ = build.status %td.build-link - = link_to build_url(build) do - %strong #{build.short_sha} + = link_to project_build_path(build.project, build) do + %strong Build ##{build.id} - %td.build-message - %span= truncate(build.commit.git_commit_message, length: 50) + %td + - if build.job + = build.job.name %td.build-branch - unless @ref diff --git a/app/views/builds/show.html.haml b/app/views/builds/show.html.haml index 8da3a1c..4694bae 100644 --- a/app/views/builds/show.html.haml +++ b/app/views/builds/show.html.haml @@ -6,8 +6,8 @@ Edit Project %p - = link_to project_path(@project) do - ← Back to project builds + = link_to project_commit_path(@project, @build.commit) do + ← Back to project commit %hr .row diff --git a/app/views/commits/_commit.html.haml b/app/views/commits/_commit.html.haml new file mode 100644 index 0000000..554a05d --- /dev/null +++ b/app/views/commits/_commit.html.haml @@ -0,0 +1,29 @@ +%tr.build.alert{class: commit_status_alert_class(commit)} + %td.status + = commit.status + + %td.build-link + = link_to project_commit_path(commit.project, commit) do + %strong #{commit.short_sha} + + %td.build-message + %span= truncate(commit.git_commit_message, length: 50) + + %td.build-branch + - unless @ref + %span + = link_to commit.ref, project_path(@project, ref: commit.ref) + + %td.duration + - if commit.duration + #{distance_of_time_in_words commit.duration} + + %td.timestamp + - if commit.finished_at + %span #{time_ago_in_words commit.finished_at} ago + + - if commit.project.coverage_enabled? + %td.coverage + - if commit.coverage + #{commit.coverage}% + diff --git a/app/views/commits/show.html.haml b/app/views/commits/show.html.haml new file mode 100644 index 0000000..d7c907b --- /dev/null +++ b/app/views/commits/show.html.haml @@ -0,0 +1,56 @@ +%h3.page-title + = @project.name + @ + #{gitlab_commit_link(@project, @commit.sha)} + .pull-right + = link_to edit_project_path(@project), class: "btn btn-default btn-small" do + %i.icon-edit.icon-white + Edit Project + +%p + = link_to project_path(@project) do + ← Back to project commits +%hr +.commit-info + %pre.commit-message + #{@commit.git_commit_message} + + .row + .col-sm-6 + - if @commit.compare? + %p + %span.attr-name Compare: + #{gitlab_compare_link(@project, @commit.short_before_sha, @commit.short_sha)} + %p + %span.attr-name Branch: + #{gitlab_ref_link(@project, @commit.ref)} + .col-sm-6 + %p + %span.attr-name Author: + #{@commit.git_author_name} + - if @commit.created_at + %p + %span.attr-name Created at: + #{@commit.created_at.to_s(:short)} + + +%h3 Status + +.build.alert{class: commit_status_alert_class(@commit)} + .status + = @commit.status.titleize + +%h3 Builds + +%table.builds + %thead + %tr + %th Status + %th Build ID + %th Job + %th Branch + %th Duration + %th Finished at + - if @project.coverage_enabled? + %th Coverage + = render @builds diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml index 9b6355a..5780904 100644 --- a/app/views/layouts/project.html.haml +++ b/app/views/layouts/project.html.haml @@ -24,8 +24,8 @@ = nav_link path: 'projects#show' do = link_to project_path(@project) do %i.icon-list-alt - Builds - %small.pull-right= @project.builds.count + Commits + %small.pull-right= @project.commits.count = nav_link path: 'charts#show' do = link_to project_charts_path(@project) do %i.icon-bar-chart diff --git a/app/views/notify/build_fail_email.html.haml b/app/views/notify/build_fail_email.html.haml index 71e36e3..7573e17 100644 --- a/app/views/notify/build_fail_email.html.haml +++ b/app/views/notify/build_fail_email.html.haml @@ -6,9 +6,9 @@ %p Status: #{@build.status} %p - Commit: #{@build.short_sha} + Commit: #{@build.commit.short_sha} %p - Author: #{@build.git_author_name} + Author: #{@build.commit.git_author_name} %p Url: #{link_to @build.short_sha, project_build_url(@build.project, @build)} diff --git a/app/views/notify/build_fail_email.text.erb b/app/views/notify/build_fail_email.text.erb index e93e9d4..ef2233c 100644 --- a/app/views/notify/build_fail_email.text.erb +++ b/app/views/notify/build_fail_email.text.erb @@ -1,7 +1,7 @@ Build failed for <%= @project.name %> Status: <%= @build.status %> -Commit: <%= @build.short_sha %> -Author: <%= @build.git_author_name %> +Commit: <%= @build.commit.short_sha %> +Author: <%= @build.commit.git_author_name %> Url: <%= project_build_url(@build.project, @build) %> diff --git a/app/views/notify/build_success_email.html.haml b/app/views/notify/build_success_email.html.haml index 9549408..10ceba4 100644 --- a/app/views/notify/build_success_email.html.haml +++ b/app/views/notify/build_success_email.html.haml @@ -6,9 +6,9 @@ %p Status: #{@build.status} %p - Commit: #{@build.short_sha} + Commit: #{@build.commit.short_sha} %p - Author: #{@build.git_author_name} + Author: #{@build.commit.git_author_name} %p Url: #{link_to @build.short_sha, project_build_url(@build.project, @build)} diff --git a/app/views/notify/build_success_email.text.erb b/app/views/notify/build_success_email.text.erb index 5ebe71a..5470a5b 100644 --- a/app/views/notify/build_success_email.text.erb +++ b/app/views/notify/build_success_email.text.erb @@ -1,7 +1,7 @@ Build successful for <%= @project.name %> Status: <%= @build.status %> -Commit: <%= @build.short_sha %> -Author: <%= @build.git_author_name %> +Commit: <%= @build.commit.short_sha %> +Author: <%= @build.commit.git_author_name %> Url: <%= project_build_url(@build.project, @build) %> diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml index c0189bb..da63494 100644 --- a/app/views/projects/_form.html.haml +++ b/app/views/projects/_form.html.haml @@ -1,4 +1,4 @@ -= form_for @project, html: { class: 'form-horizontal' } do |f| += nested_form_for @project, html: { class: 'form-horizontal' } do |f| - if @project.errors.any? #error_explanation %p.lead= "#{pluralize(@project.errors.count, "error")} prohibited this project from being saved:" @@ -8,17 +8,27 @@ %li= msg %fieldset - %legend Build settings - .form-group - = f.label :scripts, 'Build steps', class: 'control-label' - .col-sm-10 - = f.text_area :scripts, class: 'form-control', rows: 14, placeholder: "bundle exec rake spec" - .bs-callout.bs-callout-info - %p - All lines will be concatenated in one file and executed. - %br - If you change the working directory or the environment in one line - it will affect the next lines too + %legend Build scripts + = f.fields_for :jobs do |job_form| + .form-group + = f.label :name, 'Job name', class: 'control-label' + .col-sm-10 + = job_form.text_field :name, class: 'form-control', placeholder: "Ex. cucumber" + .form-group + = f.label :commands, 'Build steps', class: 'control-label' + .col-sm-10 + = job_form.text_area :commands, class: 'form-control', rows: 14, placeholder: "bundle exec rake spec" + .bs-callout.bs-callout-info + %p + All lines will be concatenated in one file and executed. + %br + If you change the working directory or the environment in one line - it will affect the next lines too + = job_form.link_to_remove "Remove this job", class: 'btn btn-danger' + %p + = f.link_to_add "Add a job", :jobs, class: 'btn btn-success pull-right' + %fieldset + %legend Build settings .form-group = label_tag nil, class: 'control-label' do Get code diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 633ad4f..fe23277 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -3,7 +3,7 @@ %ul.nav.nav-tabs.append-bottom-20 %li{class: ref_tab_class} - = link_to 'All builds', project_path(@project) + = link_to 'All commits', project_path(@project) - @project.tracked_refs.each do |ref| %li{class: ref_tab_class(ref)} = link_to ref, project_path(@project, ref: ref) @@ -45,10 +45,11 @@ - if @project.coverage_enabled? %th Coverage - = render @builds + = render @commits -= paginate @builds -- if @builds.empty? += paginate @commits + +- if @commits.empty? .bs-callout - %h4 No builds yet + %h4 No commits yet diff --git a/config/database.yml.prod b/config/database.yml.prod new file mode 100644 index 0000000..179296b --- /dev/null +++ b/config/database.yml.prod @@ -0,0 +1,19 @@ +development: + adapter: mysql2 + encoding: utf8 + reconnect: false + database: gitlab_ci_development + pool: 5 + username: root + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: &test + adapter: mysql2 + encoding: utf8 + reconnect: false + database: gitlab_ci_test + pool: 5 + username: root + # socket: /tmp/mysql.sock diff --git a/config/routes.rb b/config/routes.rb index 3dbabf6..70a5148 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,6 +20,11 @@ GitlabCi::Application.routes.draw do end resource :charts, only: [:show] + resources :commits, only: [:show] do + member do + get :status + end + end resources :builds, only: [:show] do member do diff --git a/db/migrate/20141103135037_add_parallel_to_build.rb b/db/migrate/20141103135037_add_parallel_to_build.rb new file mode 100644 index 0000000..2a3f64f --- /dev/null +++ b/db/migrate/20141103135037_add_parallel_to_build.rb @@ -0,0 +1,12 @@ +class AddParallelToBuild < ActiveRecord::Migration + def change + create_table :jobs do |t| + t.integer :project_id, null: false + t.text :commands + t.boolean :active, null: false, default: true + t.timestamps + end + + add_index :jobs, :project_id + end +end diff --git a/db/migrate/20141103151359_add_commands_to_build.rb b/db/migrate/20141103151359_add_commands_to_build.rb new file mode 100644 index 0000000..2ef4b8e --- /dev/null +++ b/db/migrate/20141103151359_add_commands_to_build.rb @@ -0,0 +1,5 @@ +class AddCommandsToBuild < ActiveRecord::Migration + def change + add_column :builds, :commands, :text + end +end diff --git a/db/migrate/20141103162726_add_job_id_to_build.rb b/db/migrate/20141103162726_add_job_id_to_build.rb new file mode 100644 index 0000000..b4e3020 --- /dev/null +++ b/db/migrate/20141103162726_add_job_id_to_build.rb @@ -0,0 +1,5 @@ +class AddJobIdToBuild < ActiveRecord::Migration + def change + add_column :builds, :job_id, :integer + end +end diff --git a/db/migrate/20141104130024_migrate_jobs.rb b/db/migrate/20141104130024_migrate_jobs.rb new file mode 100644 index 0000000..1d80fb8 --- /dev/null +++ b/db/migrate/20141104130024_migrate_jobs.rb @@ -0,0 +1,12 @@ +class MigrateJobs < ActiveRecord::Migration + def up + Project.find_each(batch_size: 100) do |project| + job = project.jobs.create(commands: project.scripts) + project.builds.order('id DESC').limit(10).update_all(job_id: job.id) + end + end + + def down + Job.destroy_all + end +end diff --git a/db/migrate/20141104153744_add_name_to_job.rb b/db/migrate/20141104153744_add_name_to_job.rb new file mode 100644 index 0000000..ef4dd17 --- /dev/null +++ b/db/migrate/20141104153744_add_name_to_job.rb @@ -0,0 +1,5 @@ +class AddNameToJob < ActiveRecord::Migration + def change + add_column :jobs, :name, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 52a2b2c..73830b6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20141031141708) do +ActiveRecord::Schema.define(version: 20141104153744) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -32,6 +32,8 @@ ActiveRecord::Schema.define(version: 20141031141708) do t.integer "runner_id" t.float "coverage" t.integer "commit_id" + t.text "commands" + t.integer "job_id" end add_index "builds", ["commit_id"], name: "index_builds_on_commit_id", using: :btree @@ -55,6 +57,17 @@ ActiveRecord::Schema.define(version: 20141031141708) do add_index "commits", ["project_id"], name: "index_commits_on_project_id", using: :btree add_index "commits", ["sha"], name: "index_commits_on_sha", using: :btree + create_table "jobs", force: true do |t| + t.integer "project_id", null: false + t.text "commands" + t.boolean "active", default: true, null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "name" + end + + add_index "jobs", ["project_id"], name: "index_jobs_on_project_id", using: :btree + create_table "projects", force: true do |t| t.string "name", null: false t.integer "timeout", default: 1800, null: false diff --git a/lib/api/builds.rb b/lib/api/builds.rb index f96c1d4..5d2c352 100644 --- a/lib/api/builds.rb +++ b/lib/api/builds.rb @@ -83,7 +83,10 @@ module API required_attributes! [:project_id, :data, :project_token] project = Project.find(params[:project_id]) authenticate_project_token!(project) - build = CreateBuildService.new.execute(project, params[:data]) + builds = CreateBuildsService.new.execute(project, params[:data]) + + # to keep api compatibility for now + build = builds.first if build.persisted? present build, with: Entities::Build diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 5f4bfcf..d6cd689 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -72,6 +72,7 @@ module API } project = Project.new(filtered_params) + project.build_default_job if project.save present project, :with => Entities::Project diff --git a/spec/factories/builds.rb b/spec/factories/builds.rb index 7e222b2..eacdd76 100644 --- a/spec/factories/builds.rb +++ b/spec/factories/builds.rb @@ -3,15 +3,23 @@ # Table name: builds # # id :integer not null, primary key +# project_id :integer +# ref :string(255) # status :string(255) # finished_at :datetime # trace :text # created_at :datetime # updated_at :datetime +# sha :string(255) # started_at :datetime # tmp_file :string(255) +# before_sha :string(255) +# push_data :text # runner_id :integer +# coverage :float # commit_id :integer +# commands :text +# job_id :integer # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index c05dad8..f767cac 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -2,15 +2,14 @@ # # Table name: commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# status :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime # # Read about factories at https://github.com/thoughtbot/factory_girl diff --git a/spec/factories/job.rb b/spec/factories/job.rb new file mode 100644 index 0000000..93f2ff7 --- /dev/null +++ b/spec/factories/job.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :job do + name 'rspec' + commands 'bundle exec rspec spec' + end +end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index b28b4ab..46b11d5 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -20,6 +20,8 @@ # email_recipients :string(255) default(""), not null # email_add_committer :boolean default(TRUE), not null # email_only_broken_builds :boolean default(TRUE), not null +# skip_refs :string(255) +# coverage_regex :string(255) # # Read about factories at https://github.com/thoughtbot/factory_girl @@ -36,5 +38,9 @@ FactoryGirl.define do factory :project do token 'iPWx6WM4lhHNedGfBpPJNP' end + + before :create do |project| + project.build_default_job + end end end diff --git a/spec/features/admin/projects_spec.rb b/spec/features/admin/projects_spec.rb index fa72337..0c8168a 100644 --- a/spec/features/admin/projects_spec.rb +++ b/spec/features/admin/projects_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe "Admin Projects" do + let(:project) { FactoryGirl.create :project } + before do skip_admin_auth login_as :user @@ -8,10 +10,19 @@ describe "Admin Projects" do describe "GET /admin/projects" do before do + project visit admin_projects_path end it { page.should have_content "Manage Projects" } end -end + describe "GET /admin/projects/:id" do + before do + visit admin_project_path(project) + end + + it { page.should have_content "Project info" } + it { page.should have_content project.name } + end +end diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb index 2c59e14..91ac95a 100644 --- a/spec/features/builds_spec.rb +++ b/spec/features/builds_spec.rb @@ -8,7 +8,7 @@ describe "Builds" do @build = FactoryGirl.create :build, commit: @commit end - describe "GET /:project/builds" do + describe "GET /:project/builds/:id" do before do visit project_build_path(@project, @build) end @@ -17,4 +17,25 @@ describe "Builds" do it { page.should have_content @commit.git_commit_message } it { page.should have_content @commit.git_author_name } end + + describe "GET /:project/builds/:id/cancel" do + before do + @build.run! + visit cancel_project_build_path(@project, @build) + end + + it { page.should have_content 'canceled' } + it { page.should have_content 'Retry' } + end + + describe "POST /:project/builds/:id/retry" do + before do + @build.cancel! + visit project_build_path(@project, @build) + click_link 'Retry' + end + + it { page.should have_content 'pending' } + it { page.should have_content 'Cancel' } + end end diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb new file mode 100644 index 0000000..3073884 --- /dev/null +++ b/spec/features/commits_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe "Commits" do + before do + login_as :user + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe "GET /:project/commits/:sha" do + before do + visit project_commit_path(@project, @commit) + end + + it { page.should have_content @commit.sha[0..7] } + it { page.should have_content @commit.git_commit_message } + it { page.should have_content @commit.git_author_name } + end +end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 7a337ef..c6320b7 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -21,7 +21,7 @@ describe "Projects" do end it { page.should have_content @project.name } - it { page.should have_content 'All builds' } + it { page.should have_content 'All commits' } end describe "GET /projects/:id/edit" do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb new file mode 100644 index 0000000..81aaa27 --- /dev/null +++ b/spec/mailers/notify_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Notify do + include EmailSpec::Helpers + include EmailSpec::Matchers + + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit + end + + describe 'build success' do + subject { Notify.build_success_email(@build.id, 'wow@example.com') } + + it 'has the correct subject' do + should have_subject /Build success for/ + end + + it 'contains name of project' do + should have_body_text /Build successful/ + end + end + + describe 'build fail' do + subject { Notify.build_fail_email(@build.id, 'wow@example.com') } + + it 'has the correct subject' do + should have_subject /Build failed for/ + end + + it 'contains name of project' do + should have_body_text /Build failed/ + end + end +end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 6e88f31..753da0d 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -3,15 +3,23 @@ # Table name: builds # # id :integer not null, primary key +# project_id :integer +# ref :string(255) # status :string(255) # finished_at :datetime # trace :text # created_at :datetime # updated_at :datetime +# sha :string(255) # started_at :datetime # tmp_file :string(255) +# before_sha :string(255) +# push_data :text # runner_id :integer +# coverage :float # commit_id :integer +# commands :text +# job_id :integer # require 'spec_helper' @@ -141,12 +149,6 @@ describe Build do end end - describe :commands do - subject { build.commands } - - it { should eq(commit.project.scripts) } - end - describe :timeout do subject { build.timeout } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 380a9b1..75f236b 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -2,14 +2,14 @@ # # Table name: commits # -# id :integer not null, primary key -# project_id :integer -# ref :string(255) -# sha :string(255) -# before_sha :string(255) -# push_data :text -# created_at :datetime -# updated_at :datetime +# id :integer not null, primary key +# project_id :integer +# ref :string(255) +# sha :string(255) +# before_sha :string(255) +# push_data :text +# created_at :datetime +# updated_at :datetime # require 'spec_helper' diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 0e29b5c..fd33ddd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -20,6 +20,8 @@ # email_recipients :string(255) default(""), not null # email_add_committer :boolean default(TRUE), not null # email_only_broken_builds :boolean default(TRUE), not null +# skip_refs :string(255) +# coverage_regex :string(255) # require 'spec_helper' @@ -112,6 +114,8 @@ describe Project do let(:project_dump) { File.read(Rails.root.join('spec/support/gitlab_stubs/raw_project.yml')) } let(:parsed_project) { Project.parse(project_dump) } + before { parsed_project.build_default_job } + it { parsed_project.should be_valid } it { parsed_project.should be_kind_of(Project) } it { parsed_project.name.should eq("GitLab / api.gitlab.org") } diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb new file mode 100644 index 0000000..0fe2313 --- /dev/null +++ b/spec/requests/api/builds_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' + +describe API::API do + include ApiHelpers + + let(:runner) { FactoryGirl.create(:runner) } + let(:project) { FactoryGirl.create(:project) } + + describe "Builds API for runners" do + before do + FactoryGirl.create :runner_project, project_id: project.id, runner_id: runner.id + end + + describe "POST /builds/register" do + it "should start a build" do + commit = FactoryGirl.create(:commit, project: project) + build = FactoryGirl.create(:build, commit: commit, status: 'pending' ) + + post api("/builds/register"), token: runner.token + + response.status.should == 201 + json_response['sha'].should == build.sha + end + + it "should return 404 error if no pending build found" do + post api("/builds/register"), token: runner.token + + response.status.should == 404 + end + end + + describe "PUT /builds/:id" do + let(:commit) { FactoryGirl.create(:commit, project: project)} + let(:build) { FactoryGirl.create(:build, commit: commit, runner_id: runner.id) } + + it "should update a running build" do + build.run! + put api("/builds/#{build.id}"), token: runner.token + response.status.should == 200 + end + end + end + + describe "POST /builds" do + let(:data) { + { + "before" => "95790bf891e76fee5e1747ab589903a6a1f80f22", + "after" => "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "ref" => "refs/heads/master", + "commits" => [ + { + "id" => "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "message" => "Update Catalan translation to e38cb41.", + "timestamp" => "2011-12-12T14:27:31+02:00", + "url" => "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "author" => { + "name" => "Jordi Mallach", + "email" => "jordi@softcatala.org", + } + } + ] + } + } + + it "should create a build" do + post api("/builds"), project_id: project.id, data: data, project_token: project.token + + response.status.should == 201 + json_response['sha'].should == "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" + end + + it "should return 400 error if no data passed" do + post api("/builds"), project_id: project.id, project_token: project.token + + response.status.should == 400 + json_response['message'].should == "400 (Bad request) \"data\" not given" + end + end +end diff --git a/spec/requests/projects_spec.rb b/spec/requests/api/projects_spec.rb index bb2f1cb..bb2f1cb 100644 --- a/spec/requests/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb diff --git a/spec/requests/runners_spec.rb b/spec/requests/api/runners_spec.rb index bf0b658..bf0b658 100644 --- a/spec/requests/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb diff --git a/spec/requests/builds_spec.rb b/spec/requests/builds_spec.rb index 0fe2313..73d540e 100644 --- a/spec/requests/builds_spec.rb +++ b/spec/requests/builds_spec.rb @@ -1,79 +1,18 @@ require 'spec_helper' -describe API::API do - include ApiHelpers - - let(:runner) { FactoryGirl.create(:runner) } - let(:project) { FactoryGirl.create(:project) } - - describe "Builds API for runners" do - before do - FactoryGirl.create :runner_project, project_id: project.id, runner_id: runner.id - end - - describe "POST /builds/register" do - it "should start a build" do - commit = FactoryGirl.create(:commit, project: project) - build = FactoryGirl.create(:build, commit: commit, status: 'pending' ) - - post api("/builds/register"), token: runner.token - - response.status.should == 201 - json_response['sha'].should == build.sha - end - - it "should return 404 error if no pending build found" do - post api("/builds/register"), token: runner.token - - response.status.should == 404 - end - end - - describe "PUT /builds/:id" do - let(:commit) { FactoryGirl.create(:commit, project: project)} - let(:build) { FactoryGirl.create(:build, commit: commit, runner_id: runner.id) } - - it "should update a running build" do - build.run! - put api("/builds/#{build.id}"), token: runner.token - response.status.should == 200 - end - end +describe "Builds" do + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + @build = FactoryGirl.create :build, commit: @commit end - describe "POST /builds" do - let(:data) { - { - "before" => "95790bf891e76fee5e1747ab589903a6a1f80f22", - "after" => "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", - "ref" => "refs/heads/master", - "commits" => [ - { - "id" => "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "message" => "Update Catalan translation to e38cb41.", - "timestamp" => "2011-12-12T14:27:31+02:00", - "url" => "http://localhost/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", - "author" => { - "name" => "Jordi Mallach", - "email" => "jordi@softcatala.org", - } - } - ] - } - } - - it "should create a build" do - post api("/builds"), project_id: project.id, data: data, project_token: project.token - - response.status.should == 201 - json_response['sha'].should == "da1560886d4f094c3e6c9ef40349f7d38b5d27d7" + describe "GET /:project/builds/:id/status.json" do + before do + get status_project_build_path(@project, @build), format: :json end - it "should return 400 error if no data passed" do - post api("/builds"), project_id: project.id, project_token: project.token - - response.status.should == 400 - json_response['message'].should == "400 (Bad request) \"data\" not given" - end + it { response.status.should == 200 } + it { response.body.should include(@build.sha) } end end diff --git a/spec/requests/commits_spec.rb b/spec/requests/commits_spec.rb new file mode 100644 index 0000000..5c894ea --- /dev/null +++ b/spec/requests/commits_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe "Commits" do + before do + @project = FactoryGirl.create :project + @commit = FactoryGirl.create :commit, project: @project + end + + describe "GET /:project/commits/:id/status.json" do + before do + get status_project_commit_path(@project, @commit), format: :json + end + + it { response.status.should == 200 } + it { response.body.should include(@commit.sha) } + end +end diff --git a/spec/services/create_build_service_spec.rb b/spec/services/create_builds_service_spec.rb index 11b4588..4c79988 100644 --- a/spec/services/create_build_service_spec.rb +++ b/spec/services/create_builds_service_spec.rb @@ -1,12 +1,13 @@ require 'spec_helper' -describe CreateBuildService do - let(:service) { CreateBuildService.new } +describe CreateBuildsService do + let(:service) { CreateBuildsService.new } let(:project) { FactoryGirl.create(:project) } describe :execute do context 'valid params' do - let(:build) { service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312') } + let(:builds) { service.execute(project, ref: 'refs/heads/master', before: '00000000', after: '31das312') } + let(:build) { builds.first } it { build.should be_kind_of(Build) } it { build.should be_pending } @@ -16,7 +17,8 @@ describe CreateBuildService do end context 'without params' do - let(:build) { service.execute(project, {}) } + let(:builds) { service.execute(project, {}) } + let(:build) { builds.first } it { build.should be_kind_of(Build) } it { build.should be_pending } |