diff options
Diffstat (limited to 'app/models')
-rw-r--r-- | app/models/ability.rb | 5 | ||||
-rw-r--r-- | app/models/application_setting.rb | 4 | ||||
-rw-r--r-- | app/models/ci/build.rb | 5 | ||||
-rw-r--r-- | app/models/ci/commit.rb | 54 | ||||
-rw-r--r-- | app/models/ci/project.rb | 2 | ||||
-rw-r--r-- | app/models/ci/runner.rb | 4 | ||||
-rw-r--r-- | app/models/commit.rb | 12 | ||||
-rw-r--r-- | app/models/commit_status.rb | 3 | ||||
-rw-r--r-- | app/models/concerns/issuable.rb | 10 | ||||
-rw-r--r-- | app/models/concerns/mentionable.rb | 6 | ||||
-rw-r--r-- | app/models/group.rb | 2 | ||||
-rw-r--r-- | app/models/group_label.rb | 9 | ||||
-rw-r--r-- | app/models/group_milestone.rb | 12 | ||||
-rw-r--r-- | app/models/issue.rb | 10 | ||||
-rw-r--r-- | app/models/merge_request.rb | 22 | ||||
-rw-r--r-- | app/models/merge_request_diff.rb | 7 | ||||
-rw-r--r-- | app/models/milestone.rb | 32 | ||||
-rw-r--r-- | app/models/note.rb | 5 | ||||
-rw-r--r-- | app/models/project.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/bamboo_service.rb | 2 | ||||
-rw-r--r-- | app/models/project_services/teamcity_service.rb | 2 | ||||
-rw-r--r-- | app/models/project_team.rb | 19 | ||||
-rw-r--r-- | app/models/repository.rb | 28 | ||||
-rw-r--r-- | app/models/service.rb | 35 | ||||
-rw-r--r-- | app/models/user.rb | 27 |
25 files changed, 213 insertions, 106 deletions
diff --git a/app/models/ability.rb b/app/models/ability.rb index 77c121ca5e8..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 @@ -187,7 +189,8 @@ class Ability :change_visibility_level, :rename_project, :remove_project, - :archive_project + :archive_project, + :remove_fork_project ] end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c8841178e93..05430c2ee18 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -44,6 +44,10 @@ class ApplicationSetting < ActiveRecord::Base allow_blank: true, format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } + validates :admin_notification_email, + allow_blank: true, + email: true + validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? value.each do |level| diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5f8d44148ca..b19e2ac1363 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -93,10 +93,7 @@ module Ci 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? diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb index 68864edfbbf..13437b2483f 100644 --- a/app/models/ci/commit.rb +++ b/app/models/ci/commit.rb @@ -24,6 +24,8 @@ module Ci 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 @@ -89,19 +91,28 @@ module Ci 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).present? + CreateBuildsService.new.execute(self, stage, ref, tag, user, trigger_request, 'success').present? end end - def create_next_builds(ref, tag, user, trigger_request) + 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 @@ -130,24 +141,7 @@ module Ci return '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 + @status ||= Ci::Status.get_status(latest_statuses) end def pending? @@ -217,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 ef28353a30c..eb65c773570 100644 --- a/app/models/ci/project.rb +++ b/app/models/ci/project.rb @@ -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 02a3e9db1fa..1b3669f1b7a 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -59,7 +59,7 @@ module Ci end def display_name - return token unless !description.blank? + return short_sha unless !description.blank? description end @@ -95,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 23b5e38336c..492f6be1ce3 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -164,6 +164,14 @@ class Commit @committer ||= User.find_by_any_email(committer_email) end + def parents + @parents ||= parent_ids.map { |id| project.commit(id) } + end + + def parent + @parent ||= project.commit(self.parent_id) if self.parent_id + end + def notes project.notes.for_commit_id(self.id) end @@ -181,10 +189,6 @@ class Commit @raw.short_id(7) end - def parents - @parents ||= Commit.decorate(super, project) - end - def ci_commit project.ci_commit(sha) end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 92905c618eb..8188ba3a28e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -16,6 +16,7 @@ class CommitStatus < ActiveRecord::Base 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) } @@ -27,7 +28,7 @@ class CommitStatus < ActiveRecord::Base end event :drop do - transition running: :failed + transition [:pending, :running] => :failed end event :success do diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index e260eae8a43..54bfd4eced5 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -48,7 +48,7 @@ module Issuable attr_mentionable :title attr_mentionable :description, cache: true - participant :author, :assignee, :notes + participant :author, :assignee, :notes_with_associations end module ClassMethods @@ -86,6 +86,10 @@ module Issuable assignee_id_changed? end + def open? + opened? || reopened? + end + # # Votes # @@ -177,6 +181,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 25da102ba4c..86b009ac7ed 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -60,12 +60,12 @@ module Mentionable end def mentioned_users(current_user = nil, load_lazy_references: true) - all_references(current_user).users + all_references(current_user, load_lazy_references: load_lazy_references).users end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def referenced_mentionables(current_user = self.author, text = nil) - refs = all_references(current_user, text) + def referenced_mentionables(current_user = self.author, text = nil, load_lazy_references: true) + refs = all_references(current_user, text, load_lazy_references: load_lazy_references) (refs.issues + refs.merge_requests + refs.commits) - [local_reference] 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/issue.rb b/app/models/issue.rb index fc7e9abe29e..72183108033 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -95,4 +95,14 @@ class Issue < ActiveRecord::Base def source_project project end + + # From all notes on this issue, we'll select the system notes about linked + # merge requests. Of those, the MRs closing `self` are returned. + def closed_by_merge_requests(current_user = nil) + return [] unless open? + + notes.system.flat_map do |note| + note.all_references(current_user).merge_requests + end.uniq.select { |mr| mr.open? && mr.closes_issue?(self) } + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c83b15c7d39..21861a46a84 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -40,7 +40,7 @@ class MergeRequest < ActiveRecord::Base after_create :create_merge_request_diff after_update :update_merge_request_diff - delegate :commits, :diffs, :last_commit, :last_commit_short_sha, to: :merge_request_diff, prefix: nil + delegate :commits, :diffs, to: :merge_request_diff, prefix: nil # When this attribute is true some MR validation is ignored # It allows us to close or modify broken merge requests @@ -157,6 +157,18 @@ class MergeRequest < ActiveRecord::Base reference end + def last_commit + merge_request_diff ? merge_request_diff.last_commit : compare_commits.last + end + + def first_commit + merge_request_diff ? merge_request_diff.first_commit : compare_commits.first + end + + def last_commit_short_sha + last_commit.short_id + end + def validate_branches if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -222,10 +234,6 @@ class MergeRequest < ActiveRecord::Base self.target_project.events.where(target_id: self.id, target_type: "MergeRequest", action: Event::CLOSED).last end - def open? - opened? || reopened? - end - def work_in_progress? !!(title =~ /\A\[?WIP\]?:? /i) end @@ -294,6 +302,10 @@ class MergeRequest < ActiveRecord::Base target_project end + def closes_issue?(issue) + closes_issues.include?(issue) + end + # Return the set of issues that will be closed if this merge request is accepted. def closes_issues(current_user = self.author) if target_branch == project.default_branch diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index c9ef8023aea..6575d0bc81f 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -55,6 +55,10 @@ class MergeRequestDiff < ActiveRecord::Base commits.first end + def first_commit + commits.last + end + def last_commit_short_sha @last_commit_short_sha ||= last_commit.short_id end @@ -163,7 +167,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/milestone.rb b/app/models/milestone.rb index 84acba30b6b..2ff16e2825c 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -105,4 +105,36 @@ class Milestone < ActiveRecord::Base def author_id nil end + + # Sorts the issues for the given IDs. + # + # This method runs a single SQL query using a CASE statement to update the + # position of all issues in the current milestone (scoped to the list of IDs). + # + # Given the ids [10, 20, 30] this method produces a SQL query something like + # the following: + # + # UPDATE issues + # SET position = CASE + # WHEN id = 10 THEN 1 + # WHEN id = 20 THEN 2 + # WHEN id = 30 THEN 3 + # ELSE position + # END + # WHERE id IN (10, 20, 30); + # + # This method expects that the IDs given in `ids` are already Fixnums. + def sort_issues(ids) + pairs = [] + + ids.each_with_index do |id, index| + pairs << id + pairs << index + 1 + end + + conditions = 'WHEN id = ? THEN ? ' * ids.length + + issues.where(id: ids). + update_all(["position = CASE #{conditions} ELSE position END", *pairs]) + end end diff --git a/app/models/note.rb b/app/models/note.rb index 96cacd667c1..074766237f1 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -60,6 +60,11 @@ 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? } diff --git a/app/models/project.rb b/app/models/project.rb index cd30467fae3..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" 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/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..0808896fd87 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -8,6 +8,14 @@ class Repository attr_accessor :raw_repository, :path_with_namespace, :project + def self.clean_old_archives + repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path + + return unless File.directory?(repository_downloads_path) + + Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) + end + def initialize(path_with_namespace, default_branch = nil, project = nil) @path_with_namespace = path_with_namespace @project = project @@ -269,14 +277,6 @@ class Repository end # Remove archives older than 2 hours - def clean_old_archives - repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path - - return unless File.directory?(repository_downloads_path) - - Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) - end - def branches_sorted_by(value) case value when 'recently_updated' @@ -312,13 +312,7 @@ class Repository end def blob_for_diff(commit, diff) - file = blob_at(commit.id, diff.new_path) - - unless file - file = prev_blob_for_diff(commit, diff) - end - - file + blob_at(commit.id, diff.file_path) end def prev_blob_for_diff(commit, diff) @@ -480,6 +474,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..7e4321d5376 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 @@ -182,7 +183,7 @@ class User < ActiveRecord::Base # User's Project preference # Note: When adding an option, it MUST go on the end of the array. - enum project_view: [:readme, :activity] + enum project_view: [:readme, :activity, :files] alias_attribute :private_token, :authentication_token @@ -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) @@ -700,12 +706,15 @@ class User < ActiveRecord::Base end def toggle_star(project) - user_star_project = users_star_projects. - where(project: project, user: self).take - if user_star_project - user_star_project.destroy - else - UsersStarProject.create!(project: project, user: self) + UsersStarProject.transaction do + user_star_project = users_star_projects. + where(project: project, user: self).lock(true).first + + if user_star_project + user_star_project.destroy + else + UsersStarProject.create!(project: project, user: self) + end end end |