summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/ci/build.rb87
-rw-r--r--app/models/ci/commit.rb88
-rw-r--r--app/models/commit.rb8
-rw-r--r--app/models/commit_status.rb79
-rw-r--r--app/models/generic_commit_status.rb15
-rw-r--r--app/services/ci/create_commit_service.rb2
-rw-r--r--app/views/projects/builds/_build.html.haml50
-rw-r--r--app/views/projects/builds/show.html.haml6
-rw-r--r--app/views/projects/commit/ci.html.haml36
-rw-r--r--app/views/projects/commit_statuses/_commit_status.html.haml52
11 files changed, 246 insertions, 179 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb
index a020b24a550..77c121ca5e8 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -135,6 +135,8 @@ class Ability
def project_report_rules
project_guest_rules + [
+ :create_commit_status,
+ :read_commit_statuses,
:download_code,
:fork_project,
:create_project_snippet,
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 5d17f4418ed..41ce522b2ff 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -24,32 +24,19 @@
#
module Ci
- class Build < ActiveRecord::Base
- extend Ci::Model
-
+ class Build < CommitStatus
LAZY_ATTRIBUTES = ['trace']
- belongs_to :commit, class_name: 'Ci::Commit'
belongs_to :runner, class_name: 'Ci::Runner'
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
- belongs_to :user
serialize :options
- validates :commit, presence: true
- validates :status, presence: true
validates :coverage, numericality: true, allow_blank: true
validates_presence_of :ref
- scope :running, ->() { where(status: "running") }
- scope :pending, ->() { where(status: "pending") }
- scope :success, ->() { where(status: "success") }
- scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
- scope :running_or_pending, ->() { where(status:[:running, :pending]) }
- scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
scope :ignore_failures, ->() { where(allow_failure: false) }
- scope :for_ref, ->(ref) { where(ref: ref) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
acts_as_taggable
@@ -74,13 +61,14 @@ module Ci
def create_from(build)
new_build = build.dup
- new_build.status = :pending
+ new_build.status = 'pending'
new_build.runner_id = nil
+ new_build.trigger_request_id = nil
new_build.save
end
def retry(build)
- new_build = Ci::Build.new(status: :pending)
+ new_build = Ci::Build.new(status: 'pending')
new_build.ref = build.ref
new_build.tag = build.tag
new_build.options = build.options
@@ -98,28 +86,7 @@ module Ci
end
state_machine :status, initial: :pending do
- event :run do
- transition pending: :running
- end
-
- event :drop do
- transition running: :failed
- end
-
- event :success do
- transition running: :success
- end
-
- event :cancel do
- transition [:pending, :running] => :canceled
- end
-
- after_transition pending: :running do |build, transition|
- build.update_attributes started_at: Time.now
- end
-
after_transition any => [:success, :failed, :canceled] do |build, transition|
- build.update_attributes finished_at: Time.now
project = build.project
if project.web_hooks?
@@ -136,19 +103,10 @@ module Ci
build.update_coverage
end
end
-
- state :pending, value: 'pending'
- state :running, value: 'running'
- state :failed, value: 'failed'
- state :success, value: 'success'
- state :canceled, value: 'canceled'
end
- delegate :sha, :short_sha, :project, :gl_project,
- to: :commit, prefix: false
-
- def before_sha
- Gitlab::Git::BLANK_SHA
+ def ignored?
+ failed? && allow_failure?
end
def trace_html
@@ -156,22 +114,6 @@ module Ci
html || ''
end
- def started?
- !pending? && !canceled? && started_at
- end
-
- def active?
- running? || pending?
- end
-
- def complete?
- canceled? || success? || failed?
- end
-
- def ignored?
- failed? && allow_failure?
- end
-
def timeout
project.timeout
end
@@ -180,14 +122,6 @@ module Ci
yaml_variables + project_variables + trigger_variables
end
- def duration
- if started_at && finished_at
- finished_at - started_at
- elsif started_at
- Time.now - started_at
- end
- end
-
def project
commit.project
end
@@ -278,6 +212,15 @@ module Ci
"#{dir_to_trace}/#{id}.log"
end
+ def description
+ name
+ end
+
+ def target_url
+ Gitlab::Application.routes.url_helpers.
+ namespace_project_build_url(gl_project.namespace, gl_project, self)
+ end
+
private
def yaml_variables
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index fde754a92a1..042a68681bb 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -20,7 +20,8 @@ module Ci
extend Ci::Model
belongs_to :gl_project, class_name: '::Project', foreign_key: :gl_project_id
- has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
+ has_many :statuses, dependent: :destroy, class_name: 'CommitStatus'
+ has_many :builds, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
validates_presence_of :sha
@@ -81,12 +82,11 @@ module Ci
end
def stage
- running_or_pending = builds_without_retry.running_or_pending
- running_or_pending.limit(1).pluck(:stage).first
+ running_or_pending = statuses.latest.running_or_pending
+ running_or_pending.first.try(:stage)
end
def create_builds(ref, tag, user, trigger_request = nil)
- return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present?
@@ -94,7 +94,6 @@ module Ci
end
def create_next_builds(ref, tag, user, trigger_request)
- return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage)
@@ -107,39 +106,47 @@ module Ci
end
def refs
- builds.group(:ref).pluck(:ref)
+ statuses.pluck(:ref).compact.uniq
end
- def last_ref
- builds.latest.first.try(:ref)
- end
-
- def builds_without_retry
- builds.latest
+ def statuses_for_ref(ref = nil)
+ if ref
+ statuses.for_ref(ref)
+ else
+ statuses
+ end
end
- def builds_without_retry_for_ref(ref)
- builds.for_ref(ref).latest
+ def builds_without_retry(ref = nil)
+ if ref
+ builds.for_ref(ref).latest
+ else
+ builds.latest
+ end
end
- def retried_builds
- @retried_builds ||= (builds.order(id: :desc) - builds_without_retry)
+ def retried
+ @retried ||= (statuses.order(id: :desc) - statuses.latest)
end
- def status
- if skip_ci?
- return 'skipped'
- elsif yaml_errors.present?
+ def status(ref = nil)
+ if yaml_errors.present?
return 'failed'
- elsif builds.none?
+ end
+
+ latest_statuses = statuses.latest.to_a
+ latest_statuses.reject! { |status| status.try(&:allow_failure?) }
+ latest_statuses.select! { |status| status.ref == nil || status.ref == ref } if ref
+
+ if latest_statuses.none?
return 'skipped'
- elsif success?
+ elsif latest_statuses.all?(&:success?)
'success'
- elsif pending?
+ elsif latest_statuses.all?(&:pending?)
'pending'
- elsif running?
+ elsif latest_statuses.any?(&:running?) || latest_statuses.any?(&:pending?)
'running'
- elsif canceled?
+ elsif latest_statuses.all?(&:canceled?)
'canceled'
else
'failed'
@@ -147,21 +154,15 @@ module Ci
end
def pending?
- builds_without_retry.all? do |build|
- build.pending?
- end
+ status == 'pending'
end
def running?
- builds_without_retry.any? do |build|
- build.running? || build.pending?
- end
+ status == 'running'
end
def success?
- builds_without_retry.all? do |build|
- build.success? || build.ignored?
- end
+ status == 'success'
end
def failed?
@@ -169,21 +170,15 @@ module Ci
end
def canceled?
- builds_without_retry.all? do |build|
- build.canceled?
- end
- end
-
- def duration
- @duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
+ status == 'canceled'
end
- def duration_for_ref(ref)
- builds_without_retry_for_ref(ref).select(&:duration).sum(&:duration).to_i
+ def duration(ref = nil)
+ statuses_for_ref(ref).latest.select(&:duration).sum(&:duration).to_i
end
def finished_at
- @finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
+ @finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
end
def coverage
@@ -195,8 +190,8 @@ module Ci
end
end
- def matrix_for_ref?(ref)
- builds_without_retry_for_ref(ref).pluck(:id).size > 1
+ def matrix?(ref)
+ builds_without_retry(ref).pluck(:id).size > 1
end
def config_processor
@@ -217,7 +212,6 @@ module Ci
end
def skip_ci?
- return false if builds.any?
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index aff329d71fa..d5c50013525 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -184,4 +184,12 @@ class Commit
def parents
@parents ||= Commit.decorate(super, project)
end
+
+ def ci_commit
+ project.ci_commit(sha)
+ end
+
+ def status
+ ci_commit.try(:status) || :not_found
+ end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
new file mode 100644
index 00000000000..4c6de18527b
--- /dev/null
+++ b/app/models/commit_status.rb
@@ -0,0 +1,79 @@
+class CommitStatus < ActiveRecord::Base
+ self.table_name = 'ci_builds'
+
+ belongs_to :commit, class_name: 'Ci::Commit'
+ belongs_to :user
+
+ validates :commit, presence: true
+ validates :status, inclusion: {in: %w(pending running failed success canceled)}
+
+ validates_presence_of :name
+
+ scope :running, ->() { where(status: 'running') }
+ scope :pending, ->() { where(status: 'pending') }
+ scope :success, ->() { where(status: 'success') }
+ scope :failed, ->() { where(status: 'failed') }
+ scope :running_or_pending, ->() { where(status:[:running, :pending]) }
+ scope :latest, ->() { where(id: unscope(:select).select('max(id)').group(:name, :ref)).order(stage_idx: :asc) }
+ scope :for_ref, ->(ref) { where(ref: [ref, nil]) }
+ scope :running_or_pending, ->() { where(status: [:running, :pending]) }
+
+ state_machine :status, initial: :pending do
+ event :run do
+ transition pending: :running
+ end
+
+ event :drop do
+ transition running: :failed
+ end
+
+ event :success do
+ transition [:pending, :running] => :success
+ end
+
+ event :cancel do
+ transition [:pending, :running] => :canceled
+ end
+
+ after_transition pending: :running do |build, transition|
+ build.update_attributes started_at: Time.now
+ end
+
+ after_transition any => [:success, :failed, :canceled] do |build, transition|
+ build.update_attributes finished_at: Time.now
+ end
+
+ state :pending, value: 'pending'
+ state :running, value: 'running'
+ state :failed, value: 'failed'
+ state :success, value: 'success'
+ state :canceled, value: 'canceled'
+ end
+
+ delegate :sha, :short_sha, :gl_project,
+ to: :commit, prefix: false
+
+ def before_sha
+ Gitlab::Git::BLANK_SHA
+ end
+
+ def started?
+ !pending? && !canceled? && started_at
+ end
+
+ def active?
+ running? || pending?
+ end
+
+ def complete?
+ canceled? || success? || failed?
+ end
+
+ def duration
+ if started_at && finished_at
+ finished_at - started_at
+ elsif started_at
+ Time.now - started_at
+ end
+ end
+end
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
new file mode 100644
index 00000000000..fa54e3540d0
--- /dev/null
+++ b/app/models/generic_commit_status.rb
@@ -0,0 +1,15 @@
+class GenericCommitStatus < CommitStatus
+ before_validation :set_default_values
+
+ # GitHub compatible API
+ alias_attribute :context, :name
+
+ def set_default_values
+ self.context ||= 'default'
+ self.stage ||= 'external'
+ end
+
+ def tags
+ [:external]
+ end
+end
diff --git a/app/services/ci/create_commit_service.rb b/app/services/ci/create_commit_service.rb
index fc1ae5774d5..0ae35387579 100644
--- a/app/services/ci/create_commit_service.rb
+++ b/app/services/ci/create_commit_service.rb
@@ -17,6 +17,8 @@ module Ci
tag = origin_ref.start_with?('refs/tags/')
commit = project.gl_project.ensure_ci_commit(sha)
+ return false if commit.skip_ci?
+
commit.update_committed!
commit.create_builds(ref, tag, user)
diff --git a/app/views/projects/builds/_build.html.haml b/app/views/projects/builds/_build.html.haml
deleted file mode 100644
index 65fd9413b60..00000000000
--- a/app/views/projects/builds/_build.html.haml
+++ /dev/null
@@ -1,50 +0,0 @@
-- gl_project = build.project.gl_project
-%tr.build
- %td.status
- = ci_status_with_icon(build.status)
-
- %td.build-link
- = link_to namespace_project_build_path(gl_project.namespace, gl_project, build) do
- %strong Build ##{build.id}
-
- - if defined?(ref)
- %td
- = build.ref
-
- %td
- = build.stage
-
- %td
- = build.name
- .pull-right
- - if build.tags.any?
- - build.tag_list.each do |tag|
- %span.label.label-primary
- = tag
- - if build.trigger_request
- %span.label.label-info triggered
- - if build.allow_failure
- %span.label.label-danger allowed to fail
-
- %td.duration
- - if build.duration
- #{duration_in_words(build.finished_at, build.started_at)}
-
- %td.timestamp
- - if build.finished_at
- %span #{time_ago_in_words build.finished_at} ago
-
- - if build.project.coverage_enabled?
- %td.coverage
- - if build.coverage
- #{build.coverage}%
-
- %td
- - if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project)
- .pull-right
- - if build.active?
- = link_to cancel_namespace_project_build_path(gl_project.namespace, gl_project, build, return_to: request.original_url), title: 'Cancel build' do
- %i.fa.fa-remove.cred
- - elsif build.commands.present?
- = link_to retry_namespace_project_build_path(gl_project.namespace, gl_project, build, return_to: request.original_url), method: :post, title: 'Retry build' do
- %i.fa.fa-repeat
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index b561078e8c7..66e668f3771 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -7,9 +7,9 @@
%code #{@build.ref}
#up-build-trace
- - if @commit.matrix_for_ref?(@build.ref)
+ - if @commit.matrix?(@build.ref)
%ul.center-top-menu.build-top-menu
- - @commit.builds_without_retry_for_ref(@build.ref).each do |build|
+ - @commit.builds_without_retry(@build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to namespace_project_build_path(@project.namespace, @project, build) do
= ci_icon_for_status(build.status)
@@ -20,7 +20,7 @@
= build.id
- - unless @commit.builds_without_retry_for_ref(@build.ref).include?(@build)
+ - unless @commit.builds_without_retry(@build.ref).include?(@build)
%li.active
%a
Build ##{@build.id}
diff --git a/app/views/projects/commit/ci.html.haml b/app/views/projects/commit/ci.html.haml
index 26ab38445c2..7f106631cac 100644
--- a/app/views/projects/commit/ci.html.haml
+++ b/app/views/projects/commit/ci.html.haml
@@ -20,13 +20,35 @@
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
-- @ci_commit.refs.each do |ref|
+- if @ci_commit.refs.blank?
+ .gray-content-block.second-block
+ Latest builds
+ - if @ci_commit.duration > 0
+ %small.pull-right
+ %i.fa.fa-time
+ #{time_interval_in_words @ci_commit.duration}
+
+ %table.table.builds
+ %thead
+ %tr
+ %th Status
+ %th Build ID
+ %th Stage
+ %th Name
+ %th Duration
+ %th Finished at
+ - if @ci_project && @ci_project.coverage_enabled?
+ %th Coverage
+ %th
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.latest, coverage: @ci_project.try(:coverage_enabled?), controls: true
+
+- @ci_commit.refs.sort.each do |ref|
.gray-content-block.second-block
Builds for #{ref}
- - if @ci_commit.duration_for_ref(ref) > 0
+ - if @ci_commit.duration(ref) > 0
%small.pull-right
%i.fa.fa-time
- #{time_interval_in_words @ci_commit.duration_for_ref(ref)}
+ #{time_interval_in_words @ci_commit.duration(ref)}
%table.table.builds
%thead
@@ -40,10 +62,10 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
- = render partial: "projects/builds/build", collection: @ci_commit.builds_without_retry.for_ref(ref), controls: true
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.statuses.for_ref(ref).latest, coverage: @ci_project.try(:coverage_enabled?), controls: true
-- if @ci_commit.retried_builds.any?
- %h3
+- if @ci_commit.retried.any?
+ .gray-content-block.second-block
Retried builds
%table.table.builds
@@ -59,4 +81,4 @@
- if @ci_project && @ci_project.coverage_enabled?
%th Coverage
%th
- = render partial: "projects/builds/build", collection: @ci_commit.retried_builds, ref: true
+ = render partial: "projects/commit_statuses/commit_status", collection: @ci_commit.retried, coverage: @ci_project.try(:coverage_enabled?), ref: true
diff --git a/app/views/projects/commit_statuses/_commit_status.html.haml b/app/views/projects/commit_statuses/_commit_status.html.haml
new file mode 100644
index 00000000000..f79929c70bf
--- /dev/null
+++ b/app/views/projects/commit_statuses/_commit_status.html.haml
@@ -0,0 +1,52 @@
+%tr.commit_status
+ %td.status
+ = ci_status_with_icon(commit_status.status)
+
+ %td.commit_status-link
+ - if commit_status.target_url
+ = link_to commit_status.target_url do
+ %strong Build ##{commit_status.id}
+ - else
+ %strong Build ##{commit_status.id}
+
+ - if defined?(ref)
+ %td
+ = commit_status.ref
+
+ %td
+ = commit_status.stage
+
+ %td
+ = commit_status.description
+ .pull-right
+ - if commit_status.tags.any?
+ - commit_status.tags.each do |tag|
+ %span.label.label-primary
+ = tag
+ - if commit_status.try(:trigger_request)
+ %span.label.label-info triggered
+ - if commit_status.try(:allow_failure)
+ %span.label.label-danger allowed to fail
+
+ %td.duration
+ - if commit_status.duration
+ #{duration_in_words(commit_status.finished_at, commit_status.started_at)}
+
+ %td.timestamp
+ - if commit_status.finished_at
+ %span #{time_ago_in_words commit_status.finished_at} ago
+
+ - if defined?(coverage)
+ %td.coverage
+ - if commit_status.try(:coverage)
+ #{commit_status.coverage}%
+
+ %td
+ - if defined?(controls) && current_user && can?(current_user, :manage_builds, gl_project)
+ .pull-right
+ - if commit_status.active?
+ = link_to cancel_namespace_project_build_path(gl_project.namespace, gl_project, commit_status, return_to: request.original_url), title: 'Cancel commit_status' do
+ %i.fa.fa-remove.cred
+ - elsif commit_status.commands.present?
+ = link_to retry_namespace_project_build_path(gl_project.namespace, gl_project, commit_status, return_to: request.original_url), method: :post, title: 'Retry commit_status' do
+ %i.fa.fa-repeat