Commit 71b9458e authored by Marin Jankovski's avatar Marin Jankovski

Resolve spec/models/repository_spec.rb conflict.

# Conflicts:
#   spec/models/repository_spec.rb
parents 4792e71b 408673bb
...@@ -13,3 +13,5 @@ lib/gitlab/redis/*.rb ...@@ -13,3 +13,5 @@ lib/gitlab/redis/*.rb
lib/gitlab/gitaly_client/operation_service.rb lib/gitlab/gitaly_client/operation_service.rb
app/models/project_services/packagist_service.rb app/models/project_services/packagist_service.rb
lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
lib/gitlab/background_migration/*
app/models/project_services/kubernetes_service.rb
...@@ -72,6 +72,10 @@ gem 'net-ldap' ...@@ -72,6 +72,10 @@ gem 'net-ldap'
# Git Wiki # Git Wiki
# Required manually in config/initializers/gollum.rb to control load order # Required manually in config/initializers/gollum.rb to control load order
# Before updating this gem, check if
# https://github.com/gollum/gollum-lib/pull/292 has been merged.
# If it has, then remove the monkey patch for update_page, rename_page and raw_data_in_committer
# in config/initializers/gollum.rb
gem 'gollum-lib', '~> 4.2', require: false gem 'gollum-lib', '~> 4.2', require: false
# Before updating this gem, check if # Before updating this gem, check if
...@@ -361,7 +365,7 @@ group :development, :test do ...@@ -361,7 +365,7 @@ group :development, :test do
gem 'scss_lint', '~> 0.56.0', require: false gem 'scss_lint', '~> 0.56.0', require: false
gem 'haml_lint', '~> 0.26.0', require: false gem 'haml_lint', '~> 0.26.0', require: false
gem 'simplecov', '~> 0.14.0', require: false gem 'simplecov', '~> 0.14.0', require: false
gem 'flay', '~> 2.8.0', require: false gem 'flay', '~> 2.10.0', require: false
gem 'bundler-audit', '~> 0.5.0', require: false gem 'bundler-audit', '~> 0.5.0', require: false
gem 'benchmark-ips', '~> 2.3.0', require: false gem 'benchmark-ips', '~> 2.3.0', require: false
......
...@@ -235,7 +235,7 @@ GEM ...@@ -235,7 +235,7 @@ GEM
fast_gettext (1.4.0) fast_gettext (1.4.0)
ffaker (2.4.0) ffaker (2.4.0)
ffi (1.9.18) ffi (1.9.18)
flay (2.8.1) flay (2.10.0)
erubis (~> 2.7.0) erubis (~> 2.7.0)
path_expander (~> 1.0) path_expander (~> 1.0)
ruby_parser (~> 3.0) ruby_parser (~> 3.0)
...@@ -617,7 +617,7 @@ GEM ...@@ -617,7 +617,7 @@ GEM
ast (~> 2.3) ast (~> 2.3)
parslet (1.5.0) parslet (1.5.0)
blankslate (~> 2.0) blankslate (~> 2.0)
path_expander (1.0.1) path_expander (1.0.2)
peek (1.0.1) peek (1.0.1)
concurrent-ruby (>= 0.9.0) concurrent-ruby (>= 0.9.0)
concurrent-ruby-ext (>= 0.9.0) concurrent-ruby-ext (>= 0.9.0)
...@@ -1072,7 +1072,7 @@ DEPENDENCIES ...@@ -1072,7 +1072,7 @@ DEPENDENCIES
faraday_middleware-aws-signers-v4 faraday_middleware-aws-signers-v4
fast_blank fast_blank
ffaker (~> 2.4) ffaker (~> 2.4)
flay (~> 2.8.0) flay (~> 2.10.0)
flipper (~> 0.11.0) flipper (~> 0.11.0)
flipper-active_record (~> 0.11.0) flipper-active_record (~> 0.11.0)
flipper-active_support_cache_store (~> 0.11.0) flipper-active_support_cache_store (~> 0.11.0)
......
...@@ -6,6 +6,14 @@ ...@@ -6,6 +6,14 @@
} }
} }
.wiki-form {
.edit-wiki-page-slug-tip {
display: inline-block;
max-width: 100%;
margin-top: 5px;
}
}
.title .edit-wiki-header { .title .edit-wiki-header {
width: 780px; width: 780px;
margin-left: auto; margin-left: auto;
......
class Admin::ServicesController < Admin::ApplicationController class Admin::ServicesController < Admin::ApplicationController
include ServiceParams include ServiceParams
before_action :whitelist_query_limiting, only: [:index]
before_action :service, only: [:edit, :update] before_action :service, only: [:edit, :update]
def index def index
...@@ -37,4 +38,8 @@ class Admin::ServicesController < Admin::ApplicationController ...@@ -37,4 +38,8 @@ class Admin::ServicesController < Admin::ApplicationController
def service def service
@service ||= Service.where(id: params[:id], template: true).first @service ||= Service.where(id: params[:id], template: true).first
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42430')
end
end end
...@@ -4,6 +4,7 @@ module Boards ...@@ -4,6 +4,7 @@ module Boards
prepend EE::Boards::IssuesController prepend EE::Boards::IssuesController
include BoardsResponses include BoardsResponses
before_action :whitelist_query_limiting, only: [:index, :update]
before_action :authorize_read_issue, only: [:index] before_action :authorize_read_issue, only: [:index]
before_action :authorize_create_issue, only: [:create] before_action :authorize_create_issue, only: [:create]
before_action :authorize_update_issue, only: [:update] before_action :authorize_update_issue, only: [:update]
...@@ -94,5 +95,10 @@ module Boards ...@@ -94,5 +95,10 @@ module Boards
} }
) )
end end
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42439
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42428')
end
end end
end end
...@@ -70,7 +70,7 @@ module UploadsActions ...@@ -70,7 +70,7 @@ module UploadsActions
end end
def build_uploader_from_params def build_uploader_from_params
uploader = uploader_class.new(model, params[:secret]) uploader = uploader_class.new(model, secret: params[:secret])
uploader.retrieve_from_store!(params[:filename]) uploader.retrieve_from_store!(params[:filename])
uploader uploader
end end
......
class Import::GitlabProjectsController < Import::BaseController class Import::GitlabProjectsController < Import::BaseController
before_action :whitelist_query_limiting, only: [:create]
before_action :verify_gitlab_project_import_enabled before_action :verify_gitlab_project_import_enabled
def new def new
...@@ -40,4 +41,8 @@ class Import::GitlabProjectsController < Import::BaseController ...@@ -40,4 +41,8 @@ class Import::GitlabProjectsController < Import::BaseController
:path, :namespace_id, :file :path, :namespace_id, :file
) )
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42437')
end
end end
...@@ -4,6 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include RendersCommits include RendersCommits
before_action :whitelist_query_limiting
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -65,4 +66,8 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -65,4 +66,8 @@ class Projects::CommitsController < Projects::ApplicationController
@commits = @commits.with_pipeline_status @commits = @commits.with_pipeline_status
@commits = prepare_commits_for_rendering(@commits) @commits = prepare_commits_for_rendering(@commits)
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42330')
end
end end
...@@ -3,6 +3,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController ...@@ -3,6 +3,7 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
include CycleAnalyticsParams include CycleAnalyticsParams
before_action :whitelist_query_limiting, only: [:show]
before_action :authorize_read_cycle_analytics! before_action :authorize_read_cycle_analytics!
def show def show
...@@ -31,4 +32,8 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController ...@@ -31,4 +32,8 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
permissions: @cycle_analytics.permissions(user: current_user) permissions: @cycle_analytics.permissions(user: current_user)
} }
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42671')
end
end end
...@@ -2,6 +2,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::ForksController < Projects::ApplicationController
include ContinueParams include ContinueParams
# Authorize # Authorize
before_action :whitelist_query_limiting, only: [:create]
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authenticate_user!, only: [:new, :create] before_action :authenticate_user!, only: [:new, :create]
...@@ -54,4 +55,8 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -54,4 +55,8 @@ class Projects::ForksController < Projects::ApplicationController
render :error render :error
end end
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42335')
end
end end
...@@ -8,6 +8,8 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -8,6 +8,8 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action :authenticate_user!, only: [:new, :export_csv] prepend_before_action :authenticate_user!, only: [:new, :export_csv]
before_action :whitelist_query_limiting_ee, only: [:update]
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
before_action :check_issues_available! before_action :check_issues_available!
before_action :issue, except: [:index, :new, :create, :bulk_update, :export_csv] before_action :issue, except: [:index, :new, :create, :bulk_update, :export_csv]
...@@ -250,4 +252,17 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -250,4 +252,17 @@ class Projects::IssuesController < Projects::ApplicationController
@finder_type = IssuesFinder @finder_type = IssuesFinder
super super
end end
def whitelist_query_limiting
# Also see the following issues:
#
# 1. https://gitlab.com/gitlab-org/gitlab-ce/issues/42423
# 2. https://gitlab.com/gitlab-org/gitlab-ce/issues/42424
# 3. https://gitlab.com/gitlab-org/gitlab-ce/issues/42426
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42422')
end
def whitelist_query_limiting_ee
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4794')
end
end end
...@@ -6,6 +6,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -6,6 +6,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
prepend ::EE::Projects::MergeRequests::CreationsController prepend ::EE::Projects::MergeRequests::CreationsController
skip_before_action :merge_request skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request! before_action :authorize_create_merge_request!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path] before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create] before_action :build_merge_request, except: [:create]
...@@ -127,4 +128,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -127,4 +128,8 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
@project.forked_from_project @project.forked_from_project
end end
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42384')
end
end end
...@@ -9,6 +9,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -9,6 +9,8 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
prepend ::EE::Projects::MergeRequestsController prepend ::EE::Projects::MergeRequestsController
skip_before_action :merge_request, only: [:index, :bulk_update] skip_before_action :merge_request, only: [:index, :bulk_update]
before_action :whitelist_query_limiting_ee, only: [:merge, :show]
before_action :whitelist_query_limiting, only: [:assign_related_issues, :update]
before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort] before_action :authorize_update_issuable!, only: [:close, :edit, :update, :remove_wip, :sort]
before_action :set_issuables_index, only: [:index] before_action :set_issuables_index, only: [:index]
before_action :authenticate_user!, only: [:assign_related_issues] before_action :authenticate_user!, only: [:assign_related_issues]
...@@ -348,4 +350,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo ...@@ -348,4 +350,14 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
access_denied! unless access_check access_denied! unless access_check
end end
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42441
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42438')
end
def whitelist_query_limiting_ee
# Also see https://gitlab.com/gitlab-org/gitlab-ee/issues/4793
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4792')
end
end end
...@@ -2,6 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include ApplicationHelper include ApplicationHelper
before_action :whitelist_query_limiting
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
...@@ -35,4 +36,8 @@ class Projects::NetworkController < Projects::ApplicationController ...@@ -35,4 +36,8 @@ class Projects::NetworkController < Projects::ApplicationController
@options[:extended_sha1] = params[:extended_sha1] @options[:extended_sha1] = params[:extended_sha1]
@commit = @repo.commit(@options[:extended_sha1]) @commit = @repo.commit(@options[:extended_sha1])
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42333')
end
end end
...@@ -2,6 +2,7 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -2,6 +2,7 @@ class Projects::NotesController < Projects::ApplicationController
include NotesActions include NotesActions
include ToggleAwardEmoji include ToggleAwardEmoji
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_read_note! before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create] before_action :authorize_create_note!, only: [:create]
before_action :authorize_resolve_note!, only: [:resolve, :unresolve] before_action :authorize_resolve_note!, only: [:resolve, :unresolve]
...@@ -79,4 +80,8 @@ class Projects::NotesController < Projects::ApplicationController ...@@ -79,4 +80,8 @@ class Projects::NotesController < Projects::ApplicationController
access_denied! unless can?(current_user, :create_note, noteable) access_denied! unless can?(current_user, :create_note, noteable)
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42383')
end
end end
class Projects::PipelinesController < Projects::ApplicationController class Projects::PipelinesController < Projects::ApplicationController
before_action :whitelist_query_limiting, only: [:create, :retry]
before_action :pipeline, except: [:index, :new, :create, :charts] before_action :pipeline, except: [:index, :new, :create, :charts]
before_action :commit, only: [:show, :builds, :failures] before_action :commit, only: [:show, :builds, :failures]
before_action :authorize_read_pipeline! before_action :authorize_read_pipeline!
...@@ -166,4 +167,9 @@ class Projects::PipelinesController < Projects::ApplicationController ...@@ -166,4 +167,9 @@ class Projects::PipelinesController < Projects::ApplicationController
def commit def commit
@commit ||= @pipeline.commit @commit ||= @pipeline.commit
end end
def whitelist_query_limiting
# Also see https://gitlab.com/gitlab-org/gitlab-ce/issues/42343
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42339')
end
end end
...@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -54,8 +54,8 @@ class Projects::WikisController < Projects::ApplicationController
else else
render 'edit' render 'edit'
end end
rescue WikiPage::PageChangedError rescue WikiPage::PageChangedError, WikiPage::PageRenameError => e
@conflict = true @error = e
render 'edit' render 'edit'
end end
......
...@@ -4,6 +4,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -4,6 +4,7 @@ class ProjectsController < Projects::ApplicationController
include PreviewMarkdown include PreviewMarkdown
prepend EE::ProjectsController prepend EE::ProjectsController
before_action :whitelist_query_limiting, only: [:create]
before_action :authenticate_user!, except: [:index, :show, :activity, :refs] before_action :authenticate_user!, except: [:index, :show, :activity, :refs]
before_action :redirect_git_extension, only: [:show] before_action :redirect_git_extension, only: [:show]
before_action :project, except: [:index, :new, :create] before_action :project, except: [:index, :new, :create]
...@@ -415,4 +416,8 @@ class ProjectsController < Projects::ApplicationController ...@@ -415,4 +416,8 @@ class ProjectsController < Projects::ApplicationController
# #
redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git' redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git'
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42440')
end
end end
...@@ -2,6 +2,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -2,6 +2,8 @@ class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify include Recaptcha::Verify
prepend EE::RegistrationsController prepend EE::RegistrationsController
before_action :whitelist_query_limiting, only: [:destroy]
def new def new
redirect_to(new_user_session_path) redirect_to(new_user_session_path)
end end
...@@ -84,4 +86,8 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -84,4 +86,8 @@ class RegistrationsController < Devise::RegistrationsController
def devise_mapping def devise_mapping
@devise_mapping ||= Devise.mappings[:user] @devise_mapping ||= Devise.mappings[:user]
end end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42380')
end
end end
class UserCalloutsController < ApplicationController
def create
if ensure_callout.persisted?
respond_to do |format|
format.json { head :ok }
end
else
respond_to do |format|
format.json { head :bad_request }
end
end
end
private
def ensure_callout
current_user.callouts.find_or_create_by(feature_name: UserCallout.feature_names[feature_name])
end
def feature_name
params.require(:feature_name)
end
end
module UserCalloutsHelper
GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze
def show_gke_cluster_integration_callout?(project)
can?(current_user, :create_cluster, project) &&
!user_dismissed?(GKE_CLUSTER_INTEGRATION)
end
private
def user_dismissed?(feature_name)
current_user&.callouts&.find_by(feature_name: feature_name)
end
end
...@@ -21,4 +21,22 @@ module WikiHelper ...@@ -21,4 +21,22 @@ module WikiHelper
add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after add_to_breadcrumb_dropdown link_to(WikiPage.unhyphenize(dir_or_page).capitalize, project_wiki_path(@project, current_slug)), location: :after
end end
end end
def wiki_page_errors(error)
return unless error
content_tag(:div, class: 'alert alert-danger') do
case error
when WikiPage::PageChangedError
page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
concat(
(s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
)
when WikiPage::PageRenameError
s_("WikiEdit|There is already a page with the same title in that path.")
else
error.message
end
end
end
end end
...@@ -87,20 +87,10 @@ module Storage ...@@ -87,20 +87,10 @@ module Storage
remove_exports! remove_exports!
end end
def remove_exports! def remove_legacy_exports!
Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) legacy_export_path = File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def export_path
File.join(Gitlab::ImportExport.storage_path, full_path_was)
end
def full_path_was FileUtils.rm_rf(legacy_export_path)
if parent
parent.full_path + '/' + path_was
else
path_was
end
end end
end end
end end
...@@ -312,12 +312,6 @@ class Group < Namespace ...@@ -312,12 +312,6 @@ class Group < Namespace
list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten list_of_ids.reverse.map { |group| variables[group.id] }.compact.flatten
end end
def full_path_was
return path_was unless has_parent?
"#{parent.full_path}/#{path_was}"
end
def group_member(user) def group_member(user)
if group_members.loaded? if group_members.loaded?
group_members.find { |gm| gm.user_id == user.id } group_members.find { |gm| gm.user_id == user.id }
......
...@@ -235,6 +235,24 @@ class Namespace < ActiveRecord::Base ...@@ -235,6 +235,24 @@ class Namespace < ActiveRecord::Base
feature_available?(:multiple_issue_boards) feature_available?(:multiple_issue_boards)
end end
def full_path_was
return path_was unless has_parent?
"#{parent.full_path}/#{path_was}"
end
# Exports belonging to projects with legacy storage are placed in a common
# subdirectory of the namespace, so a simple `rm -rf` is sufficient to remove
# them.
#
# Exports of projects using hashed storage are placed in a location defined
# only by the project ID, so each must be removed individually.
def remove_exports!
remove_legacy_exports!
all_projects.with_storage_feature(:repository).find_each(&:remove_exports)
end
private private
def refresh_access_of_projects_invited_groups def refresh_access_of_projects_invited_groups
......
...@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base ...@@ -73,6 +73,7 @@ class Project < ActiveRecord::Base
before_destroy :remove_private_deploy_keys before_destroy :remove_private_deploy_keys
after_destroy -> { run_after_commit { remove_pages } } after_destroy -> { run_after_commit { remove_pages } }
after_destroy :remove_exports
after_validation :check_pending_delete after_validation :check_pending_delete
...@@ -1537,6 +1538,8 @@ class Project < ActiveRecord::Base ...@@ -1537,6 +1538,8 @@ class Project < ActiveRecord::Base
end end
def export_path def export_path
return nil unless namespace.present? || hashed_storage?(:repository)
File.join(Gitlab::ImportExport.storage_path, disk_path) File.join(Gitlab::ImportExport.storage_path, disk_path)
end end
...@@ -1545,8 +1548,9 @@ class Project < ActiveRecord::Base ...@@ -1545,8 +1548,9 @@ class Project < ActiveRecord::Base
end end
def remove_exports def remove_exports
_, status = Gitlab::Popen.popen(%W(find #{export_path} -not -path #{export_path} -delete)) return nil unless export_path.present?
status.zero?
FileUtils.rm_rf(export_path)
end end
def full_path_slug def full_path_slug
......
...@@ -131,6 +131,8 @@ class ProjectWiki ...@@ -131,6 +131,8 @@ class ProjectWiki
end end
def delete_page(page, message = nil) def delete_page(page, message = nil)
return unless page
wiki.delete_page(page.path, commit_details(:deleted, message, page.title)) wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
update_elastic_index update_elastic_index
...@@ -145,6 +147,8 @@ class ProjectWiki ...@@ -145,6 +147,8 @@ class ProjectWiki
end end
def page_title_and_dir(title) def page_title_and_dir(title)
return unless title
title_array = title.split("/") title_array = title.split("/")
title = title_array.pop title = title_array.pop
[title, title_array.join("/")] [title, title_array.join("/")]
......
...@@ -33,7 +33,7 @@ class Upload < ActiveRecord::Base ...@@ -33,7 +33,7 @@ class Upload < ActiveRecord::Base
end end
def build_uploader def build_uploader
uploader_class.new(model).tap do |uploader| uploader_class.new(model, mount_point, **uploader_context).tap do |uploader|
uploader.upload = self uploader.upload = self
uploader.retrieve_from_store!(identifier) uploader.retrieve_from_store!(identifier)
end end
...@@ -43,6 +43,13 @@ class Upload < ActiveRecord::Base ...@@ -43,6 +43,13 @@ class Upload < ActiveRecord::Base
File.exist?(absolute_path) File.exist?(absolute_path)
end end
def uploader_context
{
identifier: identifier,
secret: secret
}.compact
end
private private
def checksummable? def checksummable?
...@@ -67,11 +74,15 @@ class Upload < ActiveRecord::Base ...@@ -67,11 +74,15 @@ class Upload < ActiveRecord::Base
!path.start_with?('/') !path.start_with?('/')
end end
def uploader_class
Object.const_get(uploader)
end
def identifier def identifier
File.basename(path) File.basename(path)
end end
def uploader_class def mount_point
Object.const_get(uploader) super&.to_sym
end end
end end
...@@ -137,6 +137,7 @@ class User < ActiveRecord::Base ...@@ -137,6 +137,7 @@ class User < ActiveRecord::Base
has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent
has_many :custom_attributes, class_name: 'UserCustomAttribute' has_many :custom_attributes, class_name: 'UserCustomAttribute'
has_many :callouts, class_name: 'UserCallout'
has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
# #
......
class UserCallout < ActiveRecord::Base
belongs_to :user
enum feature_name: {
gke_cluster_integration: 1
}
validates :user, presence: true
validates :feature_name,
presence: true,
uniqueness: { scope: :user_id },
inclusion: { in: UserCallout.feature_names.keys }
end
class WikiPage class WikiPage
PageChangedError = Class.new(StandardError) PageChangedError = Class.new(StandardError)
PageRenameError = Class.new(StandardError)
include ActiveModel::Validations include ActiveModel::Validations
include ActiveModel::Conversion include ActiveModel::Conversion
...@@ -102,7 +103,7 @@ class WikiPage ...@@ -102,7 +103,7 @@ class WikiPage
# The hierarchy of the directory this page is contained in. # The hierarchy of the directory this page is contained in.
def directory def directory
wiki.page_title_and_dir(slug).last wiki.page_title_and_dir(slug)&.last.to_s
end end
# The processed/formatted content of this page. # The processed/formatted content of this page.
...@@ -177,7 +178,7 @@ class WikiPage ...@@ -177,7 +178,7 @@ class WikiPage
# Creates a new Wiki Page. # Creates a new Wiki Page.
# #
# attr - Hash of attributes to set on the new page. # attr - Hash of attributes to set on the new page.
# :title - The title for the new page. # :title - The title (optionally including dir) for the new page.
# :content - The raw markup content. # :content - The raw markup content.
# :format - Optional symbol representing the # :format - Optional symbol representing the
# content format. Can be any type # content format. Can be any type
...@@ -189,7 +190,7 @@ class WikiPage ...@@ -189,7 +190,7 @@ class WikiPage
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def create(attrs = {}) def create(attrs = {})
@attributes.merge!(attrs) update_attributes(attrs)
save(page_details: title) do save(page_details: title) do
wiki.create_page(title, content, format, message) wiki.create_page(title, content, format, message)
...@@ -204,24 +205,29 @@ class WikiPage ...@@ -204,24 +205,29 @@ class WikiPage
# See ProjectWiki::MARKUPS Hash for available formats. # See ProjectWiki::MARKUPS Hash for available formats.
# :message - Optional commit message to set on the new version. # :message - Optional commit message to set on the new version.
# :last_commit_sha - Optional last commit sha to validate the page unchanged. # :last_commit_sha - Optional last commit sha to validate the page unchanged.
# :title - The Title to replace existing title # :title - The Title (optionally including dir) to replace existing title
# #
# Returns the String SHA1 of the newly created page # Returns the String SHA1 of the newly created page
# or False if the save was unsuccessful. # or False if the save was unsuccessful.
def update(attrs = {}) def update(attrs = {})
last_commit_sha = attrs.delete(:last_commit_sha) last_commit_sha = attrs.delete(:last_commit_sha)
if last_commit_sha && last_commit_sha != self.last_commit_sha if last_commit_sha && last_commit_sha != self.last_commit_sha
raise PageChangedError.new("You are attempting to update a page that has changed since you started editing it.") raise PageChangedError.new("WikiEdit|You are attempting to update a page that has changed since you started editing it.")
end end
attrs.slice!(:content, :format, :message, :title) update_attributes(attrs)
@attributes.merge!(attrs)
page_details = if title_changed?
if title.present? && @page.title != title page_details = title
title
else if wiki.find_page(page_details).present?
@page.url_path @attributes[:title] = @page.url_path
raise PageRenameError.new("WikiEdit|There is already a page with the same title in that path.")
end end
else
page_details = @page.url_path
end
save(page_details: page_details) do save(page_details: page_details) do
wiki.update_page( wiki.update_page(
...@@ -255,8 +261,44 @@ class WikiPage ...@@ -255,8 +261,44 @@ class WikiPage
page.version.to_s page.version.to_s
end end
def title_changed?
title.present? && self.class.unhyphenize(@page.url_path) != title
end
private private
# Process and format the title based on the user input.
def process_title(title)
return if title.blank?
title = deep_title_squish(title)
current_dirname = File.dirname(title)
if @page.present?
return title[1..-1] if current_dirname == '/'
return File.join([directory.presence, title].compact) if current_dirname == '.'
end
title
end
# This method squishes all the filename
# i.e: ' foo / bar / page_name' => 'foo/bar/page_name'
def deep_title_squish(title)
components = title.split(File::SEPARATOR).map(&:squish)
File.join(components)
end
# Updates the current @attributes hash by merging a hash of params
def update_attributes(attrs)
attrs[:title] = process_title(attrs[:title]) if attrs[:title].present?
attrs.slice!(:content, :format, :message, :title)
@attributes.merge!(attrs)
end
def set_attributes def set_attributes
attributes[:slug] = @page.url_path attributes[:slug] = @page.url_path
attributes[:title] = @page.title attributes[:title] = @page.title
......
...@@ -46,11 +46,11 @@ class FileMover ...@@ -46,11 +46,11 @@ class FileMover
end end
def uploader def uploader
@uploader ||= PersonalFileUploader.new(model, secret) @uploader ||= PersonalFileUploader.new(model, secret: secret)
end end
def temp_file_uploader def temp_file_uploader
@temp_file_uploader ||= PersonalFileUploader.new(nil, secret) @temp_file_uploader ||= PersonalFileUploader.new(nil, secret: secret)
end end
def revert def revert
......
...@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader ...@@ -62,9 +62,11 @@ class FileUploader < GitlabUploader
attr_accessor :model attr_accessor :model
def initialize(model, secret = nil) def initialize(model, mounted_as = nil, **uploader_context)
super(model, nil, **uploader_context)
@model = model @model = model
@secret = secret apply_context!(uploader_context)
end end
def base_dir def base_dir
...@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader ...@@ -107,15 +109,17 @@ class FileUploader < GitlabUploader
self.file.filename self.file.filename
end end
# the upload does not hold the secret, but holds the path
# which contains the secret: extract it
def upload=(value) def upload=(value)
super
return unless value
return if apply_context!(value.uploader_context)
# fallback to the regex based extraction
if matches = DYNAMIC_PATH_PATTERN.match(value.path) if matches = DYNAMIC_PATH_PATTERN.match(value.path)
@secret = matches[:secret] @secret = matches[:secret]
@identifier = matches[:identifier] @identifier = matches[:identifier]
end end
super
end end
def secret def secret
...@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader ...@@ -124,6 +128,18 @@ class FileUploader < GitlabUploader
private private
def apply_context!(uploader_context)
@secret, @identifier = uploader_context.values_at(:secret, :identifier)
!!(@secret && @identifier)
end
def build_upload
super.tap do |upload|
upload.secret = secret
end
end
def markdown_name def markdown_name
(image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]") (image_or_video? ? File.basename(filename, File.extname(filename)) : filename).gsub("]", "\\]")
end end
......
...@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base ...@@ -29,6 +29,10 @@ class GitlabUploader < CarrierWave::Uploader::Base
delegate :base_dir, :file_storage?, to: :class delegate :base_dir, :file_storage?, to: :class
def initialize(model, mounted_as = nil, **uploader_context)
super(model, mounted_as)
end
def file_cache_storage? def file_cache_storage?
cache_storage.is_a?(CarrierWave::Storage::File) cache_storage.is_a?(CarrierWave::Storage::File)
end end
......
...@@ -24,7 +24,7 @@ module RecordsUploads ...@@ -24,7 +24,7 @@ module RecordsUploads
uploads.where(path: upload_path).delete_all uploads.where(path: upload_path).delete_all
upload.destroy! if upload upload.destroy! if upload
self.upload = build_upload_from_uploader(self) self.upload = build_upload
upload.save! upload.save!
end end
end end
...@@ -39,12 +39,13 @@ module RecordsUploads ...@@ -39,12 +39,13 @@ module RecordsUploads
Upload.order(id: :desc).where(uploader: self.class.to_s) Upload.order(id: :desc).where(uploader: self.class.to_s)
end end
def build_upload_from_uploader(uploader) def build_upload
Upload.new( Upload.new(
size: uploader.file.size, uploader: self.class.to_s,
path: uploader.upload_path, size: file.size,
model: uploader.model, path: upload_path,
uploader: uploader.class.to_s model: model,
mount_point: mounted_as
) )
end end
......
...@@ -9,7 +9,13 @@ ...@@ -9,7 +9,13 @@
.form-group .form-group
.col-sm-12= f.label :title, class: 'control-label-full-width' .col-sm-12= f.label :title, class: 'control-label-full-width'
.col-sm-12= f.text_field :title, class: 'form-control', value: @page.title .col-sm-12
= f.text_field :title, class: 'form-control', value: @page.title
- if @page.persisted?
%span.edit-wiki-page-slug-tip
= icon('lightbulb-o')
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
= link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank'
.form-group .form-group
.col-sm-12= f.label :format, class: 'control-label-full-width' .col-sm-12= f.label :format, class: 'control-label-full-width'
.col-sm-12 .col-sm-12
......
- @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout - @content_class = "limit-container-width limit-container-width-sm" unless fluid_layout
- page_title _("Edit"), @page.title.capitalize, _("Wiki") - page_title _("Edit"), @page.title.capitalize, _("Wiki")
- if @conflict = wiki_page_errors(@error)
.alert.alert-danger
- page_link = link_to s_("WikiPageConflictMessage|the page"), project_wiki_path(@project, @page), target: "_blank"
= (s_("WikiPageConflictMessage|Someone edited the page the same time you did. Please check out %{page_link} and make sure your changes will not unintentionally remove theirs.") % { page_link: page_link }).html_safe
.wiki-page-header.has-sidebar-toggle .wiki-page-header.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" } %button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
......
---
title: Report the correct version and revision for Geo node status requests
merge_request: 4353
author:
type: fixed
---
title: FIx N+1 queries with /api/v4/groups endpoint
merge_request:
author:
type: performance
---
title: Fix export removal for hashed-storage projects within a renamed or deleted
namespace
merge_request: 16658
author:
type: fixed
---
title: Added uploader metadata to the uploads.
merge_request: 16779
author:
type: added
---
title: Allow moving wiki pages from the UI
merge_request: 16313
author:
type: fixed
---
title: Add backend for persistently dismissably callouts
merge_request:
author:
type: added
---
title: Track and act upon the number of executed queries
merge_request:
author:
type: added
...@@ -193,12 +193,6 @@ production: &base ...@@ -193,12 +193,6 @@ production: &base
# endpoint: 'http://127.0.0.1:9000' # default: nil # endpoint: 'http://127.0.0.1:9000' # default: nil
# path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object' # path_style: true # Use 'host/bucket_name/object' instead of 'bucket_name.host/object'
## Uploads (attachments, avatars, etc...)
uploads:
# The location where uploads objects are stored (default: public/).
# storage_path: public/
# base_dir: uploads/-/system
## GitLab Pages ## GitLab Pages
pages: pages:
enabled: false enabled: false
......
...@@ -35,6 +35,88 @@ module Gollum ...@@ -35,6 +35,88 @@ module Gollum
[] []
end end
end end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def update_page(page, name, format, data, commit = {})
name = name ? ::File.basename(name) : page.name
format ||= page.format
dir = ::File.dirname(page.path)
dir = '' if dir == '.'
filename = (rename = page.name != name) ? Gollum::Page.cname(name) : page.filename_stripped
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
if !rename && page.format == format
committer.add(page.path, normalize(data))
else
committer.delete(page.path)
committer.add_to_index(dir, filename, format, data)
end
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(dir, page.filename_stripped, page.format)
index.update_working_dir(dir, filename, format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def rename_page(page, rename, commit = {})
return false if page.nil?
return false if rename.nil? || rename.empty?
(target_dir, target_name) = ::File.split(rename)
(source_dir, source_name) = ::File.split(page.path)
source_name = page.filename_stripped
# File.split gives us relative paths with ".", commiter.add_to_index doesn't like that.
target_dir = '' if target_dir == '.'
source_dir = '' if source_dir == '.'
target_dir = target_dir.gsub(/^\//, '') # rubocop:disable Style/RegexpLiteral
# if the rename is a NOOP, abort
if source_dir == target_dir && source_name == target_name
return false
end
multi_commit = !!commit[:committer]
committer = multi_commit ? commit[:committer] : Committer.new(self, commit)
# This piece only works for multi_commit
# If we are in a commit batch and one of the previous operations
# has updated the page, any information we ask to the page can be outdated.
# Therefore, we should ask first to the current committer tree to see if
# there is any updated change.
raw_data = raw_data_in_committer(committer, source_dir, page.filename) ||
raw_data_in_committer(committer, source_dir, "#{target_name}.#{Page.format_to_ext(page.format)}") ||
page.raw_data
committer.delete(page.path)
committer.add_to_index(target_dir, target_name, page.format, raw_data)
committer.after_commit do |index, _sha|
@access.refresh
index.update_working_dir(source_dir, source_name, page.format)
index.update_working_dir(target_dir, target_name, page.format)
end
multi_commit ? committer : committer.commit
end
# Remove if https://github.com/gollum/gollum-lib/pull/292 has been merged
def raw_data_in_committer(committer, dir, filename)
data = nil
[*dir.split(::File::SEPARATOR), filename].each do |key|
data = data ? data[key] : committer.tree[key]
break unless data
end
data
end
end end
module Git module Git
......
if Gitlab::QueryLimiting.enable?
require_dependency 'gitlab/query_limiting/active_support_subscriber'
require_dependency 'gitlab/query_limiting/transaction'
require_dependency 'gitlab/query_limiting/middleware'
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::QueryLimiting::Middleware)
end
end
...@@ -75,6 +75,9 @@ Rails.application.routes.draw do ...@@ -75,6 +75,9 @@ Rails.application.routes.draw do
resources :issues, module: :boards, only: [:index, :update] resources :issues, module: :boards, only: [:index, :update]
end end
# UserCallouts
resources :user_callouts, only: [:create]
end end
# Koding route # Koding route
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class CreateUserCallouts < ActiveRecord::Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
create_table :user_callouts do |t|
t.integer :feature_name, null: false
t.references :user, index: true, foreign_key: { on_delete: :cascade }, null: false
end
add_index :user_callouts, [:user_id, :feature_name], unique: true
end
end
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddUploadsBuilderContext < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
def change
add_column :uploads, :mount_point, :string
add_column :uploads, :secret, :string
end
end
...@@ -999,6 +999,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -999,6 +999,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.integer "job_artifacts_count" t.integer "job_artifacts_count"
t.integer "job_artifacts_synced_count" t.integer "job_artifacts_synced_count"
t.integer "job_artifacts_failed_count" t.integer "job_artifacts_failed_count"
t.string "version"
t.string "revision"
end end
add_index "geo_node_statuses", ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree add_index "geo_node_statuses", ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree
...@@ -2253,6 +2255,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2253,6 +2255,8 @@ ActiveRecord::Schema.define(version: 20180202111106) do
t.string "uploader", null: false t.string "uploader", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.integer "store" t.integer "store"
t.string "mount_point"
t.string "secret"
end end
add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree add_index "uploads", ["checksum"], name: "index_uploads_on_checksum", using: :btree
...@@ -2271,6 +2275,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2271,6 +2275,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree
create_table "user_callouts", force: :cascade do |t|
t.integer "feature_name", null: false
t.integer "user_id", null: false
end
add_index "user_callouts", ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree
add_index "user_callouts", ["user_id"], name: "index_user_callouts_on_user_id", using: :btree
create_table "user_custom_attributes", force: :cascade do |t| create_table "user_custom_attributes", force: :cascade do |t|
t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "updated_at", null: false
...@@ -2604,6 +2616,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do ...@@ -2604,6 +2616,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do
add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade
add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade
add_foreign_key "u2f_registrations", "users" add_foreign_key "u2f_registrations", "users"
add_foreign_key "user_callouts", "users", on_delete: :cascade
add_foreign_key "user_custom_attributes", "users", on_delete: :cascade add_foreign_key "user_custom_attributes", "users", on_delete: :cascade
add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
......
...@@ -75,6 +75,7 @@ comments: false ...@@ -75,6 +75,7 @@ comments: false
- [Ordering table columns](ordering_table_columns.md) - [Ordering table columns](ordering_table_columns.md)
- [Verifying database capabilities](verifying_database_capabilities.md) - [Verifying database capabilities](verifying_database_capabilities.md)
- [Database Debugging and Troubleshooting](database_debugging.md) - [Database Debugging and Troubleshooting](database_debugging.md)
- [Query Count Limits](query_count_limits.md)
## Testing guides ## Testing guides
......
# Query Count Limits
Each controller or API endpoint is allowed to execute up to 100 SQL queries. In
a production environment we'll only log an error in case this threshold is
exceeded, but in a test environment we'll raise an error instead.
## Solving Failing Tests
When a test fails because it executes more than 100 SQL queries there are two
solutions to this problem:
1. Reduce the number of SQL queries that are executed.
2. Whitelist the controller or API endpoint.
You should only resort to whitelisting when an existing controller or endpoint
is to blame as in this case reducing the number of SQL queries can take a lot of
effort. Newly added controllers and endpoints are not allowed to execute more
than 100 SQL queries and no exceptions will be made for this rule. _If_ a large
number of SQL queries is necessary to perform certain work it's best to have
this work performed by Sidekiq instead of doing this directly in a web request.
## Whitelisting
In the event that you _have_ to whitelist a controller you'll first need to
create an issue. This issue should (preferably in the title) mention the
controller or endpoint and include the appropriate labels (`database`,
`performance`, and at least a team specific label such as `Discussion`).
Once the issue has been created you can whitelist the code in question. For
Rails controllers it's best to create a `before_action` hook that runs as early
as possible. The called method in turn should call
`Gitlab::QueryLimiting.whitelist('issue URL here')`. For example:
```ruby
class MyController < ApplicationController
before_action :whitelist_query_limiting, only: [:show]
def index
# ...
end
def show
# ...
end
def whitelist_query_limiting
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/...')
end
end
```
By using a `before_action` you don't have to modify the controller method in
question, reducing the likelihood of merge conflicts.
For Grape API endpoints there unfortunately is not a reliable way of running a
hook before a specific endpoint. This means that you have to add the whitelist
call directly into the endpoint like so:
```ruby
get '/projects/:id/foo' do
Gitlab::QueryLimiting.whitelist('...')
# ...
end
```
...@@ -109,7 +109,7 @@ keys must be manually replicated to the secondary node. ...@@ -109,7 +109,7 @@ keys must be manually replicated to the secondary node.
1. Make a backup of any existing SSH host keys: 1. Make a backup of any existing SSH host keys:
```bash ```bash
find /etc/ssh -iname ssh_host_* -exec mv {} {}.backup.`date +%F` \; find /etc/ssh -iname ssh_host_* -exec cp {} {}.backup.`date +%F` \;
``` ```
1. SSH into the **primary** node, and execute the command below: 1. SSH into the **primary** node, and execute the command below:
...@@ -118,7 +118,7 @@ keys must be manually replicated to the secondary node. ...@@ -118,7 +118,7 @@ keys must be manually replicated to the secondary node.
sudo find /etc/ssh -iname ssh_host_* -not -iname '*.pub' sudo find /etc/ssh -iname ssh_host_* -not -iname '*.pub'
``` ```
1. For each file in that list copy the file from the primary node to 1. For each file in that list replace the file from the primary node to
the **same** location on your **secondary** node. the **same** location on your **secondary** node.
1. On your **secondary** node, ensure the file permissions are correct: 1. On your **secondary** node, ensure the file permissions are correct:
......
...@@ -64,6 +64,18 @@ effect. ...@@ -64,6 +64,18 @@ effect.
You can find the **Delete** button only when editing a page. Click on it and You can find the **Delete** button only when editing a page. Click on it and
confirm you want the page to be deleted. confirm you want the page to be deleted.
## Moving a wiki page
You can move a wiki page from one directory to another by specifying the full
path in the wiki page title in the [edit](#editing-a-wiki-page) form.
![Moving a page](img/wiki_move_page_1.png)
![After moving a page](img/wiki_move_page_2.png)
In order to move a wiki page to the root directory, the wiki page title must
be preceded by the slash (`/`) character.
## Viewing a list of all created wiki pages ## Viewing a list of all created wiki pages
Every wiki has a sidebar from which a short list of the created pages can be Every wiki has a sidebar from which a short list of the created pages can be
......
module EE module EE
module GeoHelper module GeoHelper
def node_vue_list_properties
version, revision =
if ::Gitlab::Geo.primary?
[::Gitlab::VERSION, ::Gitlab::REVISION]
else
status = ::Gitlab::Geo.primary_node&.status
[status&.version, status&.revision]
end
{
primary_version: version.to_s,
primary_revision: revision.to_s,
node_details_path: admin_geo_nodes_path.to_s,
node_actions_allowed: ::Gitlab::Database.read_write?.to_s,
node_edit_allowed: ::Gitlab::Geo.license_allows?.to_s
}
end
def node_namespaces_options(namespaces) def node_namespaces_options(namespaces)
namespaces.map { |g| { id: g.id, text: g.full_name } } namespaces.map { |g| { id: g.id, text: g.full_name } }
end end
......
...@@ -4,7 +4,7 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -4,7 +4,7 @@ class GeoNodeStatus < ActiveRecord::Base
delegate :selective_sync_type, to: :geo_node delegate :selective_sync_type, to: :geo_node
# Whether we were successful in reaching this node # Whether we were successful in reaching this node
attr_accessor :success, :version, :revision attr_accessor :success
attr_writer :health_status attr_writer :health_status
attr_accessor :storage_shards attr_accessor :storage_shards
...@@ -58,10 +58,18 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -58,10 +58,18 @@ class GeoNodeStatus < ActiveRecord::Base
GeoNodeStatus.new(HashWithIndifferentAccess.new(json_data)) GeoNodeStatus.new(HashWithIndifferentAccess.new(json_data))
end end
EXCLUDED_PARAMS = %w[id created_at].freeze
EXTRA_PARAMS = %w[
success
health
health_status
last_event_timestamp
cursor_last_event_timestamp
storage_shards
].freeze
def self.allowed_params def self.allowed_params
excluded_params = %w(id created_at updated_at).freeze self.column_names - EXCLUDED_PARAMS + EXTRA_PARAMS
extra_params = %w(success health health_status last_event_timestamp cursor_last_event_timestamp version revision storage_shards updated_at).freeze
self.column_names - excluded_params + extra_params
end end
def load_data_from_current_node def load_data_from_current_node
...@@ -83,6 +91,9 @@ class GeoNodeStatus < ActiveRecord::Base ...@@ -83,6 +91,9 @@ class GeoNodeStatus < ActiveRecord::Base
self.last_successful_status_check_at = Time.now self.last_successful_status_check_at = Time.now
self.storage_shards = StorageShard.all self.storage_shards = StorageShard.all
self.version = Gitlab::VERSION
self.revision = Gitlab::REVISION
load_primary_data load_primary_data
load_secondary_data load_secondary_data
......
...@@ -37,8 +37,10 @@ module ObjectStorage ...@@ -37,8 +37,10 @@ module ObjectStorage
super super
end end
def build_upload_from_uploader(uploader) def build_upload
super.tap { |upload| upload.store = object_store } super.tap do |upload|
upload.store = object_store
end
end end
def upload=(upload) def upload=(upload)
......
...@@ -23,6 +23,6 @@ ...@@ -23,6 +23,6 @@
.alert.alert-warning WARNING: Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version. .alert.alert-warning WARNING: Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version.
- if @nodes.any? - if @nodes.any?
#js-geo-nodes{ data: { primary_version: "#{Gitlab::VERSION}", primary_revision: "#{Gitlab::REVISION}", node_details_path: "#{admin_geo_nodes_path}", node_actions_allowed: "#{Gitlab::Database.read_write?}", node_edit_allowed: "#{Gitlab::Geo.license_allows?}" } } #js-geo-nodes{ data: node_vue_list_properties }
- else - else
= render 'shared/empty_states/geo' = render 'shared/empty_states/geo'
class StoreVersionAndRevisionInGeoNodeStatus < ActiveRecord::Migration
DOWNTIME = false
def change
add_column :geo_node_statuses, :version, :string
add_column :geo_node_statuses, :revision, :string
end
end
module EE
module API
module APIGuard
module HelperMethods
extend ::Gitlab::Utils::Override
override :find_user_from_sources
def find_user_from_sources
find_user_from_access_token ||
find_user_from_job_token ||
find_user_from_warden
end
end
end
end
end
...@@ -39,10 +39,11 @@ module API ...@@ -39,10 +39,11 @@ module API
# Helper Methods for Grape Endpoint # Helper Methods for Grape Endpoint
module HelperMethods module HelperMethods
prepend EE::API::APIGuard::HelperMethods
include Gitlab::Auth::UserAuthFinders include Gitlab::Auth::UserAuthFinders
def find_current_user! def find_current_user!
user = find_user_from_access_token || find_user_from_job_token || find_user_from_warden user = find_user_from_sources
return unless user return unless user
forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) forbidden!('User is blocked') unless Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api)
...@@ -50,6 +51,10 @@ module API ...@@ -50,6 +51,10 @@ module API
user user
end end
def find_user_from_sources
find_user_from_access_token || find_user_from_warden
end
private private
# An array of scopes that were registered (using `allow_access_with_scope`) # An array of scopes that were registered (using `allow_access_with_scope`)
......
...@@ -29,6 +29,8 @@ module API ...@@ -29,6 +29,8 @@ module API
use :pagination use :pagination
end end
get ':id/repository/branches' do get ':id/repository/branches' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42329')
repository = user_project.repository repository = user_project.repository
branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name)) branches = ::Kaminari.paginate_array(repository.branches.sort_by(&:name))
merged_branch_names = repository.merged_branch_names(branches.map(&:name)) merged_branch_names = repository.merged_branch_names(branches.map(&:name))
......
...@@ -1267,14 +1267,6 @@ module API ...@@ -1267,14 +1267,6 @@ module API
def missing_oauth_application def missing_oauth_application
object.geo_node.missing_oauth_application? object.geo_node.missing_oauth_application?
end end
def version
Gitlab::VERSION
end
def revision
Gitlab::REVISION
end
end end
class PersonalAccessToken < Grape::Entity class PersonalAccessToken < Grape::Entity
......
...@@ -54,6 +54,8 @@ module API ...@@ -54,6 +54,8 @@ module API
find_params[:parent] = find_group!(params[:id]) if params[:id] find_params[:parent] = find_group!(params[:id]) if params[:id]
groups = GroupsFinder.new(current_user, find_params).execute groups = GroupsFinder.new(current_user, find_params).execute
# EE-only
groups = groups.preload(:ldap_group_links)
groups = groups.search(params[:search]) if params[:search].present? groups = groups.search(params[:search]) if params[:search].present?
groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present? groups = groups.where.not(id: params[:skip_groups]) if params[:skip_groups].present?
groups = groups.reorder(params[:order_by] => params[:sort]) groups = groups.reorder(params[:order_by] => params[:sort])
...@@ -181,6 +183,8 @@ module API ...@@ -181,6 +183,8 @@ module API
desc 'Remove a group.' desc 'Remove a group.'
delete ":id" do delete ":id" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4795')
group = find_group!(params[:id]) group = find_group!(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
......
...@@ -166,6 +166,8 @@ module API ...@@ -166,6 +166,8 @@ module API
use :issue_params use :issue_params
end end
post ':id/issues' do post ':id/issues' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42320')
authorize! :create_issue, user_project authorize! :create_issue, user_project
# Setting created_at time only allowed for admins and project owners # Setting created_at time only allowed for admins and project owners
...@@ -207,6 +209,8 @@ module API ...@@ -207,6 +209,8 @@ module API
:weight, :discussion_locked :weight, :discussion_locked
end end
put ':id/issues/:issue_iid' do put ':id/issues/:issue_iid' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42322')
issue = user_project.issues.find_by!(iid: params.delete(:issue_iid)) issue = user_project.issues.find_by!(iid: params.delete(:issue_iid))
authorize! :update_issue, issue authorize! :update_issue, issue
...@@ -240,6 +244,8 @@ module API ...@@ -240,6 +244,8 @@ module API
requires :to_project_id, type: Integer, desc: 'The ID of the new project' requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end end
post ':id/issues/:issue_iid/move' do post ':id/issues/:issue_iid/move' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42323')
issue = user_project.issues.find_by(iid: params[:issue_iid]) issue = user_project.issues.find_by(iid: params[:issue_iid])
not_found!('Issue') unless issue not_found!('Issue') unless issue
......
...@@ -164,6 +164,8 @@ module API ...@@ -164,6 +164,8 @@ module API
use :optional_params use :optional_params
end end
post ":id/merge_requests" do post ":id/merge_requests" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316')
authorize! :create_merge_request, user_project authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
...@@ -273,6 +275,8 @@ module API ...@@ -273,6 +275,8 @@ module API
at_least_one_of(*(at_least_one_of_ce + at_least_one_of_ee)) at_least_one_of(*(at_least_one_of_ce + at_least_one_of_ee))
end end
put ':id/merge_requests/:merge_request_iid' do put ':id/merge_requests/:merge_request_iid' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42318')
merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request) merge_request = find_merge_request_with_access(params.delete(:merge_request_iid), :update_merge_request)
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
...@@ -303,6 +307,8 @@ module API ...@@ -303,6 +307,8 @@ module API
optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end end
put ':id/merge_requests/:merge_request_iid/merge' do put ':id/merge_requests/:merge_request_iid/merge' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42317')
merge_request = find_project_merge_request(params[:merge_request_iid]) merge_request = find_project_merge_request(params[:merge_request_iid])
merge_when_pipeline_succeeds = to_boolean(params[:merge_when_pipeline_succeeds]) merge_when_pipeline_succeeds = to_boolean(params[:merge_when_pipeline_succeeds])
......
...@@ -42,6 +42,8 @@ module API ...@@ -42,6 +42,8 @@ module API
requires :ref, type: String, desc: 'Reference' requires :ref, type: String, desc: 'Reference'
end end
post ':id/pipeline' do post ':id/pipeline' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42124')
authorize! :create_pipeline, user_project authorize! :create_pipeline, user_project
new_pipeline = Ci::CreatePipelineService.new(user_project, new_pipeline = Ci::CreatePipelineService.new(user_project,
......
...@@ -216,6 +216,8 @@ module API ...@@ -216,6 +216,8 @@ module API
optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into' optional :namespace, type: String, desc: 'The ID or name of the namespace that the project will be forked into'
end end
post ':id/fork' do post ':id/fork' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42284')
fork_params = declared_params(include_missing: false) fork_params = declared_params(include_missing: false)
namespace_id = fork_params[:namespace] namespace_id = fork_params[:namespace]
......
...@@ -15,6 +15,8 @@ module API ...@@ -15,6 +15,8 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build' optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42283')
# validate variables # validate variables
params[:variables] = params[:variables].to_h params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) } unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
......
...@@ -389,6 +389,8 @@ module API ...@@ -389,6 +389,8 @@ module API
optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions" optional :hard_delete, type: Boolean, desc: "Whether to remove a user's contributions"
end end
delete ":id" do delete ":id" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42279')
authenticated_as_admin! authenticated_as_admin!
user = User.find_by(id: params[:id]) user = User.find_by(id: params[:id])
......
...@@ -14,6 +14,8 @@ module API ...@@ -14,6 +14,8 @@ module API
success ::API::Entities::Branch success ::API::Entities::Branch
end end
get ":id/repository/branches" do get ":id/repository/branches" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42276')
repository = user_project.repository repository = user_project.repository
branches = repository.branches.sort_by(&:name) branches = repository.branches.sort_by(&:name)
merged_branch_names = repository.merged_branch_names(branches.map(&:name)) merged_branch_names = repository.merged_branch_names(branches.map(&:name))
......
...@@ -151,6 +151,8 @@ module API ...@@ -151,6 +151,8 @@ module API
desc 'Remove a group.' desc 'Remove a group.'
delete ":id" do delete ":id" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4797')
group = find_group!(params[:id]) group = find_group!(params[:id])
authorize! :admin_group, group authorize! :admin_group, group
present ::Groups::DestroyService.new(group, current_user).execute, with: Entities::GroupDetail, current_user: current_user present ::Groups::DestroyService.new(group, current_user).execute, with: Entities::GroupDetail, current_user: current_user
......
...@@ -135,6 +135,8 @@ module API ...@@ -135,6 +135,8 @@ module API
use :issue_params use :issue_params
end end
post ':id/issues' do post ':id/issues' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42131')
# Setting created_at time only allowed for admins and project owners # Setting created_at time only allowed for admins and project owners
unless current_user.admin? || user_project.owner == current_user unless current_user.admin? || user_project.owner == current_user
params.delete(:created_at) params.delete(:created_at)
...@@ -171,6 +173,8 @@ module API ...@@ -171,6 +173,8 @@ module API
:weight :weight
end end
put ':id/issues/:issue_id' do put ':id/issues/:issue_id' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42132')
issue = user_project.issues.find(params.delete(:issue_id)) issue = user_project.issues.find(params.delete(:issue_id))
authorize! :update_issue, issue authorize! :update_issue, issue
...@@ -203,6 +207,8 @@ module API ...@@ -203,6 +207,8 @@ module API
requires :to_project_id, type: Integer, desc: 'The ID of the new project' requires :to_project_id, type: Integer, desc: 'The ID of the new project'
end end
post ':id/issues/:issue_id/move' do post ':id/issues/:issue_id/move' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42133')
issue = user_project.issues.find_by(id: params[:issue_id]) issue = user_project.issues.find_by(id: params[:issue_id])
not_found!('Issue') unless issue not_found!('Issue') unless issue
......
...@@ -93,6 +93,8 @@ module API ...@@ -93,6 +93,8 @@ module API
use :optional_params use :optional_params
end end
post ":id/merge_requests" do post ":id/merge_requests" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126')
authorize! :create_merge_request, user_project authorize! :create_merge_request, user_project
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
...@@ -169,6 +171,8 @@ module API ...@@ -169,6 +171,8 @@ module API
:remove_source_branch, :squash :remove_source_branch, :squash
end end
put path do put path do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42127')
merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request) merge_request = find_merge_request_with_access(params.delete(:merge_request_id), :update_merge_request)
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
...@@ -196,6 +200,8 @@ module API ...@@ -196,6 +200,8 @@ module API
optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge' optional :squash, type: Boolean, desc: 'When true, the commits will be squashed into a single commit on merge'
end end
put "#{path}/merge" do put "#{path}/merge" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ee/issues/4796')
merge_request = find_project_merge_request(params[:merge_request_id]) merge_request = find_project_merge_request(params[:merge_request_id])
# Merge request can not be merged # Merge request can not be merged
......
...@@ -19,6 +19,8 @@ module API ...@@ -19,6 +19,8 @@ module API
desc: 'Either running, branches, or tags' desc: 'Either running, branches, or tags'
end end
get ':id/pipelines' do get ':id/pipelines' do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42123')
authorize! :read_pipeline, user_project authorize! :read_pipeline, user_project
pipelines = PipelinesFinder.new(user_project, scope: params[:scope]).execute pipelines = PipelinesFinder.new(user_project, scope: params[:scope]).execute
......
...@@ -16,6 +16,8 @@ module API ...@@ -16,6 +16,8 @@ module API
optional :variables, type: Hash, desc: 'The list of variables to be injected into build' optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end end
post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do post ":id/(ref/:ref/)trigger/builds", requirements: { ref: /.+/ } do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42121')
# validate variables # validate variables
params[:variables] = params[:variables].to_h params[:variables] = params[:variables].to_h
unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) } unless params[:variables].all? { |key, value| key.is_a?(String) && value.is_a?(String) }
......
...@@ -46,7 +46,7 @@ module Gitlab ...@@ -46,7 +46,7 @@ module Gitlab
private private
def find_file(project, secret, file) def find_file(project, secret, file)
uploader = FileUploader.new(project, secret) uploader = FileUploader.new(project, secret: secret)
uploader.retrieve_from_store!(file) uploader.retrieve_from_store!(file)
uploader.file uploader.file
end end
......
...@@ -1222,33 +1222,13 @@ module Gitlab ...@@ -1222,33 +1222,13 @@ module Gitlab
end end
def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:) def squash(user, squash_id, branch:, start_sha:, end_sha:, author:, message:)
squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id) gitaly_migrate(:squash) do |is_enabled|
env = git_env_for_user(user).merge( if is_enabled
'GIT_AUTHOR_NAME' => author.name, gitaly_operation_client.user_squash(user, squash_id, branch,
'GIT_AUTHOR_EMAIL' => author.email start_sha, end_sha, author, message)
) else
diff_range = "#{start_sha}...#{end_sha}" git_squash(user, squash_id, branch, start_sha, end_sha, author, message)
diff_files = run_git!(
%W(diff --name-only --diff-filter=a --binary #{diff_range})
).chomp
with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do
# Apply diff of the `diff_range` to the worktree
diff = run_git!(%W(diff --binary #{diff_range}))
run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
stdin.write(diff)
end end
# Commit the `diff_range` diff
run_git!(%W(commit --no-verify --message #{message}), chdir: squash_path, env: env)
# Return the squash sha. May print a warning for ambiguous refs, but
# we can ignore that with `--quiet` and just take the SHA, if present.
# HEAD here always refers to the current HEAD commit, even if there is
# another ref called HEAD.
run_git!(
%w(rev-parse --quiet --verify HEAD), chdir: squash_path, env: env
).chomp
end end
end end
...@@ -1476,6 +1456,21 @@ module Gitlab ...@@ -1476,6 +1456,21 @@ module Gitlab
rugged.config['gitlab.fullpath'] = full_path rugged.config['gitlab.fullpath'] = full_path
end end
def refs_contains_sha(ref_type, sha)
args = %W(#{ref_type} --contains #{sha})
names = run_git(args).first
return [] unless names.respond_to?(:split)
names = names.split("\n").map(&:strip)
names.each do |name|
name.slice! '* '
end
names
end
def shell_write_ref(ref_path, ref, old_ref) def shell_write_ref(ref_path, ref, old_ref)
raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ') raise ArgumentError, "invalid ref_path #{ref_path.inspect}" if ref_path.include?(' ')
raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00") raise ArgumentError, "invalid ref #{ref.inspect}" if ref.include?("\x00")
...@@ -2182,6 +2177,37 @@ module Gitlab ...@@ -2182,6 +2177,37 @@ module Gitlab
end end
end end
def git_squash(user, squash_id, branch, start_sha, end_sha, author, message)
squash_path = worktree_path(SQUASH_WORKTREE_PREFIX, squash_id)
env = git_env_for_user(user).merge(
'GIT_AUTHOR_NAME' => author.name,
'GIT_AUTHOR_EMAIL' => author.email
)
diff_range = "#{start_sha}...#{end_sha}"
diff_files = run_git!(
%W(diff --name-only --diff-filter=a --binary #{diff_range})
).chomp
with_worktree(squash_path, branch, sparse_checkout_files: diff_files, env: env) do
# Apply diff of the `diff_range` to the worktree
diff = run_git!(%W(diff --binary #{diff_range}))
run_git!(%w(apply --index), chdir: squash_path, env: env) do |stdin|
stdin.write(diff)
end
# Commit the `diff_range` diff
run_git!(%W(commit --no-verify --message #{message}), chdir: squash_path, env: env)
# Return the squash sha. May print a warning for ambiguous refs, but
# we can ignore that with `--quiet` and just take the SHA, if present.
# HEAD here always refers to the current HEAD commit, even if there is
# another ref called HEAD.
run_git!(
%w(rev-parse --quiet --verify HEAD), chdir: squash_path, env: env
).chomp
end
end
def local_fetch_ref(source_path, source_ref:, target_ref:) def local_fetch_ref(source_path, source_ref:, target_ref:)
args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref}) args = %W(fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})
run_git(args) run_git(args)
......
...@@ -25,8 +25,9 @@ module Gitlab ...@@ -25,8 +25,9 @@ module Gitlab
@repository.exists? @repository.exists?
end end
# Disabled because of https://gitlab.com/gitlab-org/gitaly/merge_requests/539
def write_page(name, format, content, commit_details) def write_page(name, format, content, commit_details)
@repository.gitaly_migrate(:wiki_write_page) do |is_enabled| @repository.gitaly_migrate(:wiki_write_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_write_page(name, format, content, commit_details) gitaly_write_page(name, format, content, commit_details)
gollum_wiki.clear_cache gollum_wiki.clear_cache
...@@ -47,8 +48,9 @@ module Gitlab ...@@ -47,8 +48,9 @@ module Gitlab
end end
end end
# Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42094
def update_page(page_path, title, format, content, commit_details) def update_page(page_path, title, format, content, commit_details)
@repository.gitaly_migrate(:wiki_update_page) do |is_enabled| @repository.gitaly_migrate(:wiki_update_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_update_page(page_path, title, format, content, commit_details) gitaly_update_page(page_path, title, format, content, commit_details)
gollum_wiki.clear_cache gollum_wiki.clear_cache
...@@ -68,8 +70,9 @@ module Gitlab ...@@ -68,8 +70,9 @@ module Gitlab
end end
end end
# Disable because of https://gitlab.com/gitlab-org/gitlab-ce/issues/42039
def page(title:, version: nil, dir: nil) def page(title:, version: nil, dir: nil)
@repository.gitaly_migrate(:wiki_find_page) do |is_enabled| @repository.gitaly_migrate(:wiki_find_page, status: Gitlab::GitalyClient::MigrationStatus::DISABLED) do |is_enabled|
if is_enabled if is_enabled
gitaly_find_page(title: title, version: version, dir: dir) gitaly_find_page(title: title, version: version, dir: dir)
else else
...@@ -192,7 +195,10 @@ module Gitlab ...@@ -192,7 +195,10 @@ module Gitlab
assert_type!(format, Symbol) assert_type!(format, Symbol)
assert_type!(commit_details, CommitDetails) assert_type!(commit_details, CommitDetails)
gollum_wiki.write_page(name, format, content, commit_details.to_h) filename = File.basename(name)
dir = (tmp_dir = File.dirname(name)) == '.' ? '' : tmp_dir
gollum_wiki.write_page(filename, format, content, commit_details.to_h, dir)
nil nil
rescue Gollum::DuplicatePageError => e rescue Gollum::DuplicatePageError => e
...@@ -210,7 +216,15 @@ module Gitlab ...@@ -210,7 +216,15 @@ module Gitlab
assert_type!(format, Symbol) assert_type!(format, Symbol)
assert_type!(commit_details, CommitDetails) assert_type!(commit_details, CommitDetails)
gollum_wiki.update_page(gollum_page_by_path(page_path), title, format, content, commit_details.to_h) page = gollum_page_by_path(page_path)
committer = Gollum::Committer.new(page.wiki, commit_details.to_h)
# Instead of performing two renames if the title has changed,
# the update_page will only update the format and content and
# the rename_page will do anything related to moving/renaming
gollum_wiki.update_page(page, page.name, format, content, committer: committer)
gollum_wiki.rename_page(page, title, committer: committer)
committer.commit
nil nil
end end
......
...@@ -183,6 +183,32 @@ module Gitlab ...@@ -183,6 +183,32 @@ module Gitlab
end end
end end
def user_squash(user, squash_id, branch, start_sha, end_sha, author, message)
request = Gitaly::UserSquashRequest.new(
repository: @gitaly_repo,
user: Gitlab::Git::User.from_gitlab(user).to_gitaly,
squash_id: squash_id.to_s,
branch: encode_binary(branch),
start_sha: start_sha,
end_sha: end_sha,
author: Gitlab::Git::User.from_gitlab(author).to_gitaly,
commit_message: encode_binary(message)
)
response = GitalyClient.call(
@repository.storage,
:operation_service,
:user_squash,
request
)
if response.git_error.presence
raise Gitlab::Git::Repository::GitError, response.git_error
end
response.squash_sha
end
def user_commit_files( def user_commit_files(
user, branch_name, commit_message, actions, author_email, author_name, user, branch_name, commit_message, actions, author_email, author_name,
start_branch_name, start_repository) start_branch_name, start_repository)
......
module Gitlab
module QueryLimiting
# Returns true if we should enable tracking of query counts.
#
# This is only enabled in production/staging if we're running on GitLab.com.
# This ensures we don't produce any errors that users can't do anything
# about themselves.
def self.enable?
Gitlab.com? || Rails.env.development? || Rails.env.test?
end
# Allows the current request to execute any number of SQL queries.
#
# This method should _only_ be used when there's a corresponding issue to
# reduce the number of queries.
#
# The issue URL is only meant to push developers into creating an issue
# instead of blindly whitelisting offending blocks of code.
def self.whitelist(issue_url)
return unless enable_whitelist?
unless issue_url.start_with?('https://')
raise(
ArgumentError,
'You must provide a valid issue URL in order to whitelist a block of code'
)
end
Transaction&.current&.whitelisted = true
end
def self.enable_whitelist?
Rails.env.development? || Rails.env.test?
end
end
end
module Gitlab
module QueryLimiting
class ActiveSupportSubscriber < ActiveSupport::Subscriber
attach_to :active_record
def sql(*)
Transaction.current&.increment
end
end
end
end
# frozen_string_literal: true
module Gitlab
module QueryLimiting
# Middleware for reporting (or raising) when a request performs more than a
# certain amount of database queries.
class Middleware
CONTROLLER_KEY = 'action_controller.instance'.freeze
ENDPOINT_KEY = 'api.endpoint'.freeze
def initialize(app)
@app = app
end
def call(env)
transaction, retval = Transaction.run do
@app.call(env)
end
transaction.action = action_name(env)
transaction.act_upon_results
retval
end
def action_name(env)
if env[CONTROLLER_KEY]
action_for_rails(env)
elsif env[ENDPOINT_KEY]
action_for_grape(env)
end
end
private
def action_for_rails(env)
controller = env[CONTROLLER_KEY]
action = "#{controller.class.name}##{controller.action_name}"
if controller.content_type == 'text/html'
action
else
"#{action} (#{controller.content_type})"
end
end
def action_for_grape(env)
endpoint = env[ENDPOINT_KEY]
route = endpoint.route rescue nil
"#{route.request_method} #{route.path}" if route
end
end
end
end
module Gitlab
module QueryLimiting
class Transaction
THREAD_KEY = :__gitlab_query_counts_transaction
attr_accessor :count, :whitelisted
# The name of the action (e.g. `UsersController#show`) that is being
# executed.
attr_accessor :action
# The maximum number of SQL queries that can be executed in a request. For
# the sake of keeping things simple we hardcode this value here, it's not
# supposed to be changed very often anyway.
THRESHOLD = 100
# Error that is raised whenever exceeding the maximum number of queries.
ThresholdExceededError = Class.new(StandardError)
def self.current
Thread.current[THREAD_KEY]
end
# Starts a new transaction and returns it and the blocks' return value.
#
# Example:
#
# transaction, retval = Transaction.run do
# 10
# end
#
# retval # => 10
def self.run
transaction = new
Thread.current[THREAD_KEY] = transaction
[transaction, yield]
ensure
Thread.current[THREAD_KEY] = nil
end
def initialize
@action = nil
@count = 0
@whitelisted = false
end
# Sends a notification based on the number of executed SQL queries.
def act_upon_results
return unless threshold_exceeded?
error = ThresholdExceededError.new(error_message)
if raise_error?
raise(error)
else
# Raven automatically logs to the Rails log if disabled, thus we don't
# need to manually log anything in case Sentry support is not enabled.
Raven.capture_exception(error)
end
end
def increment
@count += 1 unless whitelisted
end
def raise_error?
Rails.env.test?
end
def threshold_exceeded?
count > THRESHOLD
end
def error_message
header = 'Too many SQL queries were executed'
header += " in #{action}" if action
"#{header}: a maximum of #{THRESHOLD} is allowed but #{count} SQL queries were executed"
end
end
end
end
...@@ -2,7 +2,7 @@ desc 'Code duplication analyze via flay' ...@@ -2,7 +2,7 @@ desc 'Code duplication analyze via flay'
task :flay do task :flay do
output = `bundle exec flay --mass 35 app/ lib/gitlab/ 2> #{File::NULL}` output = `bundle exec flay --mass 35 app/ lib/gitlab/ 2> #{File::NULL}`
if output.include? "Similar code found" if output.include?("Similar code found") || output.include?("IDENTICAL code found")
puts output puts output
exit 1 exit 1
end end
......
This diff is collapsed.
require 'spec_helper'
describe UserCalloutsController do
let(:user) { create(:user) }
before do
sign_in(user)
end
describe "POST #create" do
subject { post :create, feature_name: feature_name, format: :json }
context 'with valid feature name' do
let(:feature_name) { UserCallout.feature_names.keys.first }
context 'when callout entry does not exist' do
it 'should create a callout entry with dismissed state' do
expect { subject }.to change { UserCallout.count }.by(1)
end
it 'should return success' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
context 'when callout entry already exists' do
let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) }
it 'should return success' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
end
end
context 'with invalid feature name' do
let(:feature_name) { 'bogus_feature_name' }
it 'should return bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
end
end
end
...@@ -12,7 +12,7 @@ describe GeoNodeStatus, :geo do ...@@ -12,7 +12,7 @@ describe GeoNodeStatus, :geo do
let!(:project_3) { create(:project) } let!(:project_3) { create(:project) }
let!(:project_4) { create(:project) } let!(:project_4) { create(:project) }
subject { described_class.current_node_status } subject(:status) { described_class.current_node_status }
before do before do
stub_current_geo_node(secondary) stub_current_geo_node(secondary)
...@@ -425,6 +425,14 @@ describe GeoNodeStatus, :geo do ...@@ -425,6 +425,14 @@ describe GeoNodeStatus, :geo do
end end
end end
describe '#version' do
it { expect(status.version).to eq(Gitlab::VERSION) }
end
describe '#revision' do
it { expect(status.revision).to eq(Gitlab::REVISION) }
end
describe '#[]' do describe '#[]' do
it 'returns values for each attribute' do it 'returns values for each attribute' do
expect(subject[:repositories_count]).to eq(4) expect(subject[:repositories_count]).to eq(4)
......
...@@ -25,6 +25,8 @@ FactoryBot.define do ...@@ -25,6 +25,8 @@ FactoryBot.define do
cursor_last_event_id 1 cursor_last_event_id 1
cursor_last_event_timestamp { Time.now.to_i } cursor_last_event_timestamp { Time.now.to_i }
last_successful_status_check_timestamp { Time.now.beginning_of_day } last_successful_status_check_timestamp { Time.now.beginning_of_day }
version { Gitlab::VERSION }
revision { Gitlab::REVISION }
end end
trait :unhealthy do trait :unhealthy do
......
...@@ -125,6 +125,12 @@ FactoryBot.define do ...@@ -125,6 +125,12 @@ FactoryBot.define do
avatar { fixture_file_upload('spec/fixtures/dk.png') } avatar { fixture_file_upload('spec/fixtures/dk.png') }
end end
trait :with_export do
after(:create) do |project, evaluator|
ProjectExportWorker.new.perform(project.creator.id, project.id)
end
end
trait :broken_storage do trait :broken_storage do
after(:create) do |project| after(:create) do |project|
project.update_column(:repository_storage, 'broken') project.update_column(:repository_storage, 'broken')
......
...@@ -3,26 +3,29 @@ FactoryBot.define do ...@@ -3,26 +3,29 @@ FactoryBot.define do
model { build(:project) } model { build(:project) }
size 100.kilobytes size 100.kilobytes
uploader "AvatarUploader" uploader "AvatarUploader"
mount_point :avatar
secret nil
store ObjectStorage::Store::LOCAL store ObjectStorage::Store::LOCAL
# we should build a mount agnostic upload by default # we should build a mount agnostic upload by default
transient do transient do
mounted_as :avatar filename 'myfile.jpg'
secret SecureRandom.hex
end end
# this needs to comply with RecordsUpload::Concern#upload_path # this needs to comply with RecordsUpload::Concern#upload_path
path { File.join("uploads/-/system", model.class.to_s.underscore, mounted_as.to_s, 'avatar.jpg') } path { File.join("uploads/-/system", model.class.to_s.underscore, mount_point.to_s, 'avatar.jpg') }
trait :personal_snippet_upload do trait :personal_snippet_upload do
model { build(:personal_snippet) } model { build(:personal_snippet) }
path { File.join(secret, 'myfile.jpg') } path { File.join(secret, filename) }
uploader "PersonalFileUploader" uploader "PersonalFileUploader"
secret SecureRandom.hex
end end
trait :issuable_upload do trait :issuable_upload do
path { File.join(secret, 'myfile.jpg') } path { File.join(secret, filename) }
uploader "FileUploader" uploader "FileUploader"
secret SecureRandom.hex
end end
trait :object_storage do trait :object_storage do
...@@ -31,13 +34,14 @@ FactoryBot.define do ...@@ -31,13 +34,14 @@ FactoryBot.define do
trait :namespace_upload do trait :namespace_upload do
model { build(:group) } model { build(:group) }
path { File.join(secret, 'myfile.jpg') } path { File.join(secret, filename) }
uploader "NamespaceFileUploader" uploader "NamespaceFileUploader"
secret SecureRandom.hex
end end
trait :attachment_upload do trait :attachment_upload do
transient do transient do
mounted_as :attachment mount_point :attachment
end end
model { build(:note) } model { build(:note) }
......
FactoryBot.define do
factory :user_callout do
feature_name :gke_cluster_integration
user
end
end
require 'spec_helper' require 'spec_helper'
feature 'Import/Export - Namespace export file cleanup', :js do feature 'Import/Export - Namespace export file cleanup', :js do
let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } let(:export_path) { Dir.mktmpdir('namespace_export_file_spec') }
let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys }
let(:project) { create(:project) } before do
allow(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
background do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
end end
after do after do
FileUtils.rm_rf(export_path, secure: true) FileUtils.rm_rf(export_path, secure: true)
end end
context 'admin user' do shared_examples_for 'handling project exports on namespace change' do
let!(:old_export_path) { project.export_path }
before do before do
sign_in(create(:admin)) sign_in(create(:admin))
setup_export_project
end end
context 'moving the namespace' do context 'moving the namespace' do
scenario 'removes the export file' do it 'removes the export file' do
setup_export_project
old_export_path = project.export_path.dup
expect(File).to exist(old_export_path) expect(File).to exist(old_export_path)
project.namespace.update(path: 'new_path') project.namespace.update!(path: build(:namespace).path)
expect(File).not_to exist(old_export_path) expect(File).not_to exist(old_export_path)
end end
end end
context 'deleting the namespace' do context 'deleting the namespace' do
scenario 'removes the export file' do it 'removes the export file' do
setup_export_project
old_export_path = project.export_path.dup
expect(File).to exist(old_export_path) expect(File).to exist(old_export_path)
project.namespace.destroy project.namespace.destroy
...@@ -46,17 +39,29 @@ feature 'Import/Export - Namespace export file cleanup', :js do ...@@ -46,17 +39,29 @@ feature 'Import/Export - Namespace export file cleanup', :js do
expect(File).not_to exist(old_export_path) expect(File).not_to exist(old_export_path)
end end
end end
end
def setup_export_project describe 'legacy storage' do
visit edit_project_path(project) let(:project) { create(:project) }
expect(page).to have_content('Export project') it_behaves_like 'handling project exports on namespace change'
end
describe 'hashed storage' do
let(:project) { create(:project, :hashed) }
find(:link, 'Export project').send_keys(:return) it_behaves_like 'handling project exports on namespace change'
end
visit edit_project_path(project) def setup_export_project
visit edit_project_path(project)
expect(page).to have_content('Download export') expect(page).to have_content('Export project')
end
find(:link, 'Export project').send_keys(:return)
visit edit_project_path(project)
expect(page).to have_content('Download export')
end end
end end
require 'spec_helper' require 'spec_helper'
describe 'User updates wiki page' do # Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages
describe 'User updates wiki page', :skip_gitaly_mock do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
...@@ -143,6 +144,7 @@ describe 'User updates wiki page' do ...@@ -143,6 +144,7 @@ describe 'User updates wiki page' do
expect(page).to have_field('wiki[message]', with: 'Update home') expect(page).to have_field('wiki[message]', with: 'Update home')
fill_in(:wiki_content, with: 'My awesome wiki!') fill_in(:wiki_content, with: 'My awesome wiki!')
click_button('Save changes') click_button('Save changes')
expect(page).to have_content('Home') expect(page).to have_content('Home')
...@@ -151,4 +153,74 @@ describe 'User updates wiki page' do ...@@ -151,4 +153,74 @@ describe 'User updates wiki page' do
end end
end end
end end
context 'when the page is in a subdir' do
let!(:project) { create(:project, namespace: user.namespace) }
let(:project_wiki) { create(:project_wiki, project: project, user: project.creator) }
let(:page_name) { 'page_name' }
let(:page_dir) { "foo/bar/#{page_name}" }
let!(:wiki_page) { create(:wiki_page, wiki: project_wiki, attrs: { title: page_dir, content: 'Home page' }) }
before do
visit(project_wiki_edit_path(project, wiki_page))
end
it 'moves the page to the root folder' do
fill_in(:wiki_title, with: "/#{page_name}")
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, page_name))
end
it 'moves the page to other dir' do
new_page_dir = "foo1/bar1/#{page_name}"
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
end
it 'remains in the same place if title has not changed' do
original_path = project_wiki_path(project, wiki_page)
fill_in(:wiki_title, with: page_name)
click_button('Save changes')
expect(current_path).to eq(original_path)
end
it 'can be moved to a different dir with a different name' do
new_page_dir = "foo1/bar1/new_page_name"
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_page_dir))
end
it 'can be renamed and moved to the root folder' do
new_name = 'new_page_name'
fill_in(:wiki_title, with: "/#{new_name}")
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, new_name))
end
it 'squishes the title before creating the page' do
new_page_dir = " foo1 / bar1 / #{page_name} "
fill_in(:wiki_title, with: new_page_dir)
click_button('Save changes')
expect(current_path).to eq(project_wiki_path(project, "foo1/bar1/#{page_name}"))
end
end
end end
require 'spec_helper' require 'spec_helper'
describe 'User views a wiki page' do # Remove skip_gitaly_mock flag when gitaly_update_page implements moving pages
describe 'User views a wiki page', :skip_gitaly_mock do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) } let(:project) { create(:project, namespace: user.namespace) }
let(:wiki_page) do let(:wiki_page) do
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment