# == Schema Information # # Table name: ci_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 # tag :boolean default(FALSE) # yaml_errors :text # committed_at :datetime # gl_project_id :integer # 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' has_many :builds, class_name: 'Ci::Build' has_many :trigger_requests, dependent: :destroy, class_name: 'Ci::TriggerRequest' 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]) } validates_presence_of :sha validates_presence_of :before_sha validates_presence_of :ref validate :valid_commit_sha # Make sure that status is saved before_save :status def ignored? false end def self.truncate_sha(sha) sha[0...8] end def self.retried all.map { |commit| commit.retried }.flatten end def self.statuses CommitStatus.where(commit: all) end def self.builds Ci::Build.where(commit: all) end def self.stages CommitStatus.where(commit: all) .group(:stage, :stage_idx).order(:stage_idx) .pluck(:stage, :stage_idx).map(&:first) end def stages statuses .group(:stage, :stage_idx).order(:stage_idx) .pluck(:stage, :stage_idx).map(&:first) end def to_param sha end def project_id project.id end def valid_commit_sha if self.sha == Gitlab::Git::BLANK_SHA self.errors.add(:sha, " cant be 00000000 (branch removal)") end end def git_author_name commit_data.author_name if commit_data end def git_author_email commit_data.author_email if commit_data end def git_commit_message commit_data.message if commit_data end def short_sha Ci::Commit.truncate_sha(sha) end def commit_data @commit ||= project.commit(sha) rescue nil end def origin_ref ref end def tag? Gitlab::Git::tag_ref?(origin_ref) end def branch? Gitlab::Git::branch_ref?(origin_ref) end def retryable? builds.latest.any? do |build| build.failed? || build.retryable? end end def create_builds(ref, tag, 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? end end def create_next_builds(build) return unless config_processor # don't create other builds if this one is retried latest_builds = builds.similar(build).latest return unless latest_builds.exists?(build.id) # get list of stages after this build next_stages = config_processor.stages.drop_while { |stage| stage != build.stage } next_stages.delete(build.stage) # get status for all prior builds prior_builds = latest_builds.reject { |other_build| next_stages.include?(other_build.stage) } status = Ci::Status.get_status(prior_builds) # 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? end end def latest statuses.latest end def matrix_builds(build = nil) matrix_builds = builds.latest.ordered matrix_builds = matrix_builds.similar(build) if build matrix_builds.to_a end def retried @retried ||= (statuses.order(id: :desc) - statuses.latest) end def status read_attribute(:status) || update_status end def duration duration_array = latest.map(&:duration).compact duration_array.reduce(:+).to_i end def started_at read_attribute(:started_at) || update_started_at end def finished_at read_attribute(:finished_at) || update_finished_at end 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 coverage coverage_array = latest_statuses.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end end def config_processor return nil unless ci_yaml_file @config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, project.path_with_namespace) rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e save_yaml_error(e.message) nil rescue save_yaml_error("Undefined error") nil end def ci_yaml_file @ci_yaml_file ||= begin blob = project.repository.blob_at(sha, '.gitlab-ci.yml') blob.load_all_data!(project.repository) blob.data end rescue nil end def skip_ci? git_commit_message =~ /(\[ci skip\])/ if git_commit_message end private def save_yaml_error(error) return if self.yaml_errors? self.yaml_errors = error save end end end