summaryrefslogtreecommitdiff
path: root/app/models
diff options
context:
space:
mode:
authorKushal Pandya <kushalspandya@gmail.com>2017-04-06 09:46:50 +0000
committerKushal Pandya <kushalspandya@gmail.com>2017-04-06 09:46:50 +0000
commit18506d4b8b8bc780b3b1e4c61339af38b5c49bb2 (patch)
tree6aab0c83abe14064433c326996ccbe8097495454 /app/models
parentcd5b36d04e79ed8fcd649127e0d47e09ec325242 (diff)
parent49bdd8d63b577f079cdc47f7dd10ba83c677771a (diff)
downloadgitlab-ce-18506d4b8b8bc780b3b1e4c61339af38b5c49bb2.tar.gz
Merge branch 'master' into '18471-restrict-tag-pushes-protected-tags'
# Conflicts: # app/assets/javascripts/dispatcher.js # app/assets/stylesheets/pages/projects.scss
Diffstat (limited to 'app/models')
-rw-r--r--app/models/application_setting.rb7
-rw-r--r--app/models/blob.rb18
-rw-r--r--app/models/board.rb4
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/ci/pipeline.rb7
-rw-r--r--app/models/commit_status.rb4
-rw-r--r--app/models/concerns/has_status.rb1
-rw-r--r--app/models/concerns/importable.rb3
-rw-r--r--app/models/concerns/issuable.rb4
-rw-r--r--app/models/concerns/repository_mirroring.rb17
-rw-r--r--app/models/concerns/spammable.rb2
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/issue.rb18
-rw-r--r--app/models/list.rb2
-rw-r--r--app/models/merge_request.rb12
-rw-r--r--app/models/merge_request_diff.rb18
-rw-r--r--app/models/milestone.rb17
-rw-r--r--app/models/namespace.rb30
-rw-r--r--app/models/note.rb5
-rw-r--r--app/models/notification_setting.rb17
-rw-r--r--app/models/project.rb29
-rw-r--r--app/models/project_services/chat_notification_service.rb27
-rw-r--r--app/models/project_services/jira_service.rb2
-rw-r--r--app/models/project_services/kubernetes_service.rb43
-rw-r--r--app/models/project_services/mattermost_service.rb16
-rw-r--r--app/models/project_services/mock_deployment_service.rb18
-rw-r--r--app/models/project_services/mock_monitoring_service.rb17
-rw-r--r--app/models/project_services/prometheus_service.rb17
-rw-r--r--app/models/project_services/slack_service.rb16
-rw-r--r--app/models/project_team.rb3
-rw-r--r--app/models/project_wiki.rb7
-rw-r--r--app/models/repository.rb53
-rw-r--r--app/models/route.rb4
-rw-r--r--app/models/service.rb6
-rw-r--r--app/models/snippet.rb3
-rw-r--r--app/models/system_note_metadata.rb11
-rw-r--r--app/models/user.rb34
37 files changed, 348 insertions, 152 deletions
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 671a0fe98cc..2961e16f5e0 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -131,6 +131,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :polling_interval_multiplier,
+ presence: true,
+ numericality: { greater_than_or_equal_to: 0 }
+
validates_each :restricted_visibility_levels do |record, attr, value|
value&.each do |level|
unless Gitlab::VisibilityLevel.options.has_value?(level)
@@ -233,7 +237,8 @@ class ApplicationSetting < ActiveRecord::Base
signup_enabled: Settings.gitlab['signup_enabled'],
terminal_max_session_time: 0,
two_factor_grace_period: 48,
- user_default_external: false
+ user_default_external: false,
+ polling_interval_multiplier: 1
}
end
diff --git a/app/models/blob.rb b/app/models/blob.rb
index 1376b86fdad..f82126f8e65 100644
--- a/app/models/blob.rb
+++ b/app/models/blob.rb
@@ -46,6 +46,18 @@ class Blob < SimpleDelegator
text? && language && language.name == 'SVG'
end
+ def pdf?
+ name && File.extname(name) == '.pdf'
+ end
+
+ def ipython_notebook?
+ text? && language&.name == 'Jupyter Notebook'
+ end
+
+ def sketch?
+ binary? && extname.downcase.delete('.') == 'sketch'
+ end
+
def size_within_svg_limits?
size <= MAXIMUM_SVG_SIZE
end
@@ -63,6 +75,12 @@ class Blob < SimpleDelegator
end
elsif image? || svg?
'image'
+ elsif pdf?
+ 'pdf'
+ elsif ipython_notebook?
+ 'notebook'
+ elsif sketch?
+ 'sketch'
elsif text?
'text'
else
diff --git a/app/models/board.rb b/app/models/board.rb
index 2780acc67c0..cf8317891b5 100644
--- a/app/models/board.rb
+++ b/app/models/board.rb
@@ -5,7 +5,7 @@ class Board < ActiveRecord::Base
validates :project, presence: true
- def done_list
- lists.merge(List.done).take
+ def closed_list
+ lists.merge(List.closed).take
end
end
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index ad0be70c32a..8431c5f228c 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -540,6 +540,8 @@ module Ci
end
def dependencies
+ return [] if empty_dependencies?
+
depended_jobs = depends_on_builds
return depended_jobs unless options[:dependencies].present?
@@ -549,6 +551,10 @@ module Ci
end
end
+ def empty_dependencies?
+ options[:dependencies]&.empty?
+ end
+
private
def update_artifacts_size
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index f12be98c80c..49dec770096 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -164,11 +164,6 @@ module Ci
builds.latest.with_artifacts_not_expired.includes(project: [:namespace])
end
- # For now the only user who participates is the user who triggered
- def participants(_current_user = nil)
- Array(user)
- end
-
def valid_commit_sha
if self.sha == Gitlab::Git::BLANK_SHA
self.errors.add(:sha, " cant be 00000000 (branch removal)")
@@ -210,7 +205,7 @@ module Ci
end
def stuck?
- builds.pending.any?(&:stuck?)
+ builds.pending.includes(:project).any?(&:stuck?)
end
def retryable?
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 8c71267da65..17b322b5ae3 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -105,6 +105,10 @@ class CommitStatus < ActiveRecord::Base
end
end
+ def locking_enabled?
+ status_changed?
+ end
+
def before_sha
pipeline.before_sha || Gitlab::Git::BLANK_SHA
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 5101cc7e687..0a1a65da05a 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -31,6 +31,7 @@ module HasStatus
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{manual})>0 THEN 'manual'
+ WHEN (#{created})>0 THEN 'running'
ELSE 'failed'
END)"
end
diff --git a/app/models/concerns/importable.rb b/app/models/concerns/importable.rb
index 019ef755849..c9331eaf4cc 100644
--- a/app/models/concerns/importable.rb
+++ b/app/models/concerns/importable.rb
@@ -3,4 +3,7 @@ module Importable
attr_accessor :importing
alias_method :importing?, :importing
+
+ attr_accessor :imported
+ alias_method :imported?, :imported
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index e7bd20b322a..b4dded7e27e 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -14,6 +14,7 @@ module Issuable
include Awardable
include Taskable
include TimeTrackable
+ include Importable
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
@@ -50,6 +51,7 @@ module Issuable
:email,
:public_email,
to: :author,
+ allow_nil: true,
prefix: true
delegate :name,
@@ -98,7 +100,7 @@ module Issuable
acts_as_paranoid
after_save :update_assignee_cache_counts, if: :assignee_id_changed?
- after_save :record_metrics
+ after_save :record_metrics, unless: :imported?
def update_assignee_cache_counts
# make sure we flush the cache for both the old *and* new assignees(if they exist)
diff --git a/app/models/concerns/repository_mirroring.rb b/app/models/concerns/repository_mirroring.rb
new file mode 100644
index 00000000000..fed336c29d6
--- /dev/null
+++ b/app/models/concerns/repository_mirroring.rb
@@ -0,0 +1,17 @@
+module RepositoryMirroring
+ def set_remote_as_mirror(name)
+ config = raw_repository.rugged.config
+
+ # This is used to define repository as equivalent as "git clone --mirror"
+ config["remote.#{name}.fetch"] = 'refs/*:refs/*'
+ config["remote.#{name}.mirror"] = true
+ config["remote.#{name}.prune"] = true
+ end
+
+ def fetch_mirror(remote, url)
+ add_remote(remote, url)
+ set_remote_as_mirror(remote)
+ fetch_remote(remote, forced: true)
+ remove_remote(remote)
+ end
+end
diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb
index 107e6764ba2..647a6cad3d7 100644
--- a/app/models/concerns/spammable.rb
+++ b/app/models/concerns/spammable.rb
@@ -41,7 +41,7 @@ module Spammable
def check_for_spam
error_msg = if Gitlab::Recaptcha.enabled?
"Your #{spammable_entity_type} has been recognized as spam. "\
- "You can still submit it by solving Captcha."
+ "Please, change the content or solve the reCAPTCHA to proceed."
else
"Your #{spammable_entity_type} has been recognized as spam and has been discarded."
end
diff --git a/app/models/group.rb b/app/models/group.rb
index bd0ecae3da4..60274386103 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -207,7 +207,7 @@ class Group < Namespace
end
def members_with_parents
- GroupMember.non_request.where(source_id: ancestors.map(&:id).push(id))
+ GroupMember.non_request.where(source_id: ancestors.pluck(:id).push(id))
end
def users_with_parents
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 602eed86d9e..f9704b0d754 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -40,6 +40,8 @@ class Issue < ActiveRecord::Base
scope :include_associations, -> { includes(:assignee, :labels, project: :namespace) }
+ after_save :expire_etag_cache
+
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
@@ -59,10 +61,6 @@ class Issue < ActiveRecord::Base
before_transition any => :closed do |issue|
issue.closed_at = Time.zone.now
end
-
- before_transition closed: any do |issue|
- issue.closed_at = nil
- end
end
def hook_attrs
@@ -211,9 +209,8 @@ class Issue < ActiveRecord::Base
due_date.try(:past?) || false
end
- # Only issues on public projects should be checked for spam
def check_for_spam?
- project.public?
+ project.public? && (title_changed? || description_changed?)
end
def as_json(options = {})
@@ -257,4 +254,13 @@ class Issue < ActiveRecord::Base
def publicly_visible?
project.public? && !confidential?
end
+
+ def expire_etag_cache
+ key = Gitlab::Routing.url_helpers.rendered_title_namespace_project_issue_path(
+ project.namespace,
+ project,
+ self
+ )
+ Gitlab::EtagCaching::Store.new.touch(key)
+ end
end
diff --git a/app/models/list.rb b/app/models/list.rb
index 1e5da7f4dd4..fbd19acd1f5 100644
--- a/app/models/list.rb
+++ b/app/models/list.rb
@@ -2,7 +2,7 @@ class List < ActiveRecord::Base
belongs_to :board
belongs_to :label
- enum list_type: { label: 1, done: 2 }
+ enum list_type: { label: 1, closed: 2 }
validates :board, :list_type, presence: true
validates :label, :position, presence: true, if: :label?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 38eefad96b6..b2725a314ad 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -3,11 +3,9 @@ class MergeRequest < ActiveRecord::Base
include Issuable
include Referable
include Sortable
- include Importable
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
- belongs_to :project, foreign_key: :target_project_id
belongs_to :merge_user, class_name: "User"
has_many :merge_request_diffs, dependent: :destroy
@@ -155,8 +153,10 @@ class MergeRequest < ActiveRecord::Base
#
# Returns an ActiveRecord::Relation.
def self.in_projects(relation)
- source = where(source_project_id: relation).select(:id)
- target = where(target_project_id: relation).select(:id)
+ # unscoping unnecessary conditions that'll be applied
+ # when executing `where("merge_requests.id IN (#{union.to_sql})")`
+ source = unscoped.where(source_project_id: relation).select(:id)
+ target = unscoped.where(target_project_id: relation).select(:id)
union = Gitlab::SQL::Union.new([source, target])
where("merge_requests.id IN (#{union.to_sql})")
@@ -541,6 +541,10 @@ class MergeRequest < ActiveRecord::Base
target_project != source_project
end
+ def project
+ target_project
+ end
+
# If the merge request closes any issues, save this information in the
# `MergeRequestsClosingIssues` model. This is a performance optimization.
# Calculating this information for a number of merge requests requires
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index baee00b8fcd..6ad56b842b2 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -177,6 +177,16 @@ class MergeRequestDiff < ActiveRecord::Base
st_commits.count
end
+ def utf8_st_diffs
+ return [] if st_diffs.blank?
+
+ st_diffs.map do |diff|
+ diff.each do |k, v|
+ diff[k] = encode_utf8(v) if v.respond_to?(:encoding)
+ end
+ end
+ end
+
private
# Old GitLab implementations may have generated diffs as ["--broken-diff"].
@@ -270,14 +280,6 @@ class MergeRequestDiff < ActiveRecord::Base
project.merge_base_commit(head_commit_sha, start_commit_sha).try(:sha)
end
- def utf8_st_diffs
- st_diffs.map do |diff|
- diff.each do |k, v|
- diff[k] = encode_utf8(v) if v.respond_to?(:encoding)
- end
- end
- end
-
#
# #save or #update_attributes providing changes on serialized attributes do a lot of
# serialization and deserialization calls resulting in bad performance.
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index c0deb59ec4c..ac205b9b738 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -30,7 +30,7 @@ class Milestone < ActiveRecord::Base
validates :title, presence: true, uniqueness: { scope: :project_id }
validates :project, presence: true
- validate :start_date_should_be_less_than_due_date, if: Proc.new { |m| m.start_date.present? && m.due_date.present? }
+ validate :start_date_should_be_less_than_due_date, if: proc { |m| m.start_date.present? && m.due_date.present? }
strip_attributes :title
@@ -107,6 +107,21 @@ class Milestone < ActiveRecord::Base
end
end
+ def self.sort(method)
+ case method.to_s
+ when 'due_date_asc'
+ reorder(Gitlab::Database.nulls_last_order('due_date', 'ASC'))
+ when 'due_date_desc'
+ reorder(Gitlab::Database.nulls_last_order('due_date', 'DESC'))
+ when 'start_date_asc'
+ reorder(Gitlab::Database.nulls_last_order('start_date', 'ASC'))
+ when 'start_date_desc'
+ reorder(Gitlab::Database.nulls_last_order('start_date', 'DESC'))
+ else
+ order_by(method)
+ end
+ end
+
##
# Returns the String necessary to reference this Milestone in Markdown
#
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index d350f1d6770..9bfa731785f 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -120,10 +120,10 @@ class Namespace < ActiveRecord::Base
# Move the namespace directory in all storages paths used by member projects
repository_storage_paths.each do |repository_storage_path|
# Ensure old directory exists before moving it
- gitlab_shell.add_namespace(repository_storage_path, path_was)
+ gitlab_shell.add_namespace(repository_storage_path, full_path_was)
- unless gitlab_shell.mv_namespace(repository_storage_path, path_was, path)
- Rails.logger.error "Exception moving path #{repository_storage_path} from #{path_was} to #{path}"
+ unless gitlab_shell.mv_namespace(repository_storage_path, full_path_was, full_path)
+ Rails.logger.error "Exception moving path #{repository_storage_path} from #{full_path_was} to #{full_path}"
# if we cannot move namespace directory we should rollback
# db changes in order to prevent out of sync between db and fs
@@ -131,8 +131,8 @@ class Namespace < ActiveRecord::Base
end
end
- Gitlab::UploadsTransfer.new.rename_namespace(path_was, path)
- Gitlab::PagesTransfer.new.rename_namespace(path_was, path)
+ Gitlab::UploadsTransfer.new.rename_namespace(full_path_was, full_path)
+ Gitlab::PagesTransfer.new.rename_namespace(full_path_was, full_path)
remove_exports!
@@ -150,12 +150,12 @@ class Namespace < ActiveRecord::Base
end
def any_project_has_container_registry_tags?
- projects.any?(&:has_container_registry_tags?)
+ all_projects.any?(&:has_container_registry_tags?)
end
def send_update_instructions
projects.each do |project|
- project.send_move_instructions("#{path_was}/#{project.path}")
+ project.send_move_instructions("#{full_path_was}/#{project.path}")
end
end
@@ -195,7 +195,7 @@ class Namespace < ActiveRecord::Base
# Scopes the model on direct and indirect children of the record
def descendants
- self.class.joins(:route).where('routes.path LIKE ?', "#{route.path}/%").reorder('routes.path ASC')
+ self.class.joins(:route).merge(Route.inside_path(route.path)).reorder('routes.path ASC')
end
def user_ids_for_project_authorizations
@@ -214,6 +214,12 @@ class Namespace < ActiveRecord::Base
@old_repository_storage_paths ||= repository_storage_paths
end
+ # Includes projects from this namespace and projects from all subgroups
+ # that belongs to this namespace
+ def all_projects
+ Project.inside_path(full_path)
+ end
+
private
def repository_storage_paths
@@ -221,7 +227,7 @@ class Namespace < ActiveRecord::Base
# pending delete. Unscoping also get rids of the default order, which causes
# problems with SELECT DISTINCT.
Project.unscoped do
- projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path)
+ all_projects.select('distinct(repository_storage)').to_a.map(&:repository_storage_path)
end
end
@@ -230,10 +236,10 @@ class Namespace < ActiveRecord::Base
old_repository_storage_paths.each do |repository_storage_path|
# Move namespace directory into trash.
# We will remove it later async
- new_path = "#{path}+#{id}+deleted"
+ new_path = "#{full_path}+#{id}+deleted"
- if gitlab_shell.mv_namespace(repository_storage_path, path, new_path)
- message = "Namespace directory \"#{path}\" moved to \"#{new_path}\""
+ if gitlab_shell.mv_namespace(repository_storage_path, full_path, new_path)
+ message = "Namespace directory \"#{full_path}\" moved to \"#{new_path}\""
Gitlab::AppLogger.info message
# Remove namespace directroy async with delay so
diff --git a/app/models/note.rb b/app/models/note.rb
index e22e96aec6f..16d66cb1427 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -37,6 +37,7 @@ class Note < ActiveRecord::Base
has_many :todos, dependent: :destroy
has_many :events, as: :target, dependent: :destroy
+ has_one :system_note_metadata
delegate :gfm_reference, :local_reference, to: :noteable
delegate :name, to: :project, prefix: true
@@ -70,7 +71,9 @@ class Note < ActiveRecord::Base
scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
- scope :inc_relations_for_view, ->{ includes(:project, :author, :updated_by, :resolved_by, :award_emoji) }
+ scope :inc_relations_for_view, -> do
+ includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata)
+ end
scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
scope :non_diff_notes, ->{ where(type: ['Note', nil]) }
diff --git a/app/models/notification_setting.rb b/app/models/notification_setting.rb
index 52577bd52ea..e4726e62e93 100644
--- a/app/models/notification_setting.rb
+++ b/app/models/notification_setting.rb
@@ -60,16 +60,25 @@ class NotificationSetting < ActiveRecord::Base
def set_events
return if custom?
- EMAIL_EVENTS.each do |event|
- events[event] = false
- end
+ self.events = {}
end
# Validates store accessors values as boolean
# It is a text field so it does not cast correct boolean values in JSON
def events_to_boolean
EMAIL_EVENTS.each do |event|
- events[event] = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(events[event])
+ bool = ActiveRecord::ConnectionAdapters::Column::TRUE_VALUES.include?(public_send(event))
+
+ events[event] = bool
end
end
+
+ # Allow people to receive failed pipeline notifications if they already have
+ # custom notifications enabled, as these are more like mentions than the other
+ # custom settings.
+ def failed_pipeline
+ bool = super
+
+ bool.nil? || bool
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 13c5c181cc5..2469e6f8523 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -114,6 +114,8 @@ class Project < ActiveRecord::Base
has_one :kubernetes_service, dependent: :destroy, inverse_of: :project
has_one :prometheus_service, dependent: :destroy, inverse_of: :project
has_one :mock_ci_service, dependent: :destroy
+ has_one :mock_deployment_service, dependent: :destroy
+ has_one :mock_monitoring_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
@@ -239,7 +241,7 @@ class Project < ActiveRecord::Base
# We need routes alias rs for JOIN so it does not conflict with
# includes(:route) which we use in ProjectsFinder.
joins("INNER JOIN routes rs ON rs.source_id = projects.id AND rs.source_type = 'Project'").
- where('rs.path LIKE ?', "#{path}/%")
+ where('rs.path LIKE ?', "#{sanitize_sql_like(path)}/%")
end
# "enabled" here means "not disabled". It includes private features!
@@ -315,20 +317,15 @@ class Project < ActiveRecord::Base
ntable = Namespace.arel_table
pattern = "%#{query}%"
- projects = select(:id).where(
+ # unscoping unnecessary conditions that'll be applied
+ # when executing `where("projects.id IN (#{union.to_sql})")`
+ projects = unscoped.select(:id).where(
ptable[:path].matches(pattern).
or(ptable[:name].matches(pattern)).
or(ptable[:description].matches(pattern))
)
- # We explicitly remove any eager loading clauses as they're:
- #
- # 1. Not needed by this query
- # 2. Combined with .joins(:namespace) lead to all columns from the
- # projects & namespaces tables being selected, leading to a SQL error
- # due to the columns of all UNION'd queries no longer being the same.
- namespaces = select(:id).
- except(:includes).
+ namespaces = unscoped.select(:id).
joins(:namespace).
where(ntable[:name].matches(pattern))
@@ -557,6 +554,10 @@ class Project < ActiveRecord::Base
import_type == 'gitea'
end
+ def github_import?
+ import_type == 'github'
+ end
+
def check_limit
unless creator.can_create_project? || namespace.kind == 'group'
projects_limit = creator.projects_limit
@@ -874,13 +875,9 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo(user = nil)
- url = web_url
-
- if user
- url.sub!(%r{\Ahttps?://}) { |protocol| "#{protocol}#{user.username}@" }
- end
+ credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user)
- "#{url}.git"
+ Gitlab::UrlSanitizer.new("#{web_url}.git", credentials: credentials).full_url
end
def user_can_push_to_empty_repo?(user)
diff --git a/app/models/project_services/chat_notification_service.rb b/app/models/project_services/chat_notification_service.rb
index 200be99f36b..75834103db5 100644
--- a/app/models/project_services/chat_notification_service.rb
+++ b/app/models/project_services/chat_notification_service.rb
@@ -6,7 +6,7 @@ class ChatNotificationService < Service
default_value_for :category, 'chat'
prop_accessor :webhook, :username, :channel
- boolean_accessor :notify_only_broken_pipelines
+ boolean_accessor :notify_only_broken_pipelines, :notify_only_default_branch
validates :webhook, presence: true, url: true, if: :activated?
@@ -17,6 +17,7 @@ class ChatNotificationService < Service
if properties.nil?
self.properties = {}
self.notify_only_broken_pipelines = true
+ self.notify_only_default_branch = true
end
end
@@ -29,6 +30,19 @@ class ChatNotificationService < Service
pipeline wiki_page]
end
+ def fields
+ default_fields + build_event_channels
+ end
+
+ def default_fields
+ [
+ { type: 'text', name: 'webhook', placeholder: "e.g. #{webhook_placeholder}" },
+ { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
+ { type: 'checkbox', name: 'notify_only_broken_pipelines' },
+ { type: 'checkbox', name: 'notify_only_default_branch' },
+ ]
+ end
+
def execute(data)
return unless supported_events.include?(data[:object_kind])
return unless webhook.present?
@@ -123,6 +137,17 @@ class ChatNotificationService < Service
end
def should_pipeline_be_notified?(data)
+ notify_for_ref?(data) && notify_for_pipeline?(data)
+ end
+
+ def notify_for_ref?(data)
+ return true if data[:object_attributes][:tag]
+ return true unless notify_only_default_branch
+
+ data[:object_attributes][:ref] == project.default_branch
+ end
+
+ def notify_for_pipeline?(data)
case data[:object_attributes][:status]
when 'success'
!notify_only_broken_pipelines?
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index eef403dba92..3b90fd1c2c7 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -62,7 +62,7 @@ class JiraService < IssueTrackerService
def help
"You need to configure JIRA before enabling this service. For more details
read the
- [JIRA service documentation](#{help_page_url('project_services/jira')})."
+ [JIRA service documentation](#{help_page_url('user/project/integrations/jira')})."
end
def title
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 02fbd5497fa..9c56518c991 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -22,22 +22,21 @@ class KubernetesService < DeploymentService
with_options presence: true, if: :activated? do
validates :api_url, url: true
validates :token
-
- validates :namespace,
- format: {
- with: Gitlab::Regex.kubernetes_namespace_regex,
- message: Gitlab::Regex.kubernetes_namespace_regex_message,
- },
- length: 1..63
end
+ validates :namespace,
+ allow_blank: true,
+ length: 1..63,
+ if: :activated?,
+ format: {
+ with: Gitlab::Regex.kubernetes_namespace_regex,
+ message: Gitlab::Regex.kubernetes_namespace_regex_message
+ }
+
after_save :clear_reactive_cache!
def initialize_properties
- if properties.nil?
- self.properties = {}
- self.namespace = "#{project.path}-#{project.id}" if project.present?
- end
+ self.properties = {} if properties.nil?
end
def title
@@ -62,7 +61,7 @@ class KubernetesService < DeploymentService
{ type: 'text',
name: 'namespace',
title: 'Kubernetes namespace',
- placeholder: 'Kubernetes namespace' },
+ placeholder: namespace_placeholder },
{ type: 'text',
name: 'api_url',
title: 'API URL',
@@ -92,7 +91,7 @@ class KubernetesService < DeploymentService
variables = [
{ key: 'KUBE_URL', value: api_url, public: true },
{ key: 'KUBE_TOKEN', value: token, public: false },
- { key: 'KUBE_NAMESPACE', value: namespace, public: true }
+ { key: 'KUBE_NAMESPACE', value: namespace_variable, public: true }
]
if ca_pem.present?
@@ -135,8 +134,26 @@ class KubernetesService < DeploymentService
{ pods: pods }
end
+ TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
+
private
+ def namespace_placeholder
+ default_namespace || TEMPLATE_PLACEHOLDER
+ end
+
+ def namespace_variable
+ if namespace.present?
+ namespace
+ else
+ default_namespace
+ end
+ end
+
+ def default_namespace
+ "#{project.path}-#{project.id}" if project.present?
+ end
+
def build_kubeclient!(api_path: 'api', api_version: 'v1')
raise "Incomplete settings" unless api_url && namespace && token
diff --git a/app/models/project_services/mattermost_service.rb b/app/models/project_services/mattermost_service.rb
index 1156d050622..0362ed172c7 100644
--- a/app/models/project_services/mattermost_service.rb
+++ b/app/models/project_services/mattermost_service.rb
@@ -22,19 +22,11 @@ class MattermostService < ChatNotificationService
</ol>'
end
- def fields
- default_fields + build_event_channels
- end
-
- def default_fields
- [
- { type: 'text', name: 'webhook', placeholder: 'e.g. http://mattermost_host/hooks/…' },
- { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
- { type: 'checkbox', name: 'notify_only_broken_pipelines' },
- ]
- end
-
def default_channel_placeholder
"Channel handle (e.g. town-square)"
end
+
+ def webhook_placeholder
+ 'http://mattermost.example.com/hooks/…'
+ end
end
diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb
new file mode 100644
index 00000000000..59a3811ce5d
--- /dev/null
+++ b/app/models/project_services/mock_deployment_service.rb
@@ -0,0 +1,18 @@
+class MockDeploymentService < DeploymentService
+ def title
+ 'Mock deployment'
+ end
+
+ def description
+ 'Mock deployment service'
+ end
+
+ def self.to_param
+ 'mock_deployment'
+ end
+
+ # No terminals support
+ def terminals(environment)
+ []
+ end
+end
diff --git a/app/models/project_services/mock_monitoring_service.rb b/app/models/project_services/mock_monitoring_service.rb
new file mode 100644
index 00000000000..dd04e04e198
--- /dev/null
+++ b/app/models/project_services/mock_monitoring_service.rb
@@ -0,0 +1,17 @@
+class MockMonitoringService < MonitoringService
+ def title
+ 'Mock monitoring'
+ end
+
+ def description
+ 'Mock monitoring service'
+ end
+
+ def self.to_param
+ 'mock_monitoring'
+ end
+
+ def metrics(environment)
+ JSON.parse(File.read(Rails.root + 'spec/fixtures/metrics.json'))
+ end
+end
diff --git a/app/models/project_services/prometheus_service.rb b/app/models/project_services/prometheus_service.rb
index 375966b9efc..6854d2243d7 100644
--- a/app/models/project_services/prometheus_service.rb
+++ b/app/models/project_services/prometheus_service.rb
@@ -30,7 +30,14 @@ class PrometheusService < MonitoringService
end
def help
- 'Retrieves `container_cpu_usage_seconds_total` and `container_memory_usage_bytes` from the configured Prometheus server. An `environment` label is required on each metric to identify the Environment.'
+ <<-MD.strip_heredoc
+ Retrieves the Kubernetes node metrics `container_cpu_usage_seconds_total`
+ and `container_memory_usage_bytes` from the configured Prometheus server.
+
+ If you are not using [Auto-Deploy](https://docs.gitlab.com/ee/ci/autodeploy/index.html)
+ or have set up your own Prometheus server, an `environment` label is required on each metric to
+ [identify the Environment](https://docs.gitlab.com/ce/user/project/integrations/prometheus.html#metrics-and-labels).
+ MD
end
def self.to_param
@@ -67,16 +74,16 @@ class PrometheusService < MonitoringService
def calculate_reactive_cache(environment_slug)
return unless active? && project && !project.pending_delete?
- memory_query = %{sum(container_memory_usage_bytes{container_name="app",environment="#{environment_slug}"})/1024/1024}
- cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name="app",environment="#{environment_slug}"}[2m]))}
+ memory_query = %{(sum(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"}) / count(container_memory_usage_bytes{container_name!="POD",environment="#{environment_slug}"})) /1024/1024}
+ cpu_query = %{sum(rate(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}[2m])) / count(container_cpu_usage_seconds_total{container_name!="POD",environment="#{environment_slug}"}) * 100}
{
success: true,
metrics: {
- # Memory used in MB
+ # Average Memory used in MB
memory_values: client.query_range(memory_query, start: 8.hours.ago),
memory_current: client.query(memory_query),
- # CPU Usage rate in cores.
+ # Average CPU Utilization
cpu_values: client.query_range(cpu_query, start: 8.hours.ago),
cpu_current: client.query(cpu_query)
},
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index b657db6f9ee..71da0af75f6 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -21,19 +21,11 @@ class SlackService < ChatNotificationService
</ol>'
end
- def fields
- default_fields + build_event_channels
- end
-
- def default_fields
- [
- { type: 'text', name: 'webhook', placeholder: 'e.g. https://hooks.slack.com/services/…' },
- { type: 'text', name: 'username', placeholder: 'e.g. GitLab' },
- { type: 'checkbox', name: 'notify_only_broken_pipelines' },
- ]
- end
-
def default_channel_placeholder
"Channel name (e.g. general)"
end
+
+ def webhook_placeholder
+ 'https://hooks.slack.com/services/…'
+ end
end
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 8a53e974b6f..6d6644053f8 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -169,6 +169,9 @@ class ProjectTeam
# Lookup only the IDs we need
user_ids = user_ids - access.keys
+
+ return access if user_ids.empty?
+
users_access = project.project_authorizations.
where(user: user_ids).
group(:user_id).
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 539b31780b3..70eef359cdd 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -42,8 +42,11 @@ class ProjectWiki
url_to_repo
end
- def http_url_to_repo
- [Gitlab.config.gitlab.url, "/", path_with_namespace, ".git"].join('')
+ def http_url_to_repo(user = nil)
+ url = "#{Gitlab.config.gitlab.url}/#{path_with_namespace}.git"
+ credentials = Gitlab::UrlSanitizer.http_credentials_for_user(user)
+
+ Gitlab::UrlSanitizer.new(url, credentials: credentials).full_url
end
def wiki_base_path
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 6ab04440ca8..dc1c1fab880 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -2,6 +2,7 @@ require 'securerandom'
class Repository
include Gitlab::ShellAdapter
+ include RepositoryMirroring
attr_accessor :path_with_namespace, :project
@@ -58,13 +59,13 @@ class Repository
def raw_repository
return nil unless path_with_namespace
- @raw_repository ||= Gitlab::Git::Repository.new(path_to_repo)
+ @raw_repository ||= initialize_raw_repository
end
# Return absolute path to repository
def path_to_repo
@path_to_repo ||= File.expand_path(
- File.join(@project.repository_storage_path, path_with_namespace + ".git")
+ File.join(repository_storage_path, path_with_namespace + ".git")
)
end
@@ -145,12 +146,7 @@ class Repository
# may cause the branch to "disappear" erroneously or have the wrong SHA.
#
# See: https://github.com/libgit2/libgit2/issues/1534 and https://gitlab.com/gitlab-org/gitlab-ce/issues/15392
- raw_repo =
- if fresh_repo
- Gitlab::Git::Repository.new(path_to_repo)
- else
- raw_repository
- end
+ raw_repo = fresh_repo ? initialize_raw_repository : raw_repository
raw_repo.find_branch(name)
end
@@ -401,10 +397,6 @@ class Repository
expire_tags_cache
end
- def before_import
- expire_content_cache
- end
-
# Runs code after the HEAD of a repository is changed.
def after_change_head
expire_method_caches(METHOD_CACHES_FOR_FILE_TYPES.keys)
@@ -508,9 +500,7 @@ class Repository
end
end
- def branch_names
- branches.map(&:name)
- end
+ delegate :branch_names, to: :raw_repository
cache_method :branch_names, fallback: []
delegate :tag_names, to: :raw_repository
@@ -981,7 +971,13 @@ class Repository
end
def is_ancestor?(ancestor_id, descendant_id)
- merge_base(ancestor_id, descendant_id) == ancestor_id
+ Gitlab::GitalyClient.migrate(:is_ancestor) do |is_enabled|
+ if is_enabled
+ raw_repository.is_ancestor?(ancestor_id, descendant_id)
+ else
+ merge_base_commit(ancestor_id, descendant_id) == ancestor_id
+ end
+ end
end
def empty_repo?
@@ -1027,6 +1023,23 @@ class Repository
rugged.references.delete(tmp_ref) if tmp_ref
end
+ def add_remote(name, url)
+ raw_repository.remote_add(name, url)
+ rescue Rugged::ConfigError
+ raw_repository.remote_update(name, url: url)
+ end
+
+ def remove_remote(name)
+ raw_repository.remote_delete(name)
+ true
+ rescue Rugged::ConfigError
+ false
+ end
+
+ def fetch_remote(remote, forced: false, no_tags: false)
+ gitlab_shell.fetch_remote(repository_storage_path, path_with_namespace, remote, forced: forced, no_tags: no_tags)
+ end
+
def fetch_ref(source_path, source_ref, target_ref)
args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
Gitlab::Popen.popen(args, path_to_repo)
@@ -1144,4 +1157,12 @@ class Repository
def repository_event(event, tags = {})
Gitlab::Metrics.add_event(event, { path: path_with_namespace }.merge(tags))
end
+
+ def repository_storage_path
+ @project.repository_storage_path
+ end
+
+ def initialize_raw_repository
+ Gitlab::Git::Repository.new(project.repository_storage, path_with_namespace + '.git')
+ end
end
diff --git a/app/models/route.rb b/app/models/route.rb
index 41e6eb7cb73..4b3efab5c3c 100644
--- a/app/models/route.rb
+++ b/app/models/route.rb
@@ -10,9 +10,11 @@ class Route < ActiveRecord::Base
after_update :rename_descendants
+ scope :inside_path, -> (path) { where('routes.path LIKE ?', "#{sanitize_sql_like(path)}/%") }
+
def rename_descendants
if path_changed? || name_changed?
- descendants = Route.where('path LIKE ?', "#{path_was}/%")
+ descendants = self.class.inside_path(path_was)
descendants.each do |route|
attributes = {}
diff --git a/app/models/service.rb b/app/models/service.rb
index e73f7e5d1a3..5a0ec58d193 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -25,7 +25,7 @@ class Service < ActiveRecord::Base
belongs_to :project, inverse_of: :services
has_one :service_hook
- validates :project_id, presence: true, unless: Proc.new { |service| service.template? }
+ validates :project_id, presence: true, unless: proc { |service| service.template? }
scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') }
scope :issue_trackers, -> { where(category: 'issue_tracker') }
@@ -238,7 +238,9 @@ class Service < ActiveRecord::Base
slack
teamcity
]
- service_names << 'mock_ci' if Rails.env.development?
+ if Rails.env.development?
+ service_names += %w[mock_ci mock_deployment mock_monitoring]
+ end
service_names.sort_by(&:downcase)
end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index dbd564e5e7d..30aca62499c 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -132,7 +132,8 @@ class Snippet < ActiveRecord::Base
end
def check_for_spam?
- public?
+ visibility_level_changed?(to: Snippet::PUBLIC) ||
+ (public? && (title_changed? || content_changed?))
end
def spammable_entity_type
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
new file mode 100644
index 00000000000..1e6fc837a75
--- /dev/null
+++ b/app/models/system_note_metadata.rb
@@ -0,0 +1,11 @@
+class SystemNoteMetadata < ActiveRecord::Base
+ ICON_TYPES = %w[
+ commit merge confidential visible label assignee cross_reference
+ title time_tracking branch milestone discussion task moved opened closed merged
+ ].freeze
+
+ validates :note, presence: true
+ validates :action, inclusion: ICON_TYPES, allow_nil: true
+
+ belongs_to :note
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 8c7ad5d5174..95a766f2ede 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -22,6 +22,7 @@ class User < ActiveRecord::Base
default_value_for :hide_no_ssh_key, false
default_value_for :hide_no_password, false
default_value_for :project_view, :files
+ default_value_for :notified_of_own_activity, false
attr_encrypted :otp_secret,
key: Gitlab::Application.secrets.otp_key_base,
@@ -115,7 +116,9 @@ class User < ActiveRecord::Base
validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
validates :bio, length: { maximum: 255 }, allow_blank: true
- validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
+ validates :projects_limit,
+ presence: true,
+ numericality: { greater_than_or_equal_to: 0, less_than_or_equal_to: Gitlab::Database::MAX_INT_VALUE }
validates :username,
namespace: true,
presence: true,
@@ -126,10 +129,9 @@ class User < ActiveRecord::Base
validate :unique_email, if: ->(user) { user.email_changed? }
validate :owns_notification_email, if: ->(user) { user.notification_email_changed? }
validate :owns_public_email, if: ->(user) { user.public_email_changed? }
+ validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
- before_validation :generate_password, on: :create
- before_validation :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id }
before_validation :sanitize_attrs
before_validation :set_notification_email, if: ->(user) { user.email_changed? }
before_validation :set_public_email, if: ->(user) { user.public_email_changed? }
@@ -139,8 +141,6 @@ class User < ActiveRecord::Base
before_save :ensure_external_user_rights
after_save :ensure_namespace_correct
after_initialize :set_projects_limit
- before_create :check_confirmation_email
- after_create :post_create_hook
after_destroy :post_destroy_hook
# User's Layout preference
@@ -324,6 +324,8 @@ class User < ActiveRecord::Base
end
def find_by_personal_access_token(token_string)
+ return unless token_string
+
PersonalAccessTokensFinder.new(state: 'active').find_by(token: token_string)&.user
end
@@ -382,10 +384,8 @@ class User < ActiveRecord::Base
"#{self.class.reference_prefix}#{username}"
end
- def generate_password
- if force_random_password
- self.password = self.password_confirmation = Devise.friendly_token.first(Devise.password_length.min)
- end
+ def skip_confirmation=(bool)
+ skip_confirmation! if bool
end
def generate_reset_token
@@ -397,10 +397,6 @@ class User < ActiveRecord::Base
@reset_token
end
- def check_confirmation_email
- skip_confirmation! unless current_application_settings.send_user_confirmation_email
- end
-
def recently_sent_password_reset?
reset_password_sent_at.present? && reset_password_sent_at >= 1.minute.ago
end
@@ -639,8 +635,10 @@ class User < ActiveRecord::Base
end
def fork_of(project)
- links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects)
-
+ links = ForkedProjectLink.where(
+ forked_from_project_id: project,
+ forked_to_project_id: personal_projects.unscope(:order)
+ )
if links.any?
links.first.forked_to_project
else
@@ -795,12 +793,6 @@ class User < ActiveRecord::Base
end
end
- def post_create_hook
- log_info("User \"#{name}\" (#{email}) was created")
- notification_service.new_user(self, @reset_token) if created_by_id
- system_hook_service.execute_hooks_for(self, :create)
- end
-
def post_destroy_hook
log_info("User \"#{name}\" (#{email}) was removed")
system_hook_service.execute_hooks_for(self, :destroy)