diff options
Diffstat (limited to 'app/models')
29 files changed, 366 insertions, 231 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb index 11ada46610b..b72178fa126 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -41,6 +41,7 @@ class Ability :read_project_member, :read_merge_request, :read_note, + :read_build, :download_code ] @@ -127,6 +128,7 @@ class Ability :read_project_member, :read_merge_request, :read_note, + :read_build, :create_project, :create_issue, :create_note @@ -135,6 +137,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..b19e2ac1363 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,57 +86,24 @@ 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? Ci::WebHookService.new.build_end(build) end - if build.commit.should_create_next_builds?(build) - build.commit.create_next_builds(build.ref, build.tag, build.user, build.trigger_request) - end - + build.commit.create_next_builds(build) project.execute_services(build) if project.coverage_enabled? 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,36 +111,12 @@ 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 def variables - 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 + predefined_variables + yaml_variables + project_variables + trigger_variables end def project @@ -278,6 +209,37 @@ 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) + end + end + + def retry_url + if commands.present? + Gitlab::Application.routes.url_helpers. + retry_namespace_project_build_path(gl_project.namespace, gl_project, self) + end + end + + def can_be_served?(runner) + (tag_list - runner.tag_list).empty? + end + + def any_runners_online? + project.any_runners? { |runner| runner.active? && runner.online? && can_be_served?(runner) } + end + + def show_warning? + pending? && !any_runners_online? + end + private def yaml_variables @@ -305,5 +267,14 @@ module Ci [] end end + + def predefined_variables + variables = [] + variables << { key: :CI_BUILD_TAG, value: ref, public: true } if tag? + variables << { key: :CI_BUILD_NAME, value: name, public: true } + variables << { key: :CI_BUILD_STAGE, value: stage, public: true } + variables << { key: :CI_BUILD_TRIGGERED, value: 'true', public: true } if trigger_request + variables + end end end diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index fde754a92a1..13437b2483f 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -20,9 +20,12 @@ 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' + scope :ordered, -> { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) } + validates_presence_of :sha validate :valid_commit_sha @@ -47,7 +50,7 @@ module Ci end def retry - builds_without_retry.each do |build| + latest_builds.each do |build| Ci::Build.retry(build) end end @@ -81,87 +84,76 @@ 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? + CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? end end - def create_next_builds(ref, tag, user, trigger_request) - return if skip_ci? && trigger_request.blank? + def create_next_builds(build) return unless config_processor - stages = builds.where(ref: ref, tag: tag, trigger_request: trigger_request).group_by(&:stage) + # don't create other builds if this one is retried + latest_builds = builds.similar(build).latest + return unless latest_builds.exists?(build.id) - config_processor.stages.any? do |stage| - unless stages.include?(stage) - CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request).present? - end + # 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 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 ||= Ci::Status.get_status(latest_statuses) 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 +161,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 +183,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 +204,6 @@ module Ci end def skip_ci? - return false if builds.any? git_commit_message =~ /(\[ci skip\])/ if git_commit_message end @@ -225,16 +211,6 @@ module Ci update!(committed_at: DateTime.now) end - def should_create_next_builds?(build) - # don't create other builds if this one is retried - other_builds = builds.similar(build).latest - return false unless other_builds.include?(build) - - other_builds.all? do |build| - build.success? || build.ignored? - end - end - private def save_yaml_error(error) diff --git a/app/models/ci/project.rb b/app/models/ci/project.rb index 88ba933a434..eb65c773570 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -115,12 +115,12 @@ module Ci web_url end - def any_runners? - if runners.active.any? + def any_runners?(&block) + if runners.active.any?(&block) return true end - shared_runners_enabled && Ci::Runner.shared.active.any? + shared_runners_enabled && Ci::Runner.shared.active.any?(&block) end def set_default_values @@ -205,7 +205,7 @@ module Ci end def commits - gl_project.ci_commits + gl_project.ci_commits.ordered end def builds diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 6838ccfaaab..1b3669f1b7a 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -20,6 +20,8 @@ module Ci class Runner < ActiveRecord::Base extend Ci::Model + + LAST_CONTACT_TIME = 5.minutes.ago has_many :builds, class_name: 'Ci::Build' has_many :runner_projects, dependent: :destroy, class_name: 'Ci::RunnerProject' @@ -33,6 +35,7 @@ module Ci scope :shared, ->() { where(is_shared: true) } scope :active, ->() { where(active: true) } scope :paused, ->() { where(active: false) } + scope :online, ->() { where('contacted_at > ?', LAST_CONTACT_TIME) } acts_as_taggable @@ -56,7 +59,7 @@ module Ci end def display_name - return token unless !description.blank? + return short_sha unless !description.blank? description end @@ -65,6 +68,20 @@ module Ci is_shared end + def online? + contacted_at && contacted_at > LAST_CONTACT_TIME + end + + def status + if contacted_at.nil? + :not_connected + elsif active? + online? ? :online : :offline + else + :paused + end + end + def belongs_to_one_project? runner_projects.count == 1 end @@ -78,7 +95,7 @@ module Ci end def short_sha - token[0...10] + token[0...8] if token end end 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..8188ba3a28e --- /dev/null +++ b/app/models/commit_status.rb @@ -0,0 +1,96 @@ +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 :finished, -> { where(status:[:success, :failed, :canceled]) } + 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 [:pending, :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 + + def show_warning? + false + end +end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 4db4ffb2e79..0e8bcc1a4ec 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -47,7 +47,8 @@ module Issuable prefix: true attr_mentionable :title, :description - participant :author, :assignee, :notes, :mentioned_users + + participant :author, :assignee, :notes_with_associations, :mentioned_users end module ClassMethods @@ -176,6 +177,10 @@ module Issuable self.class.to_s.underscore end + def notes_with_associations + notes.includes(:author, :project) + end + private def filter_superceded_votes(votes, notes) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 5b0ae411642..b34def66d2e 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -41,55 +41,49 @@ module Mentionable self end - # Determine whether or not a cross-reference Note has already been created between this Mentionable and - # the specified target. - def has_mentioned?(target) - SystemNoteService.cross_reference_exists?(target, local_reference) + def all_references(current_user = self.author, text = self.mentionable_text) + ext = Gitlab::ReferenceExtractor.new(self.project, current_user) + ext.analyze(text) + ext end def mentioned_users(current_user = nil) - return [] if mentionable_text.blank? - - ext = Gitlab::ReferenceExtractor.new(self.project, current_user) - ext.analyze(mentionable_text) - ext.users.uniq + all_references(current_user).users.uniq end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def references(p = project, current_user = self.author, text = mentionable_text) + def referenced_mentionables(current_user = self.author, text = self.mentionable_text) return [] if text.blank? - ext = Gitlab::ReferenceExtractor.new(p, current_user) - ext.analyze(text) - - (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference] + refs = all_references(current_user, text) + (refs.issues + refs.merge_requests + refs.commits).uniq - [local_reference] end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. - def create_cross_references!(p = project, a = author, without = []) - refs = references(p) - + def create_cross_references!(author = self.author, without = [], text = self.mentionable_text) + refs = referenced_mentionables(author, text) + # We're using this method instead of Array diffing because that requires # both of the object's `hash` values to be the same, which may not be the # case for otherwise identical Commit objects. - refs.reject! { |ref| without.include?(ref) } + refs.reject! { |ref| without.include?(ref) || cross_reference_exists?(ref) } refs.each do |ref| - SystemNoteService.cross_reference(ref, local_reference, a) + SystemNoteService.cross_reference(ref, local_reference, author) end end # When a mentionable field is changed, creates cross-reference notes that # don't already exist - def create_new_cross_references!(p = project, a = author) + def create_new_cross_references!(author = self.author) changes = detect_mentionable_changes return if changes.empty? original_text = changes.collect { |_, vals| vals.first }.join(' ') - preexisting = references(p, self.author, original_text) - create_cross_references!(p, a, preexisting) + preexisting = referenced_mentionables(author, original_text) + create_cross_references!(author, preexisting) end private @@ -111,4 +105,10 @@ module Mentionable # Only include changed fields that are mentionable source.select { |key, val| mentionable.include?(key) } end + + # Determine whether or not a cross-reference Note has already been created between this Mentionable and + # the specified target. + def cross_reference_exists?(target) + SystemNoteService.cross_reference_exists?(target, local_reference) + end end diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index 7c9597333dd..ffc874357fd 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -37,8 +37,8 @@ module Participable # Be aware that this method makes a lot of sql queries. # Save result into variable if you are going to reuse it inside same request - def participants(current_user = self.author, project = self.project) - participants = self.class.participant_attrs.flat_map do |attr| + def participants(current_user = self.author) + self.class.participant_attrs.flat_map do |attr| meth = method(attr) value = @@ -48,28 +48,22 @@ module Participable meth.call end - participants_for(value, current_user, project) - end.compact.uniq - - if project - participants.select! do |user| - user.can?(:read_project, project) - end + participants_for(value, current_user) + end.compact.uniq.select do |user| + user.can?(:read_project, self.project) end - - participants end private - def participants_for(value, current_user = nil, project = nil) + def participants_for(value, current_user = nil) case value when User [value] when Enumerable, ActiveRecord::Relation - value.flat_map { |v| participants_for(v, current_user, project) } + value.flat_map { |v| participants_for(v, current_user) } when Participable - value.participants(current_user, project) + value.participants(current_user) 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/models/group.rb b/app/models/group.rb index 9cd146bb73b..465c22d23ac 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -64,7 +64,7 @@ class Group < Namespace end def owners - @owners ||= group_members.owners.map(&:user) + @owners ||= group_members.owners.includes(:user).map(&:user) end def add_users(user_ids, access_level, current_user = nil) diff --git a/app/models/group_label.rb b/app/models/group_label.rb new file mode 100644 index 00000000000..0fc39cb8771 --- /dev/null +++ b/app/models/group_label.rb @@ -0,0 +1,9 @@ +class GroupLabel + attr_accessor :title, :labels + alias_attribute :name, :title + + def initialize(title, labels) + @title = title + @labels = labels + end +end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 1dd2be68ebf..91844da62e2 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -1,5 +1,5 @@ class GroupMilestone - + attr_accessor :title, :milestones alias_attribute :name, :title def initialize(title, milestones) @@ -7,18 +7,10 @@ class GroupMilestone @milestones = milestones end - def title - @title - end - def safe_title @title.parameterize end - - def milestones - @milestones - end - + def projects milestones.map { |milestone| milestone.project } 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/merge_request_diff.rb b/app/models/merge_request_diff.rb index c9ef8023aea..bc2d691ece0 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -163,7 +163,8 @@ class MergeRequestDiff < ActiveRecord::Base merge_request.fetch_ref # Get latest sha of branch from source project - source_sha = merge_request.source_project.commit(source_branch).sha + source_commit = merge_request.source_project.commit(source_branch) + source_sha = source_commit.try(:sha) Gitlab::CompareResult.new( Gitlab::Git::Compare.new( diff --git a/app/models/namespace.rb b/app/models/namespace.rb index bc8525df5a5..5782e649f8b 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -118,6 +118,8 @@ class Namespace < ActiveRecord::Base gitlab_shell.add_namespace(path_was) if gitlab_shell.mv_namespace(path_was, path) + Gitlab::UploadsTransfer.new.rename_namespace(path_was, path) + # If repositories moved successfully we need to # send update instructions to users. # However we cannot allow rollback since we moved namespace dir diff --git a/app/models/note.rb b/app/models/note.rb index de3b6df88f7..ebbdd2f33a1 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -60,9 +60,13 @@ class Note < ActiveRecord::Base scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } + scope :with_associations, -> do + includes(:author, :noteable, :updated_by, + project: [:project_members, { group: [:group_members] }]) + end + serialize :st_diff before_create :set_diff, if: ->(n) { n.line_code.present? } - after_update :set_references class << self def discussions_from_notes(notes) @@ -333,15 +337,13 @@ class Note < ActiveRecord::Base end def noteable_type_name - if noteable_type.present? - noteable_type.downcase - end + noteable_type.downcase if noteable_type.present? end # FIXME: Hack for polymorphic associations with STI # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations - def noteable_type=(sType) - super(sType.to_s.classify.constantize.base_class.to_s) + def noteable_type=(noteable_type) + super(noteable_type.to_s.classify.constantize.base_class.to_s) end # Reset notes events cache @@ -357,10 +359,6 @@ class Note < ActiveRecord::Base Event.reset_event_cache_for(self) end - def set_references - create_new_cross_references!(project, author) - end - def system? read_attribute(:system) end diff --git a/app/models/project.rb b/app/models/project.rb index 021920008ad..88cd88dcb5a 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -119,7 +119,7 @@ class Project < ActiveRecord::Base has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy has_many :starrers, through: :users_star_projects, source: :user - has_many :ci_commits, ->() { order('CASE WHEN ci_commits.committed_at IS NULL THEN 0 ELSE 1 END', :committed_at, :id) }, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id + has_many :ci_commits, dependent: :destroy, class_name: 'Ci::Commit', foreign_key: :gl_project_id has_many :ci_builds, through: :ci_commits, source: :builds, dependent: :destroy, class_name: 'Ci::Build' has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" @@ -656,6 +656,8 @@ class Project < ActiveRecord::Base # db changes in order to prevent out of sync between db and fs raise Exception.new('repository cannot be renamed') end + + Gitlab::UploadsTransfer.new.rename_project(path_was, path, namespace.path) end def hook_attrs diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb index 5f5255ab487..d31b12f539e 100644 --- a/app/models/project_services/bamboo_service.rb +++ b/app/models/project_services/bamboo_service.rb @@ -48,7 +48,7 @@ class BambooService < CiService end def reset_password - if prop_updated?(:bamboo_url) + if bamboo_url_changed? && !password_touched? self.password = nil end 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 fb11cad352e..0b022461250 100644 --- a/app/models/project_services/teamcity_service.rb +++ b/app/models/project_services/teamcity_service.rb @@ -45,7 +45,7 @@ class TeamcityService < CiService end def reset_password - if prop_updated?(:teamcity_url) + if teamcity_url_changed? && !password_touched? self.password = nil end end diff --git a/app/models/project_team.rb b/app/models/project_team.rb index f602a965364..9f380a382cb 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -139,15 +139,28 @@ class ProjectTeam Gitlab::Access.options.key max_member_access(user_id) end + # This method assumes project and group members are eager loaded for optimal + # performance. def max_member_access(user_id) access = [] - access << project.project_members.find_by(user_id: user_id).try(:access_field) + + project.project_members.each do |member| + if member.user_id == user_id + access << member.access_field if member.access_field + break + end + end if group - access << group.group_members.find_by(user_id: user_id).try(:access_field) + group.group_members.each do |member| + if member.user_id == user_id + access << member.access_field if member.access_field + break + end + end end - access.compact.max + access.max end private diff --git a/app/models/repository.rb b/app/models/repository.rb index 8b51602bc23..921e1a9e426 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -480,6 +480,10 @@ class Repository end end + def merge_base(first_commit_id, second_commit_id) + rugged.merge_base(first_commit_id, second_commit_id) + end + def search_files(query, ref) offset = 2 args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref}) diff --git a/app/models/service.rb b/app/models/service.rb index 7e845d565b1..d610abd1683 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -33,6 +33,8 @@ class Service < ActiveRecord::Base after_initialize :initialize_properties + after_commit :reset_updated_properties + belongs_to :project has_one :service_hook @@ -103,6 +105,7 @@ class Service < ActiveRecord::Base # Provide convenient accessor methods # for each serialized property. + # Also keep track of updated properties in a similar way as ActiveModel::Dirty def self.prop_accessor(*args) args.each do |arg| class_eval %{ @@ -111,21 +114,39 @@ class Service < ActiveRecord::Base end def #{arg}=(value) + updated_properties['#{arg}'] = #{arg} unless #{arg}_changed? self.properties['#{arg}'] = value end + + def #{arg}_changed? + #{arg}_touched? && #{arg} != #{arg}_was + end + + def #{arg}_touched? + updated_properties.include?('#{arg}') + end + + def #{arg}_was + updated_properties['#{arg}'] + end } 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) + # Returns a hash of the properties that have been assigned a new value since last save, + # indicating their original values (attr => original value). + # ActiveRecord does not provide a mechanism to track changes in serialized keys, + # so we need a specific implementation for service properties. + # This allows to track changes to properties set with the accessor methods, + # but not direct manipulation of properties hash. + def updated_properties + @updated_properties ||= ActiveSupport::HashWithIndifferentAccess.new end + def reset_updated_properties + @updated_properties = nil + end + def async_execute(data) return unless supported_events.include?(data[:object_kind]) diff --git a/app/models/user.rb b/app/models/user.rb index 889d2d3b867..17ccb3b8788 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -68,6 +68,7 @@ class User < ActiveRecord::Base include Referable include Sortable include TokenAuthenticatable + include CaseSensitivity default_value_for :admin, false default_value_for :can_create_group, gitlab_config.default_can_create_group @@ -273,8 +274,13 @@ class User < ActiveRecord::Base end def by_login(login) - where('lower(username) = :value OR lower(email) = :value', - value: login.to_s.downcase).first + return nil unless login + + if login.include?('@'.freeze) + unscoped.iwhere(email: login).take + else + unscoped.iwhere(username: login).take + end end def find_by_username!(username) |