diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/ability.rb | 2 | ||||
-rw-r--r-- | app/models/ci/build.rb | 97 | ||||
-rw-r--r-- | app/models/ci/commit.rb | 94 | ||||
-rw-r--r-- | app/models/commit.rb | 8 | ||||
-rw-r--r-- | app/models/commit_status.rb | 91 | ||||
-rw-r--r-- | app/models/generic_commit_status.rb | 15 | ||||
-rw-r--r-- | app/models/merge_request.rb | 5 | ||||
-rw-r--r-- | app/models/project_services/bamboo_service.rb | 7 | ||||
-rw-r--r-- | app/models/project_services/ci/hip_chat_service.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/ci/mail_service.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/ci/slack_message.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/ci/slack_service.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/teamcity_service.rb | 7 | ||||
-rw-r--r-- | app/models/service.rb | 9 |
14 files changed, 214 insertions, 129 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..f8c731a7bf7 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,25 @@ module Ci "#{dir_to_trace}/#{id}.log" end + def target_url + Gitlab::Application.routes.url_helpers. + namespace_project_build_url(gl_project.namespace, gl_project, self) + end + + def cancel_url + if active? + Gitlab::Application.routes.url_helpers. + cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url) + end + end + + def retry_url + if commands.present? + Gitlab::Application.routes.url_helpers. + cancel_namespace_project_build_path(gl_project.namespace, gl_project, self, return_to: request.original_url) + end + end + private def yaml_variables diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index fde754a92a1..68864edfbbf 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 @@ -47,7 +48,7 @@ module Ci end def retry - builds_without_retry.each do |build| + latest_builds.each do |build| Ci::Build.retry(build) end end @@ -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.ordered + 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,61 +106,60 @@ module Ci end def refs - builds.group(:ref).pluck(:ref) + statuses.order(:ref).pluck(:ref).uniq end - def last_ref - builds.latest.first.try(:ref) + def latest_statuses + @latest_statuses ||= statuses.latest.to_a end - def builds_without_retry - builds.latest + def latest_builds + @latest_builds ||= builds.latest.to_a end - def builds_without_retry_for_ref(ref) - builds.for_ref(ref).latest + def latest_builds_for_ref(ref) + latest_builds.select { |build| build.ref == ref } 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? + if yaml_errors.present? return 'failed' - elsif builds.none? - return 'skipped' - elsif success? - 'success' - elsif pending? - 'pending' - elsif running? - 'running' - elsif canceled? - 'canceled' - else - 'failed' + end + + @status ||= begin + latest = latest_statuses + latest.reject! { |status| status.try(&:allow_failure?) } + + if latest.none? + 'skipped' + elsif latest.all?(&:success?) + 'success' + elsif latest.all?(&:pending?) + 'pending' + elsif latest.any?(&:running?) || latest.any?(&:pending?) + 'running' + elsif latest.all?(&:canceled?) + 'canceled' + else + 'failed' + end 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? @@ -169,26 +167,21 @@ 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 + duration_array = latest_statuses.map(&:duration).compact + duration_array.reduce(:+).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 if project.coverage_enabled? - coverage_array = builds_without_retry.map(&:coverage).compact + coverage_array = latest_builds.map(&:coverage).compact if coverage_array.size >= 1 '%.2f' % (coverage_array.reduce(:+) / coverage_array.size) end @@ -196,7 +189,7 @@ module Ci end def matrix_for_ref?(ref) - builds_without_retry_for_ref(ref).pluck(:id).size > 1 + latest_builds_for_ref(ref).size > 1 end def config_processor @@ -217,7 +210,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..b4d91b1b0c3 --- /dev/null +++ b/app/models/commit_status.rb @@ -0,0 +1,91 @@ +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 + + 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 :latest, -> { where(id: unscope(:select).select('max(id)').group(:name, :ref)) } + scope :ordered, -> { order(:ref, :stage_idx, :name) } + scope :for_ref, ->(ref) { where(ref: ref) } + 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 + + # 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? + 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 + + def cancel_url + nil + end + + def retry_url + nil + 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/models/merge_request.rb b/app/models/merge_request.rb index eb468c6cd53..c83b15c7d39 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -227,7 +227,7 @@ class MergeRequest < ActiveRecord::Base end def work_in_progress? - title =~ /\A\[?WIP\]?:? /i + !!(title =~ /\A\[?WIP\]?:? /i) end def mergeable? @@ -275,7 +275,8 @@ class MergeRequest < ActiveRecord::Base attrs = { source: source_project.hook_attrs, target: target_project.hook_attrs, - last_commit: nil + last_commit: nil, + work_in_progress: work_in_progress? } unless last_commit.nil? diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index d8aedbd2ab4..5f5255ab487 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -40,12 +40,19 @@ class BambooService < CiService attr_accessor :response after_save :compose_service_hook, if: :activated? + before_update :reset_password def compose_service_hook hook = service_hook || build_service_hook hook.save end + def reset_password + if prop_updated?(:bamboo_url) + self.password = nil + end + end + def title 'Atlassian Bamboo CI' end diff --git a/app/models/project_services/ci/hip_chat_service.rb b/app/models/project_services/ci/hip_chat_service.rb index 0e6e97394bc..f17993d9f3b 100644 --- a/app/models/project_services/ci/hip_chat_service.rb +++ b/app/models/project_services/ci/hip_chat_service.rb @@ -49,7 +49,7 @@ module Ci commit = build.commit return unless commit - return unless commit.builds_without_retry.include? build + return unless commit.latest_builds.include? build case commit.status.to_sym when :failed diff --git a/app/models/project_services/ci/mail_service.rb b/app/models/project_services/ci/mail_service.rb index 11a2743f969..fd193301001 100644 --- a/app/models/project_services/ci/mail_service.rb +++ b/app/models/project_services/ci/mail_service.rb @@ -48,7 +48,7 @@ module Ci # it doesn't make sense to send emails for retried builds commit = build.commit return unless commit - return unless commit.builds_without_retry.include?(build) + return unless commit.latest_builds.include?(build) case build.status.to_sym when :failed diff --git a/app/models/project_services/ci/slack_message.rb b/app/models/project_services/ci/slack_message.rb index 5ac8907ecd0..dc050a3fc59 100644 --- a/app/models/project_services/ci/slack_message.rb +++ b/app/models/project_services/ci/slack_message.rb @@ -23,7 +23,7 @@ module Ci def attachments fields = [] - commit.builds_without_retry.each do |build| + commit.latest_builds.each do |build| next if build.allow_failure? next unless build.failed? fields << { diff --git a/app/models/project_services/ci/slack_service.rb b/app/models/project_services/ci/slack_service.rb index 76db573dc17..ee8e4988826 100644 --- a/app/models/project_services/ci/slack_service.rb +++ b/app/models/project_services/ci/slack_service.rb @@ -48,7 +48,7 @@ module Ci commit = build.commit return unless commit - return unless commit.builds_without_retry.include?(build) + return unless commit.latest_builds.include?(build) case commit.status.to_sym when :failed diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb index 3c002a1634b..fb11cad352e 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -37,12 +37,19 @@ class TeamcityService < CiService attr_accessor :response after_save :compose_service_hook, if: :activated? + before_update :reset_password def compose_service_hook hook = service_hook || build_service_hook hook.save end + def reset_password + if prop_updated?(:teamcity_url) + self.password = nil + end + end + def title 'JetBrains TeamCity CI' end diff --git a/app/models/service.rb b/app/models/service.rb index 60fcc9d2857..7e845d565b1 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -117,6 +117,15 @@ class Service < ActiveRecord::Base end end + # ActiveRecord does not provide a mechanism to track changes in serialized keys. + # This is why we need to perform extra query to do it mannually. + def prop_updated?(prop_name) + relation_name = self.type.underscore + previous_value = project.send(relation_name).send(prop_name) + return false if previous_value.nil? + previous_value != send(prop_name) + end + def async_execute(data) return unless supported_events.include?(data[:object_kind]) |