Commit 5d69f5b4 authored by Kamil Trzciński's avatar Kamil Trzciński

Use Ci::Commit as Pipeline

parent 986b4a54
......@@ -94,8 +94,8 @@ class Projects::CommitController < Projects::ApplicationController
@commit ||= @project.commit(params[:id])
end
def ci_commit
@ci_commit ||= project.ci_commit(commit.sha)
def ci_commits
@ci_commits ||= project.ci_commits.where(sha: commit.sha)
end
def define_show_vars
......@@ -108,7 +108,8 @@ class Projects::CommitController < Projects::ApplicationController
@diff_refs = [commit.parent || commit, commit]
@notes_count = commit.notes.count
@statuses = ci_commit.statuses if ci_commit
@statuses = CommitStatus.where(commit: ci_commits)
@builds = Ci::Build.where(commit: ci_commits)
end
def assign_revert_commit_vars
......
......@@ -118,6 +118,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare
@ci_commit = @merge_request.ci_commit
@ci_commits = [@ci_commit].compact
@statuses = @ci_commit.statuses if @ci_commit
@note_counts = Note.where(commit_id: @commits.map(&:id)).
......@@ -308,6 +309,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request_diff = @merge_request.merge_request_diff
@ci_commit = @merge_request.ci_commit
@ci_commits = [@ci_commit].compact
@statuses = @ci_commit.statuses if @ci_commit
if @merge_request.locked_long_ago?
......@@ -318,6 +320,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def define_widget_vars
@ci_commit = @merge_request.ci_commit
@ci_commits = [@ci_commit].compact
closes_issues
end
......
module CiStatusHelper
def ci_status_path(ci_commit)
project = ci_commit.project
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
def ci_status_icon(ci_commit)
ci_icon_for_status(ci_commit.status)
end
def ci_status_label(ci_commit)
ci_label_for_status(ci_commit.status)
end
def ci_status_with_icon(status, target = nil)
content = ci_icon_for_status(status) + '&nbsp;'.html_safe + ci_label_for_status(status)
klass = "ci-status ci-#{status}"
......@@ -47,10 +34,10 @@ module CiStatusHelper
end
def render_ci_status(ci_commit, tooltip_placement: 'auto left')
link_to ci_status_icon(ci_commit),
ci_status_path(ci_commit),
link_to ci_icon_for_status(ci_commit.status),
project_ci_commit_path(ci_commit.project, ci_commit),
class: "ci-status-link ci-status-icon-#{ci_commit.status.dasherize}",
title: "Build #{ci_status_label(ci_commit)}",
title: "Build #{ci_label_for_status(ci_commit.status)}",
data: { toggle: 'tooltip', placement: tooltip_placement }
end
......
......@@ -25,10 +25,22 @@ module GitlabRoutingHelper
namespace_project_commits_path(project.namespace, project, @ref || project.repository.root_ref)
end
def project_pipelines_path(project, *args)
namespace_project_pipelines_path(project.namespace, project, *args)
end
def project_builds_path(project, *args)
namespace_project_builds_path(project.namespace, project, *args)
end
def project_commit_path(project, commit)
builds_namespace_project_commit_path(project.namespace, project, commit.id)
end
def project_ci_commit_path(project, ci_commit)
builds_namespace_project_commit_path(project.namespace, project, ci_commit.sha)
end
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
......
......@@ -50,7 +50,6 @@ module Ci
scope :unstarted, ->() { where(runner_id: nil) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :similar, ->(build) { where(ref: build.ref, tag: build.tag, trigger_request_id: build.trigger_request_id) }
mount_uploader :artifacts_file, ArtifactUploader
mount_uploader :artifacts_metadata, ArtifactUploader
......@@ -62,6 +61,8 @@ module Ci
before_destroy { project }
after_create :execute_hooks
class << self
def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name|
......@@ -126,12 +127,16 @@ module Ci
end
def retried?
!self.commit.latest_statuses_for_ref(self.ref).include?(self)
!self.commit.latest.include?(self)
end
def retry
Ci::Build.retry(self)
end
def depends_on_builds
# Get builds of the same type
latest_builds = self.commit.builds.similar(self).latest
latest_builds = self.commit.builds.latest
# Return builds from previous stages
latest_builds.where('stage_idx < ?', stage_idx)
......
......@@ -19,6 +19,7 @@
module Ci
class Commit < ActiveRecord::Base
extend Ci::Model
include CiStatus
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
has_many :statuses, class_name: 'CommitStatus'
......@@ -28,12 +29,18 @@ module Ci
validates_presence_of :sha
validate :valid_commit_sha
# Make sure that status is saved
before_save :status
before_save :started_at
before_save :finished_at
before_save :duration
def self.truncate_sha(sha)
sha[0...8]
end
def to_param
sha
def stages
statuses.group(:stage).order(:stage_idx).pluck(:stage)
end
def project_id
......@@ -68,15 +75,26 @@ module Ci
nil
end
def stage
running_or_pending = statuses.latest.running_or_pending.ordered
running_or_pending.first.try(:stage)
def branch?
!tag?
end
def retryable?
builds.latest.any? do |build|
build.failed? || build.retryable?
end
end
def invalidate
status = nil
started_at = nil
finished_at = nil
end
def create_builds(ref, tag, user, trigger_request = nil)
def create_builds(user, trigger_request = nil)
return unless config_processor
config_processor.stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present?
CreateBuildsService.new(self).execute(stage, user, 'success', trigger_request).present?
end
end
......@@ -84,7 +102,7 @@ module Ci
return unless config_processor
# don't create other builds if this one is retried
latest_builds = builds.similar(build).latest
latest_builds = builds.latest
return unless latest_builds.exists?(build.id)
# get list of stages after this build
......@@ -97,26 +115,12 @@ module Ci
# create builds for next stages based
next_stages.any? do |stage|
CreateBuildsService.new.execute(self, stage, build.ref, build.tag, build.user, build.trigger_request, status).present?
CreateBuildsService.new(self).execute(stage, build.user, status, build.trigger_request).present?
end
end
def refs
statuses.order(:ref).pluck(:ref).uniq
end
def latest_statuses
@latest_statuses ||= statuses.latest.to_a
end
def latest_statuses_for_ref(ref)
latest_statuses.select { |status| status.ref == ref }
end
def matrix_builds(build = nil)
matrix_builds = builds.latest.ordered
matrix_builds = matrix_builds.similar(build) if build
matrix_builds.to_a
def latest
statuses.latest
end
def retried
......@@ -124,56 +128,23 @@ module Ci
end
def status
if yaml_errors.present?
return 'failed'
end
@status ||= Ci::Status.get_status(latest_statuses)
end
def pending?
status == 'pending'
end
def running?
status == 'running'
end
def success?
status == 'success'
end
def failed?
status == 'failed'
end
def canceled?
status == 'canceled'
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
read_attribute(:status) || update_status
end
def duration
duration_array = statuses.map(&:duration).compact
duration_array.reduce(:+).to_i
read_attribute(:duration) || update_duration
end
def started_at
@started_at ||= statuses.order('started_at ASC').first.try(:started_at)
read_attribute(:started_at) || update_started_at
end
def finished_at
@finished_at ||= statuses.order('finished_at DESC').first.try(:finished_at)
read_attribute(:finished_at) || update_finished_at
end
def coverage
coverage_array = latest_statuses.map(&:coverage).compact
coverage_array = latest.map(&:coverage).compact
if coverage_array.size >= 1
'%.2f' % (coverage_array.reduce(:+) / coverage_array.size)
end
......@@ -191,6 +162,7 @@ module Ci
end
def ci_yaml_file
return nil if defined?(@ci_yaml_file)
@ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
......@@ -206,6 +178,32 @@ module Ci
private
def update_status
status =
if yaml_errors.present?
'failed'
else
latest.status
end
end
def update_started_at
started_at =
statuses.order(:id).first.try(:started_at)
end
def update_finished_at
finished_at =
statuses.order(id: :desc).first.try(:finished_at)
end
def update_duration
duration = begin
duration_array = latest.map(&:duration).compact
duration_array.reduce(:+).to_i
end
end
def save_yaml_error(error)
return if self.yaml_errors?
self.yaml_errors = error
......
......@@ -209,12 +209,13 @@ class Commit
@raw.short_id(7)
end
def ci_commit
project.ci_commit(sha)
def ci_commits
@ci_commits ||= project.ci_commits.where(sha: sha)
end
def status
ci_commit.try(:status) || :not_found
return @status if defined?(@status)
@status ||= ci_commits.status
end
def revert_branch_name
......
......@@ -33,6 +33,8 @@
#
class CommitStatus < ActiveRecord::Base
include CiStatus
self.table_name = 'ci_builds'
belongs_to :project, class_name: '::Project', foreign_key: :gl_project_id
......@@ -40,21 +42,13 @@ class CommitStatus < ActiveRecord::Base
belongs_to :user
validates :commit, presence: true
validates :status, inclusion: { in: %w(pending running failed success canceled) }
validates_presence_of :name
alias_attribute :author, :user
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 :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) }
scope :latest, -> { where(id: unscope(:select).select('max(id)').group(:name)) }
scope :ordered, -> { order(:ref, :stage_idx, :name) }
scope :for_ref, ->(ref) { where(ref: ref) }
AVAILABLE_STATUSES = ['pending', 'running', 'success', 'failed', 'canceled']
......@@ -87,31 +81,13 @@ class CommitStatus < ActiveRecord::Base
MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.commit.project, nil).trigger(commit_status)
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, to: :commit, prefix: false
# TODO: this should be removed with all references
def before_sha
Gitlab::Git::BLANK_SHA
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
after_transition any => any do |commit_status|
commit_status.commit.invalidate
commit_status.save
end
end
def complete?
canceled? || success? || failed?
end
delegate :before_sha, :sha, :short_sha, to: :commit, prefix: false
def ignored?
allow_failure? && (failed? || canceled?)
......
module CiStatus
extend ActiveSupport::Concern
module ClassMethods
def status
objs = all.to_a
if objs.none?
nil
elsif objs.all? { |status| status.success? || status.try(:ignored?) }
'success'
elsif objs.all?(&:pending?)
'pending'
elsif objs.any?(&:running?) || all.any?(&:pending?)
'running'
elsif objs.all?(&:canceled?)
'canceled'
else
'failed'
end
end
def duration
duration_array = all.map(&:duration).compact
duration_array.reduce(:+).to_i
end
end
included do
validates :status, inclusion: { in: %w(pending running failed success canceled) }
state_machine :status, initial: :pending do
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
state :success, value: 'success'
state :canceled, value: 'canceled'
end
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 :finished, -> { where(status: [:success, :failed, :canceled]) }
end
def started?
!pending? && !canceled? && started_at
end
def active?
running? || pending?
end
def complete?
canceled? || success? || failed?
end
end
\ No newline at end of file
......@@ -589,7 +589,7 @@ class MergeRequest < ActiveRecord::Base
end
def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
@ci_commit ||= source_project.ci_commit(last_commit.id, source_branch) if last_commit && source_project
end
def diff_refs
......
......@@ -919,12 +919,12 @@ class Project < ActiveRecord::Base
!namespace.share_with_group_lock
end
def ci_commit(sha)
ci_commits.find_by(sha: sha)
def ci_commit(sha, ref)
ci_commits.find_by(sha: sha, ref: ref)
end
def ensure_ci_commit(sha)
ci_commit(sha) || ci_commits.create(sha: sha)
def ensure_ci_commit(sha, ref)
ci_commit(sha, ref) || ci_commits.create(sha: sha, ref: ref)
end
def enable_ci
......
module Ci
class CreateBuildsService
def execute(commit, stage, ref, tag, user, trigger_request, status)
builds_attrs = commit.config_processor.builds_for_stage_and_ref(stage, ref, tag, trigger_request)
def initialize(commit)
@commit = commit
end
def execute(stage, user, status, trigger_request = nil)
builds_attrs = config_processor.builds_for_stage_and_ref(stage, @commit.ref, @commit.tag, trigger_request)
# check when to create next build
builds_attrs = builds_attrs.select do |build_attrs|
......@@ -17,7 +21,8 @@ module Ci
builds_attrs.map do |build_attrs|
# don't create the same build twice
unless commit.builds.find_by(ref: ref, tag: tag, trigger_request: trigger_request, name: build_attrs[:name])
unless commit.builds.find_by(ref: @commit.ref, tag: @commit.tag,
trigger_request: trigger_request, name: build_attrs[:name])
build_attrs.slice!(:name,
:commands,
:tag_list,
......@@ -26,17 +31,21 @@ module Ci
:stage,
:stage_idx)
build_attrs.merge!(ref: ref,
tag: tag,
build_attrs.merge!(ref: @commit.ref,
tag: @commit.tag,
trigger_request: trigger_request,
user: user,
project: commit.project)
project: @commit.project)
build = commit.builds.create!(build_attrs)
build.execute_hooks
build
@commit.builds.create!(build_attrs)
end
end
end
private
def config_processor
@config_processor ||= @commit.config_processor
end
end
end
......@@ -7,7 +7,7 @@ module Ci
# check if ref is tag
tag = project.repository.find_tag(ref).present?
ci_commit = project.ensure_ci_commit(commit.sha)
ci_commit = project.ci_commits.create(commit.sha, ref)
trigger_request = trigger.trigger_requests.create!(
variables: variables,
......
......@@ -2,6 +2,7 @@ class CreateCommitBuildsService
def execute(project, user, params)
return false unless project.builds_enabled?
before_sha = params[:checkout_sha] || params[:before]
sha = params[:checkout_sha] || params[:after]
origin_ref = params[:ref]
......@@ -10,15 +11,16 @@ class CreateCommitBuildsService
end
ref = Gitlab::Git.ref_name(origin_ref)
tag = Gitlab::Git.tag_ref?(origin_ref)
# Skip branch removal
if sha == Gitlab::Git::BLANK_SHA
return false
end
commit = project.ci_commit(sha)
commit = project.ci_commit(sha, ref)
unless commit
commit = project.ci_commits.new(sha: sha)
commit = project.ci_commits.new(sha: sha, ref: ref, before_sha: before_sha, tag: tag)
# Skip creating ci_commit when no gitlab-ci.yml is found
unless commit.ci_yaml_file
......@@ -32,8 +34,7 @@ class CreateCommitBuildsService
# Skip creating builds for commits that have [ci skip]
unless commit.skip_ci?
# Create builds for commit
tag = Gitlab::Git.tag_ref?(origin_ref)
commit.create_builds(ref, tag, user)
commit.create_builds(user)
end
commit
......
......@@ -117,7 +117,7 @@
%td.build-link
- if project
= link_to ci_status_path(build.commit) do
= link_to builds_namespace_project_commit_path(project.namespace, project, build.sha) do
%strong #{build.commit.short_sha}
%td.timestamp
......
.project-last-commit
- ci_commit = project.ci_commit(commit.sha)
- if ci_commit
= link_to ci_status_path(ci_commit), class: "ci-status ci-#{ci_commit.status}" do
= ci_status_icon(ci_commit)
= ci_status_label(ci_commit)
- if commit.status
= link_to project_commit_path(commit.project, commit), class: "ci-status ci-#{commit.status}" do
= ci_icon_for_status(commit.status)
= ci_label_for_status(commit.status)
= link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
= link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit), class: "commit-row-message"
......
......@@ -4,7 +4,7 @@
.build-page
.gray-content-block.top-block
Build ##{@build.id} for commit
%strong.monospace= link_to @build.commit.short_sha, ci_status_path(@build.commit)
%strong.monospace= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha)
from
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
- merge_request = @build.merge_request
......@@ -13,7 +13,7 @@
= link_to "merge request ##{merge_request.iid}", merge_request_path(merge_request)
#up-build-trace
- builds = @build.commit.matrix_builds(@build)
- builds = @build.commit.builds.latest.to_a
- if builds.size > 1
%ul.nav-links.no-top.no-bottom
- builds.each do |build|
......@@ -173,7 +173,7 @@
Commit
.pull-right
%small
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, @build.sha), class: "monospace"
%p
%span.attr-name Branch:
= link_to @build.ref, namespace_project_commits_path(@project.namespace, @project, @build.ref)
......@@ -196,7 +196,7 @@
.build-widget
%h4.title #{pluralize(@builds.count(:id), "other build")} for
= succeed ":" do
= link_to @build.commit.short_sha, ci_status_path(@build.commit), class: "monospace"
= link_to @build.commit.short_sha, builds_namespace_project_commit_path(@project.namespace, @project, build.sha), class: "monospace"
%table.table.builds
- @builds.each_with_index do |build, i|
%tr.build
......
......@@ -19,11 +19,12 @@
%td
= link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace"
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
- if !defined?(ref) || ref
%td
- if build.ref
= link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref)
- else
.light none
- if defined?(runner) && runner
%td
......@@ -48,6 +49,8 @@
%span.label.label-info triggered
- if build.try(:allow_failure)
%span.label.label-danger allowed to fail
- if defined?(retried) && retried
%span.label.label-warning retried
%td.duration
- if build.duration
......
.gray-content-block.middle-block
.pull-right
- if can?(current_user, :update_build, @ci_commit.project)
- if @ci_commit.builds.latest.failed.any?(&:retryable?)