Commit 546a3c65 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Refactor commit and build

parent 0de7c83a
......@@ -18,7 +18,7 @@ module Ci
if commit
# Redirect to commit page
redirect_to ci_project_ref_commit_path(@project, @build.commit.ref, @build.commit.sha)
redirect_to ci_project_commit_path(@project, @build.commit)
return
end
end
......
......@@ -13,7 +13,7 @@ module Ci
end
def status
commit = Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
commit = Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id], params[:ref_id])
render json: commit.to_json(only: [:id, :sha], methods: [:status, :coverage])
rescue ActiveRecord::RecordNotFound
render json: { status: "not_found" }
......@@ -22,7 +22,7 @@ module Ci
def cancel
commit.builds.running_or_pending.each(&:cancel)
redirect_to ci_project_ref_commits_path(project, commit.ref, commit.sha)
redirect_to ci_project_commits_path(project, commit.sha)
end
private
......@@ -32,7 +32,7 @@ module Ci
end
def commit
@commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha_and_ref!(params[:id], params[:ref_id])
@commit ||= Ci::Project.find(params[:project_id]).commits.find_by_sha!(params[:id])
end
end
end
......@@ -19,7 +19,8 @@ module Ci
@ref = params[:ref]
@commits = @project.commits.reverse_order
@commits = @commits.where(ref: @ref) if @ref
# TODO: this is broken
# @commits = @commits.where(ref: @ref) if @ref
@commits = @commits.page(params[:page]).per(20)
end
......
module Ci
module CommitsHelper
def ci_commit_path(commit)
ci_project_ref_commits_path(commit.project, commit.ref, commit.sha)
ci_project_commits_path(commit.project, commit)
end
def commit_link(commit)
......
......@@ -26,7 +26,7 @@ module Ci
def yaml_web_editor_link(project)
commits = project.commits
if commits.any? && commits.last.push_data[:ci_yaml_file]
if commits.any? && commits.last.ci_yaml_file
"#{project.gitlab_url}/edit/master/.gitlab-ci.yml"
else
"#{project.gitlab_url}/new/master"
......
module CiStatusHelper
def ci_status_path(ci_commit)
ci_project_ref_commits_path(ci_commit.project, ci_commit.ref, ci_commit)
ci_project_commits_path(ci_commit.project, ci_commit)
end
def ci_status_icon(ci_commit)
......
......@@ -34,10 +34,12 @@ module Ci
belongs_to :trigger_request, class_name: 'Ci::TriggerRequest'
serialize :options
serialize :push_data
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") }
......@@ -45,6 +47,9 @@ module Ci
scope :failed, ->() { where(status: "failed") }
scope :unstarted, ->() { where(runner_id: nil) }
scope :running_or_pending, ->() { where(status:[:running, :pending]) }
scope :latest, ->() { group(:name).order(stage_idx: :asc, created_at: :desc) }
scope :ignore_failures, ->() { where(allow_failure: false) }
scope :for_ref, ->(ref) { where(ref: ref) }
acts_as_taggable
......@@ -82,6 +87,7 @@ module Ci
new_build.name = build.name
new_build.allow_failure = build.allow_failure
new_build.stage = build.stage
new_build.stage_idx = build.stage_idx
new_build.trigger_request = build.trigger_request
new_build.save
new_build
......@@ -187,6 +193,16 @@ module Ci
project.name
end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && push_data[:user_email].present?
recipients << push_data[:user_email]
end
recipients.uniq
end
def repo_url
project.repo_url_with_auth
end
......
......@@ -23,9 +23,7 @@ module Ci
has_many :builds, dependent: :destroy, class_name: 'Ci::Build'
has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest'
serialize :push_data
validates_presence_of :ref, :sha, :before_sha, :push_data
validates_presence_of :sha
validate :valid_commit_sha
def self.truncate_sha(sha)
......@@ -69,15 +67,15 @@ module Ci
end
def git_author_name
commit_data[:author][:name] if commit_data && commit_data[:author]
commit_data.author_name if commit_data
end
def git_author_email
commit_data[:author][:email] if commit_data && commit_data[:author]
commit_data.author_email if commit_data
end
def git_commit_message
commit_data[:message] if commit_data && commit_data[:message]
commit_data.message if commit_data
end
def short_before_sha
......@@ -89,84 +87,31 @@ module Ci
end
def commit_data
push_data[:commits].find do |commit|
commit[:id] == sha
end
@commit ||= gl_project.commit(sha)
rescue
nil
end
def project_recipients
recipients = project.email_recipients.split(' ')
if project.email_add_pusher? && push_data[:user_email].present?
recipients << push_data[:user_email]
end
recipients.uniq
end
def stage
return unless config_processor
stages = builds_without_retry.select(&:active?).map(&:stage)
config_processor.stages.find { |stage| stages.include? stage }
builds_without_retry.group(:stage_idx).select(:stage).last
end
def create_builds_for_stage(stage, trigger_request)
def create_builds(ref, tag, push_data, trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
builds.create!({
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
trigger_request: trigger_request,
})
end
CreateBuildsService.new.execute(self, config_processor, ref, tag, push_data, trigger_request)
end
def create_next_builds(trigger_request)
return if skip_ci? && trigger_request.blank?
return unless config_processor
stages = builds.where(trigger_request: trigger_request).group_by(&:stage)
config_processor.stages.any? do |stage|
!stages.include?(stage) && create_builds_for_stage(stage, trigger_request).present?
end
def refs
builds.group(:ref).pluck(:ref)
end
def create_builds(trigger_request = nil)
return if skip_ci? && trigger_request.blank?
return unless config_processor
config_processor.stages.any? do |stage|
create_builds_for_stage(stage, trigger_request).present?
end
def last_ref
builds.latest.first.try(:ref)
end
def builds_without_retry
@builds_without_retry ||=
begin
grouped_builds = builds.group_by(&:name)
grouped_builds.map do |name, builds|
builds.sort_by(&:id).last
end
end
end
def builds_without_retry_sorted
return builds_without_retry unless config_processor
stages = config_processor.stages
builds_without_retry.sort_by do |build|
[stages.index(build.stage) || -1, build.name || ""]
end
builds.latest
end
def retried_builds
......@@ -180,35 +125,32 @@ module Ci
return 'failed'
elsif builds.none?
return 'skipped'
elsif success?
'success'
elsif pending?
'pending'
elsif running?
'running'
elsif canceled?
'canceled'
end
statuses = builds_without_retry.ignore_failures.pluck(:status)
if statuses.all? { |status| status == 'success' }
return 'success'
elsif statuses.all? { |status| status == 'pending' }
return 'pending'
elsif statuses.include?('running') || statuses.include?('pending')
return 'running'
elsif statuses.all? { |status| status == 'canceled' }
return 'canceled'
else
'failed'
return 'failed'
end
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?
......@@ -216,15 +158,17 @@ module Ci
end
def canceled?
builds_without_retry.all? do |build|
build.canceled?
end
status == 'canceled'
end
def duration
@duration ||= builds_without_retry.select(&:duration).sum(&:duration).to_i
end
def duration_for_ref(ref)
builds_without_retry.for_ref(ref).select(&:duration).sum(&:duration).to_i
end
def finished_at
@finished_at ||= builds.order('finished_at DESC').first.try(:finished_at)
end
......@@ -238,12 +182,12 @@ module Ci
end
end
def matrix?
builds_without_retry.size > 1
def matrix_for_ref?(ref)
builds_without_retry.for_ref(ref).pluck(:id).size > 1
end
def config_processor
@config_processor ||= Ci::GitlabCiYamlProcessor.new(push_data[:ci_yaml_file])
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file)
rescue Ci::GitlabCiYamlProcessor::ValidationError => e
save_yaml_error(e.message)
nil
......@@ -253,10 +197,15 @@ module Ci
nil
end
def ci_yaml_file
gl_project.repository.blob_at(sha, '.gitlab-ci.yml')
rescue
nil
end
def skip_ci?
return false if builds.any?
commits = push_data[:commits]
commits.present? && commits.last[:message] =~ /(\[ci skip\])/
git_commit_message =~ /(\[ci skip\])/ if git_commit_message
end
def update_committed!
......
......@@ -28,18 +28,6 @@ module Ci
status
end
# only check for toggling build status within same ref.
def last_commit_changed_status?
ref = last_commit.ref
last_commits = commits.where(ref: ref).last(2)
if last_commits.size < 2
false
else
last_commits[0].status != last_commits[1].status
end
end
def last_commit_for_ref(ref)
commits.where(ref: ref).last
end
......
......@@ -744,7 +744,11 @@ class Project < ActiveRecord::Base
end
def ci_commit(sha)
gitlab_ci_project.commits.find_by(sha: sha) if gitlab_ci?
ci_commits.find_by(sha: sha)
end
def ensure_ci_commit(sha)
ci_commit(sha) || ci_commits.create(sha: sha)
end
def ensure_gitlab_ci_project
......
......@@ -13,7 +13,7 @@ module Ci
lines.push("<a href=\"#{ci_project_url(project)}\">#{project.name}</a> - ")
if commit.matrix?
lines.push("<a href=\"#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}\">Commit ##{commit.id}</a></br>")
lines.push("<a href=\"#{ci_project_commits_url(project, commit.sha)}\">Commit ##{commit.id}</a></br>")
else
first_build = commit.builds_without_retry.first
lines.push("<a href=\"#{ci_project_build_url(project, first_build)}\">Build '#{first_build.name}' ##{first_build.id}</a></br>")
......
......@@ -61,7 +61,7 @@ module Ci
end
def execute(build)
build.commit.project_recipients.each do |recipient|
build.project_recipients.each do |recipient|
case build.status.to_sym
when :success
mailer.build_success_email(build.id, recipient)
......
......@@ -48,7 +48,7 @@ module Ci
def attachment_message
out = "<#{ci_project_url(project)}|#{project_name}>: "
if commit.matrix?
out << "Commit <#{ci_project_ref_commits_url(project, commit.ref, commit.sha)}|\##{commit.id}> "
out << "Commit <#{ci_project_commits_url(project, commit.sha)}|\##{commit.id}> "
else
build = commit.builds_without_retry.first
out << "Build <#{ci_project_build_url(project, build)}|\##{build.id}> "
......
......@@ -63,7 +63,7 @@ class GitlabCiService < CiService
end
def get_ci_commit(sha, ref)
Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha_and_ref!(sha, ref)
Ci::Project.find(project.gitlab_ci_project).commits.find_by_sha!(sha)
end
def commit_status(sha, ref)
......@@ -80,7 +80,7 @@ class GitlabCiService < CiService
def build_page(sha, ref)
if project.gitlab_ci_project.present?
ci_project_ref_commits_url(project.gitlab_ci_project, ref, sha)
ci_project_commits_url(project.gitlab_ci_project, sha)
end
end
......
module Ci
class CreateBuildsService
def execute(commit, ref, tag, push_data, config_processor, trigger_request)
config_processor.stages.any? do |stage|
builds_attrs = config_processor.builds_for_stage_and_ref(stage, ref, tag)
builds_attrs.map do |build_attrs|
# don't create the same build twice
unless commit.builds.find_by_name_and_trigger_request(name: build_attrs[:name], ref: ref, tag: tag, trigger_request: trigger_request)
commit.builds.create!({
name: build_attrs[:name],
commands: build_attrs[:script],
tag_list: build_attrs[:tags],
options: build_attrs[:options],
allow_failure: build_attrs[:allow_failure],
stage: build_attrs[:stage],
stage_idx: build_attrs[:stage_idx],
trigger_request: trigger_request,
ref: ref,
tag: tag,
push_data: push_data,
})
end
end
end
end
end
end
......@@ -16,33 +16,22 @@ module Ci
return false
end
commit = project.commits.find_by_sha_and_ref(sha, ref)
# Create commit if not exists yet
unless commit
data = {
ref: ref,
sha: sha,
tag: origin_ref.start_with?('refs/tags/'),
before_sha: before_sha,
push_data: {
before: before_sha,
after: sha,
ref: ref,
user_name: params[:user_name],
user_email: params[:user_email],
repository: params[:repository],
commits: params[:commits],
total_commits_count: params[:total_commits_count],
ci_yaml_file: params[:ci_yaml_file]
}
}
commit = project.commits.create(data)
end
tag = origin_ref.start_with?('refs/tags/')
push_data = {
before: before_sha,
after: sha,
ref: ref,
user_name: params[:user_name],
user_email: params[:user_email],
repository: params[:repository],
commits: params[:commits],
total_commits_count: params[:total_commits_count],
ci_yaml_file: params[:ci_yaml_file]
}
commit = project.gl_project.ensure_ci_commit(sha)
commit.update_committed!
commit.create_builds unless commit.builds.any?
commit.create_builds(ref, tag, push_data)
commit
end
......
module Ci
class CreateTriggerRequestService
def execute(project, trigger, ref, variables = nil)
commit = project.commits.where(ref: ref).last
commit = project.gl_project.commit(ref)
return unless commit
ci_commit = project.gl_project.ensure_ci_commit(commit.sha)
trigger_request = trigger.trigger_requests.create!(
commit: commit,
variables: variables
)
if commit.create_builds(trigger_request)
if ci_commit.create_builds(ref, tag, nil, trigger_request)
trigger_request
end
end
......
......@@ -6,6 +6,10 @@
= link_to ci_project_build_path(build.project, build) do
%strong Build ##{build.id}
- if defined?(ref)
%td
= build.ref
%td
= build.stage
......
#up-build-trace
- if @commit.matrix?
- if @commit.matrix_for_ref?(@build.ref)
%ul.center-top-menu
- @commit.builds_without_retry_sorted.each do |build|
- @commit.builds_without_retry.for_ref(build.ref).each do |build|
%li{class: ('active' if build == @build) }
= link_to ci_project_build_url(@project, build) do
= ci_icon_for_status(build.status)
......@@ -12,7 +12,7 @@
= build.id
- unless @commit.builds_without_retry.include?(@build)
- unless @commit.builds_without_retry.for_ref(@build.ref).include?(@build)
%li.active
%a
Build ##{@build.id}
......
......@@ -7,7 +7,7 @@
%td.build-link
= link_to ci_project_ref_commits_path(commit.project, commit.ref, commit.sha) do
= link_to ci_project_commits_path(commit.project, commit.sha) do
%strong #{commit.short_sha}
%td.build-message
......@@ -16,7 +16,7 @@
%td.build-branch
- unless @ref
%span
= link_to truncate(commit.ref, length: 25), ci_project_path(@project, ref: commit.ref)
= link_to truncate(commit.last_ref, length: 25), ci_project_path(@project, ref: commit.last_ref)
%td.duration
- if commit.duration > 0
......
......@@ -17,14 +17,11 @@
%p
%span.attr-name Commit:
#{gitlab_commit_link(@project, @commit.sha)}
%p
%span.attr-name Branch:
#{gitlab_ref_link(@project, @commit.ref)}
.col-sm-6
%p
%span.attr-name Author:
#{@commit.git_author_name} (#{@commit.git_author_email})
- if @commit.git_author_name || @commit.git_author_email
%p
%span.attr-name Author:
#{@commit.git_author_name} (#{@commit.git_author_email})
- if @commit.created_at
%p
%span.attr-name Created at:
......@@ -33,7 +30,7 @@
- if current_user && can?(current_user, :manage_builds, gl_project)
.pull-right
- if @commit.builds.running_or_pending.any?
= link_to "Cancel", cancel_ci_project_ref_commits_path(@project, @commit.ref, @commit.sha), class: 'btn btn-sm btn-danger'
= link_to "Cancel", cancel_ci_project_commits_path(@project, @commit), class: 'btn btn-sm btn-danger'
- if @commit.yaml_errors.present?
......@@ -43,30 +40,31 @@
- @commit.yaml_errors.split(",").each do |error|
%li= error
- unless @commit.push_data[:ci_yaml_file]
- unless @commit.ci_yaml_file
.bs-callout.bs-callout-warning
\.gitlab-ci.yml not found in this commit
%h3
Builds
- if @commit.duration > 0
%small.pull-right
%i.fa.fa-time
#{time_interval_in_words @commit.duration}
- @commit.refs.each do |ref|
%h3
Builds for #{ref}
- if @commit.duration_for_ref(ref) > 0
%small.pull-right
%i.fa.fa-time
#{time_interval_in_words @commit.duration_for_ref(ref)}
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Stage
%th Name
%th Duration
%th Finished at
- if @project.coverage_enabled?
%th Coverage
%th
= render @commit.builds_without_retry_sorted, controls: true
%table.table.builds
%thead
%tr
%th Status
%th Build ID
%th Stage
%th Name
%th Duration
%th Finished at
- if @project.coverage_enabled?
%th Coverage
%th
= render @commit.builds_without_retry.for_ref(ref), controls: true
- if @commit.retried_builds.any?
%h3
......@@ -77,6 +75,7 @@
%tr
%th Status
%th Build ID
%th Ref
%th Stage
%th Name
%th Duration
......@@ -84,4 +83,4 @@
- if @project.coverage_enabled?
%th Coverage
%th
= render @commit.retried_builds
= render @commit.retried_builds, ref: true
......@@ -11,7 +11,7 @@
%p
Author: #{@build.commit.git_author_name}
%p
Branch: #{@build.commit.ref}
Branch: #{@build.ref}
%p
Message: #{@build.commit.git_commit_message}
......
......@@ -3,7 +3,7 @@ Build failed for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.commit.short_sha %>
Author: <%= @build.commit.git_author_name %>
Branch: <%= @build.commit.ref %>
Branch: <%= @build.ref %>
Message: <%= @build.commit.git_commit_message %>
Url: <%= ci_project_build_url(@build.project, @build) %>
......@@ -12,7 +12,7 @@
%p
Author: #{@build.commit.git_author_name}
%p
Branch: #{@build.commit.ref}
Branch: #{@build.ref}
%p
Message: #{@build.commit.git_commit_message}
......
......@@ -3,7 +3,7 @@ Build successful for <%= @project.name %>
Status: <%= @build.status %>
Commit: <%= @build.commit.short_sha %>
Author: <%= @build.commit.git_author_name %>
Branch: <%= @build.commit.ref %>
Branch: <%= @build.ref %>
Message: <%= @build.commit.git_commit_message %>
Url: <%= ci_project_build_url(@build.project, @build) %>
......@@ -30,12 +30,10 @@ Gitlab::Application.routes.draw do
resource :charts, only: [:show]
resources :refs, constraints: { ref_id: /.*/ }, only: [] do
resources :commits, only: [:show] do
member do
get :status
get :cancel
end
resources :commits, only: [:show] do
member do
get :status
get :cancel
end
end
......
class AddStageIdxToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :stage_idx, :integer
end
end
class AddIndexForBuildName < ActiveRecord::Migration
def up
add_index :ci_builds, [:commit_id, :stage_idx, :created_at]
end
end
class AddShaAndRefToBuilds < ActiveRecord::Migration
def change
add_column :ci_builds, :tag, :boolean
add_column :ci_builds, :ref, :string
add_column :ci_builds, :push_data, :text
end
end
class MigrateShaAndRefToBuild < ActiveRecord::Migration
def change
execute('UPDATE ci_builds SET ref=(SELECT ref FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE ref IS NULL')
execute('UPDATE ci_builds SET push_data=(SELECT push_data FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE push_data IS NULL')
execute('UPDATE ci_builds SET tag=(SELECT tag FROM ci_commits WHERE ci_commits.id = ci_builds.commit_id) WHERE tag IS NULL')
end
end
......@@ -85,6 +85,7 @@ module Ci
def build_job(name, job)
{
stage_idx: stages.index(job[:stage]),
stage: job[:stage],
script: "#{@before_script.join("\n")}\n#{normalize_script(job[:script])}",
tags: job[:tags] || [],
......
......@@ -7,7 +7,7 @@ describe "Commits" do
describe "GET /:project/refs/:ref_name/commits/:id/status.json" do
before do
get status_ci_project_ref_commits_path(@commit.project, @commit.ref, @commit.sha), format: :json
get status_ci_project_commits_path(@commit.project, @commit.sha), format: :json
end
it { expect(response.status).to eq(200) }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment